Inline clientside callbacks - #967

Merged
Marc-Andre-Rivet merged 12 commits into
plotly:devfrom
jjaraalm:inline_clientside
Nov 15, 2019
Merged

Inline clientside callbacks#967
Marc-Andre-Rivet merged 12 commits into
plotly:devfrom
jjaraalm:inline_clientside

Conversation

@jjaraalm

@jjaraalmjjaraalm commented Oct 16, 2019

Copy link
Copy Markdown
Contributor

Addresses at least part of #956 and adds support for writing JS clientside callbacks in python source.

Adds explicit JS source injection through a new keyword argument to Dash.clientside_callback:

clientside_callback(
ClientsideFunction('namespace_to_inject_to', 'function_to_inject'),
Output('output', 'value'),
[Input('input', 'value')],
source=""" function(value) { console.log(value); return parseInt(value, 10) + 1; } """
)

Also adds source injection via the Dash.callback decorator. This probably should be explained or documented better so that users don't think they can write arbitrary python code here. Eventually I would like to add support for transpiling python -> JS, but this is just a stepping stone.

@callback(Output('output', 'value'), [Input('input', 'value')],clientside=True)defnative_js_callback(value): # Interior source is JS that will be added to the # dash_clientide._inline_callbacks namespace.# Single-line comments are properly escaped.console.log(value);
returnparseInt(value, 10) +1;

Contributor Checklist

  • I have broken down my PR scope into the following TODO tasks
    • Add inline clientside callbacks to Dash.clientside_callback
    • Add decorated clientside callbacks to Dash.callback
  • I have run the tests locally and they passed. (refer to testing section in contributing)
  • I have added tests, or extended existing tests, to cover any new features or bugs fixed in this PR

optionals

  • I have added entry in the CHANGELOG.md
  • If this PR needs a follow-up in dash docs, community thread, I have mentioned the relevant URLS as follow
    • this github #PR number updates the dash docs
    • here is the show and tell thread in plotly dash community

@alexcjohnson

Copy link
Copy Markdown
Collaborator

@jjaraalm you're on a roll 🏎

I very much like your implementation with inline_scripts, that's simpler than what I was envisioning 🎉 - that said if it's going to be in config - and thereby intentionally exposed to users - we would need to (a) include it in the Dash constructor args, and (b) document it fully. For now, unless there are clear use cases for users to add other things to it, let's keep it private and out of config, just set it as something like self._inline_scripts.

I'd like to see if we can get rid of the source kwarg, and just replace the ClientsideFunction first arg with the function source string, as proposed in #956. This would require choosing an implicit namespace and function name. Is there a reason to want explicit names? I suppose in principle that could allow multiple callbacks to use the same function, but that feels to me like an unnecessary optimization that will lead to more confusing code, especially since such sharing would only generally be useful for very short functions. I'd propose a namespace like _dashprivate, and the name could be just the (first) output id and prop, joined with maybe a double underscore, ie output__value?

I'm not a fan of writing JS code directly in a Python function body, and I don't see how it can be a stepping stone without later becoming a breaking change. If it's alright with you, let's leave this flavor out unless and until we're ready to do the transpiled version.

@jjaraalm

Copy link
Copy Markdown
ContributorAuthor

Thanks for the feedback. I wasn't thinking so much about the implications of putting it in config, I just liked the symmetry with external_scripts. I could see why some people might use it in the constructor (to avoid writing simple JS assets), but I agree it probably shouldn't be there, I'll fix that.

I agree with your assessment of the utility of namespace/function name for inline scripts. Really I only see that being used for intricate, overly complex, and probably not well-documented callback ... which I'm guilty of sometimes haha.

I think JS in python feels dirty, but that said I dislike code-in-strings even more. It's more clear that it's a foreign language, but also there's no easy way to get syntax highlighting or linting in editors (that I know of). I've always found code-in-strings to be a pain to write and debug. I understand your points though, and we can leave it out.

@jjaraalm

Copy link
Copy Markdown
ContributorAuthor

I decided to create a new namespace prefixed by _dashprivate_ for each output id. This is to prevent the highly unlikely case where someone creates a custom component with horrible prop names then decides to use even worse id names which result in a conflict.

Comment threaddash/dash.py
argument that describes which JavaScript function to call
(Dash will look for the JavaScript function at
`window[namespace][function_name]`).
`window.dash_clientside[namespace][function_name]`), or it may take

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

good catch! 🔬

Comment threaddash/dash.py Outdated
@alexcjohnson

Copy link
Copy Markdown
Collaborator

Alright, Looks good to me! Just needs a changelog (and updating so it doesn't conflict with your other PR 😏) and we'll be ready to go.

@Marc-Andre-Rivet

Copy link
Copy Markdown
Contributor

@alexcjohnson@jjaraalm Looking into the DashR parity implementation and came to the conclusion that we wanted to prepend the namespace with _dashprivate_ -- glad to see you've already thought of everything! This looks great. As previously stated, just needs a changelog and an update 🎉

@jjaraalm

Copy link
Copy Markdown
ContributorAuthor

Sorry guys, was traveling. This should take care of it I think.

@alexcjohnsonalexcjohnson left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Love it, thanks for the finishing touches. 💃
I'm going to hold off merging for a little bit though in case we want to make a patch release on Monday's 1.6.0

@Marc-Andre-RivetMarc-Andre-Rivet 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.

💃 @jjaraalm Took the liberty of merging in dev changes. Will merge it in once the build passes one last time. Again thanks for the contribution! The next planned release is 1.7.0 around 11/25.

@Marc-Andre-Rivet
Marc-Andre-Rivet merged commit 5d9f578 into plotly:devNov 15, 2019
@ieipi

ieipi commented Dec 18, 2019

Copy link
Copy Markdown

I've updated to dash=1.7 and want to use the new inline callbacks feature, but got the following error:

TypeError: clientside_callback() got an unexpected keyword argument 'source'
(base)

and when i want to use the native_js_callback syntax, it gave the error:

callback() got an unexpected keyword argument 'clientside'

@alexcjohnson

Copy link
Copy Markdown
Collaborator

@ieipi the final implementation is not as described in the lead comment for the PR. See https://community.plot.ly/t/dash-v1-7-0-released/31961 for an example.

HammadTheOne pushed a commit to HammadTheOne/dash that referenced this pull request May 28, 2021
…ted-git-info-2.8.9
Bump hosted-git-info from 2.7.1 to 2.8.9
HammadTheOne pushed a commit that referenced this pull request Jul 23, 2021
…t-info-2.8.9
Bump hosted-git-info from 2.7.1 to 2.8.9
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@jjaraalm@alexcjohnson@Marc-Andre-Rivet@ieipi
, '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

Inline clientside callbacks - #967

Merged
Marc-Andre-Rivet merged 12 commits into
plotly:devfrom
jjaraalm:inline_clientside
Nov 15, 2019
Merged

Inline clientside callbacks#967
Marc-Andre-Rivet merged 12 commits into
plotly:devfrom
jjaraalm:inline_clientside

Conversation

@jjaraalm

@jjaraalmjjaraalm commented Oct 16, 2019

Copy link
Copy Markdown
Contributor

Addresses at least part of #956 and adds support for writing JS clientside callbacks in python source.

Adds explicit JS source injection through a new keyword argument to Dash.clientside_callback:

clientside_callback(
ClientsideFunction('namespace_to_inject_to', 'function_to_inject'),
Output('output', 'value'),
[Input('input', 'value')],
source=""" function(value) { console.log(value); return parseInt(value, 10) + 1; } """
)

Also adds source injection via the Dash.callback decorator. This probably should be explained or documented better so that users don't think they can write arbitrary python code here. Eventually I would like to add support for transpiling python -> JS, but this is just a stepping stone.

@callback(Output('output', 'value'), [Input('input', 'value')],clientside=True)defnative_js_callback(value): # Interior source is JS that will be added to the # dash_clientide._inline_callbacks namespace.# Single-line comments are properly escaped.console.log(value);
returnparseInt(value, 10) +1;

Contributor Checklist

  • I have broken down my PR scope into the following TODO tasks
    • Add inline clientside callbacks to Dash.clientside_callback
    • Add decorated clientside callbacks to Dash.callback
  • I have run the tests locally and they passed. (refer to testing section in contributing)
  • I have added tests, or extended existing tests, to cover any new features or bugs fixed in this PR

optionals

  • I have added entry in the CHANGELOG.md
  • If this PR needs a follow-up in dash docs, community thread, I have mentioned the relevant URLS as follow
    • this github #PR number updates the dash docs
    • here is the show and tell thread in plotly dash community

@alexcjohnson

Copy link
Copy Markdown
Collaborator

@jjaraalm you're on a roll 🏎

I very much like your implementation with inline_scripts, that's simpler than what I was envisioning 🎉 - that said if it's going to be in config - and thereby intentionally exposed to users - we would need to (a) include it in the Dash constructor args, and (b) document it fully. For now, unless there are clear use cases for users to add other things to it, let's keep it private and out of config, just set it as something like self._inline_scripts.

I'd like to see if we can get rid of the source kwarg, and just replace the ClientsideFunction first arg with the function source string, as proposed in #956. This would require choosing an implicit namespace and function name. Is there a reason to want explicit names? I suppose in principle that could allow multiple callbacks to use the same function, but that feels to me like an unnecessary optimization that will lead to more confusing code, especially since such sharing would only generally be useful for very short functions. I'd propose a namespace like _dashprivate, and the name could be just the (first) output id and prop, joined with maybe a double underscore, ie output__value?

I'm not a fan of writing JS code directly in a Python function body, and I don't see how it can be a stepping stone without later becoming a breaking change. If it's alright with you, let's leave this flavor out unless and until we're ready to do the transpiled version.

@jjaraalm

Copy link
Copy Markdown
ContributorAuthor

Thanks for the feedback. I wasn't thinking so much about the implications of putting it in config, I just liked the symmetry with external_scripts. I could see why some people might use it in the constructor (to avoid writing simple JS assets), but I agree it probably shouldn't be there, I'll fix that.

I agree with your assessment of the utility of namespace/function name for inline scripts. Really I only see that being used for intricate, overly complex, and probably not well-documented callback ... which I'm guilty of sometimes haha.

I think JS in python feels dirty, but that said I dislike code-in-strings even more. It's more clear that it's a foreign language, but also there's no easy way to get syntax highlighting or linting in editors (that I know of). I've always found code-in-strings to be a pain to write and debug. I understand your points though, and we can leave it out.

@jjaraalm

Copy link
Copy Markdown
ContributorAuthor

I decided to create a new namespace prefixed by _dashprivate_ for each output id. This is to prevent the highly unlikely case where someone creates a custom component with horrible prop names then decides to use even worse id names which result in a conflict.

Comment threaddash/dash.py
argument that describes which JavaScript function to call
(Dash will look for the JavaScript function at
`window[namespace][function_name]`).
`window.dash_clientside[namespace][function_name]`), or it may take

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

good catch! 🔬

Comment threaddash/dash.py Outdated
@alexcjohnson

Copy link
Copy Markdown
Collaborator

Alright, Looks good to me! Just needs a changelog (and updating so it doesn't conflict with your other PR 😏) and we'll be ready to go.

@Marc-Andre-Rivet

Copy link
Copy Markdown
Contributor

@alexcjohnson@jjaraalm Looking into the DashR parity implementation and came to the conclusion that we wanted to prepend the namespace with _dashprivate_ -- glad to see you've already thought of everything! This looks great. As previously stated, just needs a changelog and an update 🎉

@jjaraalm

Copy link
Copy Markdown
ContributorAuthor

Sorry guys, was traveling. This should take care of it I think.

@alexcjohnsonalexcjohnson left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Love it, thanks for the finishing touches. 💃
I'm going to hold off merging for a little bit though in case we want to make a patch release on Monday's 1.6.0

@Marc-Andre-RivetMarc-Andre-Rivet 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.

💃 @jjaraalm Took the liberty of merging in dev changes. Will merge it in once the build passes one last time. Again thanks for the contribution! The next planned release is 1.7.0 around 11/25.

@Marc-Andre-Rivet
Marc-Andre-Rivet merged commit 5d9f578 into plotly:devNov 15, 2019
@ieipi

ieipi commented Dec 18, 2019

Copy link
Copy Markdown

I've updated to dash=1.7 and want to use the new inline callbacks feature, but got the following error:

TypeError: clientside_callback() got an unexpected keyword argument 'source'
(base)

and when i want to use the native_js_callback syntax, it gave the error:

callback() got an unexpected keyword argument 'clientside'

@alexcjohnson

Copy link
Copy Markdown
Collaborator

@ieipi the final implementation is not as described in the lead comment for the PR. See https://community.plot.ly/t/dash-v1-7-0-released/31961 for an example.

HammadTheOne pushed a commit to HammadTheOne/dash that referenced this pull request May 28, 2021
…ted-git-info-2.8.9
Bump hosted-git-info from 2.7.1 to 2.8.9
HammadTheOne pushed a commit that referenced this pull request Jul 23, 2021
…t-info-2.8.9
Bump hosted-git-info from 2.7.1 to 2.8.9
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@jjaraalm@alexcjohnson@Marc-Andre-Rivet@ieipi
, '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

Inline clientside callbacks - #967

Merged
Marc-Andre-Rivet merged 12 commits into
plotly:devfrom
jjaraalm:inline_clientside
Nov 15, 2019
Merged

Inline clientside callbacks#967
Marc-Andre-Rivet merged 12 commits into
plotly:devfrom
jjaraalm:inline_clientside

Conversation

@jjaraalm

@jjaraalmjjaraalm commented Oct 16, 2019

Copy link
Copy Markdown
Contributor

Addresses at least part of #956 and adds support for writing JS clientside callbacks in python source.

Adds explicit JS source injection through a new keyword argument to Dash.clientside_callback:

clientside_callback(
ClientsideFunction('namespace_to_inject_to', 'function_to_inject'),
Output('output', 'value'),
[Input('input', 'value')],
source=""" function(value) { console.log(value); return parseInt(value, 10) + 1; } """
)

Also adds source injection via the Dash.callback decorator. This probably should be explained or documented better so that users don't think they can write arbitrary python code here. Eventually I would like to add support for transpiling python -> JS, but this is just a stepping stone.

@callback(Output('output', 'value'), [Input('input', 'value')],clientside=True)defnative_js_callback(value): # Interior source is JS that will be added to the # dash_clientide._inline_callbacks namespace.# Single-line comments are properly escaped.console.log(value);
returnparseInt(value, 10) +1;

Contributor Checklist

  • I have broken down my PR scope into the following TODO tasks
    • Add inline clientside callbacks to Dash.clientside_callback
    • Add decorated clientside callbacks to Dash.callback
  • I have run the tests locally and they passed. (refer to testing section in contributing)
  • I have added tests, or extended existing tests, to cover any new features or bugs fixed in this PR

optionals

  • I have added entry in the CHANGELOG.md
  • If this PR needs a follow-up in dash docs, community thread, I have mentioned the relevant URLS as follow
    • this github #PR number updates the dash docs
    • here is the show and tell thread in plotly dash community

@alexcjohnson

Copy link
Copy Markdown
Collaborator

@jjaraalm you're on a roll 🏎

I very much like your implementation with inline_scripts, that's simpler than what I was envisioning 🎉 - that said if it's going to be in config - and thereby intentionally exposed to users - we would need to (a) include it in the Dash constructor args, and (b) document it fully. For now, unless there are clear use cases for users to add other things to it, let's keep it private and out of config, just set it as something like self._inline_scripts.

I'd like to see if we can get rid of the source kwarg, and just replace the ClientsideFunction first arg with the function source string, as proposed in #956. This would require choosing an implicit namespace and function name. Is there a reason to want explicit names? I suppose in principle that could allow multiple callbacks to use the same function, but that feels to me like an unnecessary optimization that will lead to more confusing code, especially since such sharing would only generally be useful for very short functions. I'd propose a namespace like _dashprivate, and the name could be just the (first) output id and prop, joined with maybe a double underscore, ie output__value?

I'm not a fan of writing JS code directly in a Python function body, and I don't see how it can be a stepping stone without later becoming a breaking change. If it's alright with you, let's leave this flavor out unless and until we're ready to do the transpiled version.

@jjaraalm

Copy link
Copy Markdown
ContributorAuthor

Thanks for the feedback. I wasn't thinking so much about the implications of putting it in config, I just liked the symmetry with external_scripts. I could see why some people might use it in the constructor (to avoid writing simple JS assets), but I agree it probably shouldn't be there, I'll fix that.

I agree with your assessment of the utility of namespace/function name for inline scripts. Really I only see that being used for intricate, overly complex, and probably not well-documented callback ... which I'm guilty of sometimes haha.

I think JS in python feels dirty, but that said I dislike code-in-strings even more. It's more clear that it's a foreign language, but also there's no easy way to get syntax highlighting or linting in editors (that I know of). I've always found code-in-strings to be a pain to write and debug. I understand your points though, and we can leave it out.

@jjaraalm

Copy link
Copy Markdown
ContributorAuthor

I decided to create a new namespace prefixed by _dashprivate_ for each output id. This is to prevent the highly unlikely case where someone creates a custom component with horrible prop names then decides to use even worse id names which result in a conflict.

Comment threaddash/dash.py
argument that describes which JavaScript function to call
(Dash will look for the JavaScript function at
`window[namespace][function_name]`).
`window.dash_clientside[namespace][function_name]`), or it may take

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

good catch! 🔬

Comment threaddash/dash.py Outdated
@alexcjohnson

Copy link
Copy Markdown
Collaborator

Alright, Looks good to me! Just needs a changelog (and updating so it doesn't conflict with your other PR 😏) and we'll be ready to go.

@Marc-Andre-Rivet

Copy link
Copy Markdown
Contributor

@alexcjohnson@jjaraalm Looking into the DashR parity implementation and came to the conclusion that we wanted to prepend the namespace with _dashprivate_ -- glad to see you've already thought of everything! This looks great. As previously stated, just needs a changelog and an update 🎉

@jjaraalm

Copy link
Copy Markdown
ContributorAuthor

Sorry guys, was traveling. This should take care of it I think.

@alexcjohnsonalexcjohnson left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Love it, thanks for the finishing touches. 💃
I'm going to hold off merging for a little bit though in case we want to make a patch release on Monday's 1.6.0

@Marc-Andre-RivetMarc-Andre-Rivet 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.

💃 @jjaraalm Took the liberty of merging in dev changes. Will merge it in once the build passes one last time. Again thanks for the contribution! The next planned release is 1.7.0 around 11/25.

@Marc-Andre-Rivet
Marc-Andre-Rivet merged commit 5d9f578 into plotly:devNov 15, 2019
@ieipi

ieipi commented Dec 18, 2019

Copy link
Copy Markdown

I've updated to dash=1.7 and want to use the new inline callbacks feature, but got the following error:

TypeError: clientside_callback() got an unexpected keyword argument 'source'
(base)

and when i want to use the native_js_callback syntax, it gave the error:

callback() got an unexpected keyword argument 'clientside'

@alexcjohnson

Copy link
Copy Markdown
Collaborator

@ieipi the final implementation is not as described in the lead comment for the PR. See https://community.plot.ly/t/dash-v1-7-0-released/31961 for an example.

HammadTheOne pushed a commit to HammadTheOne/dash that referenced this pull request May 28, 2021
…ted-git-info-2.8.9
Bump hosted-git-info from 2.7.1 to 2.8.9
HammadTheOne pushed a commit that referenced this pull request Jul 23, 2021
…t-info-2.8.9
Bump hosted-git-info from 2.7.1 to 2.8.9
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@jjaraalm@alexcjohnson@Marc-Andre-Rivet@ieipi
, '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

Inline clientside callbacks - #967

Merged
Marc-Andre-Rivet merged 12 commits into
plotly:devfrom
jjaraalm:inline_clientside
Nov 15, 2019
Merged

Inline clientside callbacks#967
Marc-Andre-Rivet merged 12 commits into
plotly:devfrom
jjaraalm:inline_clientside

Conversation

@jjaraalm

@jjaraalmjjaraalm commented Oct 16, 2019

Copy link
Copy Markdown
Contributor

Addresses at least part of #956 and adds support for writing JS clientside callbacks in python source.

Adds explicit JS source injection through a new keyword argument to Dash.clientside_callback:

clientside_callback(
ClientsideFunction('namespace_to_inject_to', 'function_to_inject'),
Output('output', 'value'),
[Input('input', 'value')],
source=""" function(value) { console.log(value); return parseInt(value, 10) + 1; } """
)

Also adds source injection via the Dash.callback decorator. This probably should be explained or documented better so that users don't think they can write arbitrary python code here. Eventually I would like to add support for transpiling python -> JS, but this is just a stepping stone.

@callback(Output('output', 'value'), [Input('input', 'value')],clientside=True)defnative_js_callback(value): # Interior source is JS that will be added to the # dash_clientide._inline_callbacks namespace.# Single-line comments are properly escaped.console.log(value);
returnparseInt(value, 10) +1;

Contributor Checklist

  • I have broken down my PR scope into the following TODO tasks
    • Add inline clientside callbacks to Dash.clientside_callback
    • Add decorated clientside callbacks to Dash.callback
  • I have run the tests locally and they passed. (refer to testing section in contributing)
  • I have added tests, or extended existing tests, to cover any new features or bugs fixed in this PR

optionals

  • I have added entry in the CHANGELOG.md
  • If this PR needs a follow-up in dash docs, community thread, I have mentioned the relevant URLS as follow
    • this github #PR number updates the dash docs
    • here is the show and tell thread in plotly dash community

@alexcjohnson

Copy link
Copy Markdown
Collaborator

@jjaraalm you're on a roll 🏎

I very much like your implementation with inline_scripts, that's simpler than what I was envisioning 🎉 - that said if it's going to be in config - and thereby intentionally exposed to users - we would need to (a) include it in the Dash constructor args, and (b) document it fully. For now, unless there are clear use cases for users to add other things to it, let's keep it private and out of config, just set it as something like self._inline_scripts.

I'd like to see if we can get rid of the source kwarg, and just replace the ClientsideFunction first arg with the function source string, as proposed in #956. This would require choosing an implicit namespace and function name. Is there a reason to want explicit names? I suppose in principle that could allow multiple callbacks to use the same function, but that feels to me like an unnecessary optimization that will lead to more confusing code, especially since such sharing would only generally be useful for very short functions. I'd propose a namespace like _dashprivate, and the name could be just the (first) output id and prop, joined with maybe a double underscore, ie output__value?

I'm not a fan of writing JS code directly in a Python function body, and I don't see how it can be a stepping stone without later becoming a breaking change. If it's alright with you, let's leave this flavor out unless and until we're ready to do the transpiled version.

@jjaraalm

Copy link
Copy Markdown
ContributorAuthor

Thanks for the feedback. I wasn't thinking so much about the implications of putting it in config, I just liked the symmetry with external_scripts. I could see why some people might use it in the constructor (to avoid writing simple JS assets), but I agree it probably shouldn't be there, I'll fix that.

I agree with your assessment of the utility of namespace/function name for inline scripts. Really I only see that being used for intricate, overly complex, and probably not well-documented callback ... which I'm guilty of sometimes haha.

I think JS in python feels dirty, but that said I dislike code-in-strings even more. It's more clear that it's a foreign language, but also there's no easy way to get syntax highlighting or linting in editors (that I know of). I've always found code-in-strings to be a pain to write and debug. I understand your points though, and we can leave it out.

@jjaraalm

Copy link
Copy Markdown
ContributorAuthor

I decided to create a new namespace prefixed by _dashprivate_ for each output id. This is to prevent the highly unlikely case where someone creates a custom component with horrible prop names then decides to use even worse id names which result in a conflict.

Comment threaddash/dash.py
argument that describes which JavaScript function to call
(Dash will look for the JavaScript function at
`window[namespace][function_name]`).
`window.dash_clientside[namespace][function_name]`), or it may take

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

good catch! 🔬

Comment threaddash/dash.py Outdated
@alexcjohnson

Copy link
Copy Markdown
Collaborator

Alright, Looks good to me! Just needs a changelog (and updating so it doesn't conflict with your other PR 😏) and we'll be ready to go.

@Marc-Andre-Rivet

Copy link
Copy Markdown
Contributor

@alexcjohnson@jjaraalm Looking into the DashR parity implementation and came to the conclusion that we wanted to prepend the namespace with _dashprivate_ -- glad to see you've already thought of everything! This looks great. As previously stated, just needs a changelog and an update 🎉

@jjaraalm

Copy link
Copy Markdown
ContributorAuthor

Sorry guys, was traveling. This should take care of it I think.

@alexcjohnsonalexcjohnson left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Love it, thanks for the finishing touches. 💃
I'm going to hold off merging for a little bit though in case we want to make a patch release on Monday's 1.6.0

@Marc-Andre-RivetMarc-Andre-Rivet 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.

💃 @jjaraalm Took the liberty of merging in dev changes. Will merge it in once the build passes one last time. Again thanks for the contribution! The next planned release is 1.7.0 around 11/25.

@Marc-Andre-Rivet
Marc-Andre-Rivet merged commit 5d9f578 into plotly:devNov 15, 2019
@ieipi

ieipi commented Dec 18, 2019

Copy link
Copy Markdown

I've updated to dash=1.7 and want to use the new inline callbacks feature, but got the following error:

TypeError: clientside_callback() got an unexpected keyword argument 'source'
(base)

and when i want to use the native_js_callback syntax, it gave the error:

callback() got an unexpected keyword argument 'clientside'

@alexcjohnson

Copy link
Copy Markdown
Collaborator

@ieipi the final implementation is not as described in the lead comment for the PR. See https://community.plot.ly/t/dash-v1-7-0-released/31961 for an example.

HammadTheOne pushed a commit to HammadTheOne/dash that referenced this pull request May 28, 2021
…ted-git-info-2.8.9
Bump hosted-git-info from 2.7.1 to 2.8.9
HammadTheOne pushed a commit that referenced this pull request Jul 23, 2021
…t-info-2.8.9
Bump hosted-git-info from 2.7.1 to 2.8.9
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@jjaraalm@alexcjohnson@Marc-Andre-Rivet@ieipi
, '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

Inline clientside callbacks - #967

Merged
Marc-Andre-Rivet merged 12 commits into
plotly:devfrom
jjaraalm:inline_clientside
Nov 15, 2019
Merged

Inline clientside callbacks#967
Marc-Andre-Rivet merged 12 commits into
plotly:devfrom
jjaraalm:inline_clientside

Conversation

@jjaraalm

@jjaraalmjjaraalm commented Oct 16, 2019

Copy link
Copy Markdown
Contributor

Addresses at least part of #956 and adds support for writing JS clientside callbacks in python source.

Adds explicit JS source injection through a new keyword argument to Dash.clientside_callback:

clientside_callback(
ClientsideFunction('namespace_to_inject_to', 'function_to_inject'),
Output('output', 'value'),
[Input('input', 'value')],
source=""" function(value) { console.log(value); return parseInt(value, 10) + 1; } """
)

Also adds source injection via the Dash.callback decorator. This probably should be explained or documented better so that users don't think they can write arbitrary python code here. Eventually I would like to add support for transpiling python -> JS, but this is just a stepping stone.

@callback(Output('output', 'value'), [Input('input', 'value')],clientside=True)defnative_js_callback(value): # Interior source is JS that will be added to the # dash_clientide._inline_callbacks namespace.# Single-line comments are properly escaped.console.log(value);
returnparseInt(value, 10) +1;

Contributor Checklist

  • I have broken down my PR scope into the following TODO tasks
    • Add inline clientside callbacks to Dash.clientside_callback
    • Add decorated clientside callbacks to Dash.callback
  • I have run the tests locally and they passed. (refer to testing section in contributing)
  • I have added tests, or extended existing tests, to cover any new features or bugs fixed in this PR

optionals

  • I have added entry in the CHANGELOG.md
  • If this PR needs a follow-up in dash docs, community thread, I have mentioned the relevant URLS as follow
    • this github #PR number updates the dash docs
    • here is the show and tell thread in plotly dash community

@alexcjohnson

Copy link
Copy Markdown
Collaborator

@jjaraalm you're on a roll 🏎

I very much like your implementation with inline_scripts, that's simpler than what I was envisioning 🎉 - that said if it's going to be in config - and thereby intentionally exposed to users - we would need to (a) include it in the Dash constructor args, and (b) document it fully. For now, unless there are clear use cases for users to add other things to it, let's keep it private and out of config, just set it as something like self._inline_scripts.

I'd like to see if we can get rid of the source kwarg, and just replace the ClientsideFunction first arg with the function source string, as proposed in #956. This would require choosing an implicit namespace and function name. Is there a reason to want explicit names? I suppose in principle that could allow multiple callbacks to use the same function, but that feels to me like an unnecessary optimization that will lead to more confusing code, especially since such sharing would only generally be useful for very short functions. I'd propose a namespace like _dashprivate, and the name could be just the (first) output id and prop, joined with maybe a double underscore, ie output__value?

I'm not a fan of writing JS code directly in a Python function body, and I don't see how it can be a stepping stone without later becoming a breaking change. If it's alright with you, let's leave this flavor out unless and until we're ready to do the transpiled version.

@jjaraalm

Copy link
Copy Markdown
ContributorAuthor

Thanks for the feedback. I wasn't thinking so much about the implications of putting it in config, I just liked the symmetry with external_scripts. I could see why some people might use it in the constructor (to avoid writing simple JS assets), but I agree it probably shouldn't be there, I'll fix that.

I agree with your assessment of the utility of namespace/function name for inline scripts. Really I only see that being used for intricate, overly complex, and probably not well-documented callback ... which I'm guilty of sometimes haha.

I think JS in python feels dirty, but that said I dislike code-in-strings even more. It's more clear that it's a foreign language, but also there's no easy way to get syntax highlighting or linting in editors (that I know of). I've always found code-in-strings to be a pain to write and debug. I understand your points though, and we can leave it out.

@jjaraalm

Copy link
Copy Markdown
ContributorAuthor

I decided to create a new namespace prefixed by _dashprivate_ for each output id. This is to prevent the highly unlikely case where someone creates a custom component with horrible prop names then decides to use even worse id names which result in a conflict.

Comment threaddash/dash.py
argument that describes which JavaScript function to call
(Dash will look for the JavaScript function at
`window[namespace][function_name]`).
`window.dash_clientside[namespace][function_name]`), or it may take

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

good catch! 🔬

Comment threaddash/dash.py Outdated
@alexcjohnson

Copy link
Copy Markdown
Collaborator

Alright, Looks good to me! Just needs a changelog (and updating so it doesn't conflict with your other PR 😏) and we'll be ready to go.

@Marc-Andre-Rivet

Copy link
Copy Markdown
Contributor

@alexcjohnson@jjaraalm Looking into the DashR parity implementation and came to the conclusion that we wanted to prepend the namespace with _dashprivate_ -- glad to see you've already thought of everything! This looks great. As previously stated, just needs a changelog and an update 🎉

@jjaraalm

Copy link
Copy Markdown
ContributorAuthor

Sorry guys, was traveling. This should take care of it I think.

@alexcjohnsonalexcjohnson left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Love it, thanks for the finishing touches. 💃
I'm going to hold off merging for a little bit though in case we want to make a patch release on Monday's 1.6.0

@Marc-Andre-RivetMarc-Andre-Rivet 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.

💃 @jjaraalm Took the liberty of merging in dev changes. Will merge it in once the build passes one last time. Again thanks for the contribution! The next planned release is 1.7.0 around 11/25.

@Marc-Andre-Rivet
Marc-Andre-Rivet merged commit 5d9f578 into plotly:devNov 15, 2019
@ieipi

ieipi commented Dec 18, 2019

Copy link
Copy Markdown

I've updated to dash=1.7 and want to use the new inline callbacks feature, but got the following error:

TypeError: clientside_callback() got an unexpected keyword argument 'source'
(base)

and when i want to use the native_js_callback syntax, it gave the error:

callback() got an unexpected keyword argument 'clientside'

@alexcjohnson

Copy link
Copy Markdown
Collaborator

@ieipi the final implementation is not as described in the lead comment for the PR. See https://community.plot.ly/t/dash-v1-7-0-released/31961 for an example.

HammadTheOne pushed a commit to HammadTheOne/dash that referenced this pull request May 28, 2021
…ted-git-info-2.8.9
Bump hosted-git-info from 2.7.1 to 2.8.9
HammadTheOne pushed a commit that referenced this pull request Jul 23, 2021
…t-info-2.8.9
Bump hosted-git-info from 2.7.1 to 2.8.9
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@jjaraalm@alexcjohnson@Marc-Andre-Rivet@ieipi
, '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

Inline clientside callbacks - #967

Merged
Marc-Andre-Rivet merged 12 commits into
plotly:devfrom
jjaraalm:inline_clientside
Nov 15, 2019
Merged

Inline clientside callbacks#967
Marc-Andre-Rivet merged 12 commits into
plotly:devfrom
jjaraalm:inline_clientside

Conversation

@jjaraalm

@jjaraalmjjaraalm commented Oct 16, 2019

Copy link
Copy Markdown
Contributor

Addresses at least part of #956 and adds support for writing JS clientside callbacks in python source.

Adds explicit JS source injection through a new keyword argument to Dash.clientside_callback:

clientside_callback(
ClientsideFunction('namespace_to_inject_to', 'function_to_inject'),
Output('output', 'value'),
[Input('input', 'value')],
source=""" function(value) { console.log(value); return parseInt(value, 10) + 1; } """
)

Also adds source injection via the Dash.callback decorator. This probably should be explained or documented better so that users don't think they can write arbitrary python code here. Eventually I would like to add support for transpiling python -> JS, but this is just a stepping stone.

@callback(Output('output', 'value'), [Input('input', 'value')],clientside=True)defnative_js_callback(value): # Interior source is JS that will be added to the # dash_clientide._inline_callbacks namespace.# Single-line comments are properly escaped.console.log(value);
returnparseInt(value, 10) +1;

Contributor Checklist

  • I have broken down my PR scope into the following TODO tasks
    • Add inline clientside callbacks to Dash.clientside_callback
    • Add decorated clientside callbacks to Dash.callback
  • I have run the tests locally and they passed. (refer to testing section in contributing)
  • I have added tests, or extended existing tests, to cover any new features or bugs fixed in this PR

optionals

  • I have added entry in the CHANGELOG.md
  • If this PR needs a follow-up in dash docs, community thread, I have mentioned the relevant URLS as follow
    • this github #PR number updates the dash docs
    • here is the show and tell thread in plotly dash community

@alexcjohnson

Copy link
Copy Markdown
Collaborator

@jjaraalm you're on a roll 🏎

I very much like your implementation with inline_scripts, that's simpler than what I was envisioning 🎉 - that said if it's going to be in config - and thereby intentionally exposed to users - we would need to (a) include it in the Dash constructor args, and (b) document it fully. For now, unless there are clear use cases for users to add other things to it, let's keep it private and out of config, just set it as something like self._inline_scripts.

I'd like to see if we can get rid of the source kwarg, and just replace the ClientsideFunction first arg with the function source string, as proposed in #956. This would require choosing an implicit namespace and function name. Is there a reason to want explicit names? I suppose in principle that could allow multiple callbacks to use the same function, but that feels to me like an unnecessary optimization that will lead to more confusing code, especially since such sharing would only generally be useful for very short functions. I'd propose a namespace like _dashprivate, and the name could be just the (first) output id and prop, joined with maybe a double underscore, ie output__value?

I'm not a fan of writing JS code directly in a Python function body, and I don't see how it can be a stepping stone without later becoming a breaking change. If it's alright with you, let's leave this flavor out unless and until we're ready to do the transpiled version.

@jjaraalm

Copy link
Copy Markdown
ContributorAuthor

Thanks for the feedback. I wasn't thinking so much about the implications of putting it in config, I just liked the symmetry with external_scripts. I could see why some people might use it in the constructor (to avoid writing simple JS assets), but I agree it probably shouldn't be there, I'll fix that.

I agree with your assessment of the utility of namespace/function name for inline scripts. Really I only see that being used for intricate, overly complex, and probably not well-documented callback ... which I'm guilty of sometimes haha.

I think JS in python feels dirty, but that said I dislike code-in-strings even more. It's more clear that it's a foreign language, but also there's no easy way to get syntax highlighting or linting in editors (that I know of). I've always found code-in-strings to be a pain to write and debug. I understand your points though, and we can leave it out.

@jjaraalm

Copy link
Copy Markdown
ContributorAuthor

I decided to create a new namespace prefixed by _dashprivate_ for each output id. This is to prevent the highly unlikely case where someone creates a custom component with horrible prop names then decides to use even worse id names which result in a conflict.

Comment threaddash/dash.py
argument that describes which JavaScript function to call
(Dash will look for the JavaScript function at
`window[namespace][function_name]`).
`window.dash_clientside[namespace][function_name]`), or it may take

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

good catch! 🔬

Comment threaddash/dash.py Outdated
@alexcjohnson

Copy link
Copy Markdown
Collaborator

Alright, Looks good to me! Just needs a changelog (and updating so it doesn't conflict with your other PR 😏) and we'll be ready to go.

@Marc-Andre-Rivet

Copy link
Copy Markdown
Contributor

@alexcjohnson@jjaraalm Looking into the DashR parity implementation and came to the conclusion that we wanted to prepend the namespace with _dashprivate_ -- glad to see you've already thought of everything! This looks great. As previously stated, just needs a changelog and an update 🎉

@jjaraalm

Copy link
Copy Markdown
ContributorAuthor

Sorry guys, was traveling. This should take care of it I think.

@alexcjohnsonalexcjohnson left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Love it, thanks for the finishing touches. 💃
I'm going to hold off merging for a little bit though in case we want to make a patch release on Monday's 1.6.0

@Marc-Andre-RivetMarc-Andre-Rivet 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.

💃 @jjaraalm Took the liberty of merging in dev changes. Will merge it in once the build passes one last time. Again thanks for the contribution! The next planned release is 1.7.0 around 11/25.

@Marc-Andre-Rivet
Marc-Andre-Rivet merged commit 5d9f578 into plotly:devNov 15, 2019
@ieipi

ieipi commented Dec 18, 2019

Copy link
Copy Markdown

I've updated to dash=1.7 and want to use the new inline callbacks feature, but got the following error:

TypeError: clientside_callback() got an unexpected keyword argument 'source'
(base)

and when i want to use the native_js_callback syntax, it gave the error:

callback() got an unexpected keyword argument 'clientside'

@alexcjohnson

Copy link
Copy Markdown
Collaborator

@ieipi the final implementation is not as described in the lead comment for the PR. See https://community.plot.ly/t/dash-v1-7-0-released/31961 for an example.

HammadTheOne pushed a commit to HammadTheOne/dash that referenced this pull request May 28, 2021
…ted-git-info-2.8.9
Bump hosted-git-info from 2.7.1 to 2.8.9
HammadTheOne pushed a commit that referenced this pull request Jul 23, 2021
…t-info-2.8.9
Bump hosted-git-info from 2.7.1 to 2.8.9
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@jjaraalm@alexcjohnson@Marc-Andre-Rivet@ieipi
, '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

Inline clientside callbacks - #967

Merged
Marc-Andre-Rivet merged 12 commits into
plotly:devfrom
jjaraalm:inline_clientside
Nov 15, 2019
Merged

Inline clientside callbacks#967
Marc-Andre-Rivet merged 12 commits into
plotly:devfrom
jjaraalm:inline_clientside

Conversation

@jjaraalm

@jjaraalmjjaraalm commented Oct 16, 2019

Copy link
Copy Markdown
Contributor

Addresses at least part of #956 and adds support for writing JS clientside callbacks in python source.

Adds explicit JS source injection through a new keyword argument to Dash.clientside_callback:

clientside_callback(
ClientsideFunction('namespace_to_inject_to', 'function_to_inject'),
Output('output', 'value'),
[Input('input', 'value')],
source=""" function(value) { console.log(value); return parseInt(value, 10) + 1; } """
)

Also adds source injection via the Dash.callback decorator. This probably should be explained or documented better so that users don't think they can write arbitrary python code here. Eventually I would like to add support for transpiling python -> JS, but this is just a stepping stone.

@callback(Output('output', 'value'), [Input('input', 'value')],clientside=True)defnative_js_callback(value): # Interior source is JS that will be added to the # dash_clientide._inline_callbacks namespace.# Single-line comments are properly escaped.console.log(value);
returnparseInt(value, 10) +1;

Contributor Checklist

  • I have broken down my PR scope into the following TODO tasks
    • Add inline clientside callbacks to Dash.clientside_callback
    • Add decorated clientside callbacks to Dash.callback
  • I have run the tests locally and they passed. (refer to testing section in contributing)
  • I have added tests, or extended existing tests, to cover any new features or bugs fixed in this PR

optionals

  • I have added entry in the CHANGELOG.md
  • If this PR needs a follow-up in dash docs, community thread, I have mentioned the relevant URLS as follow
    • this github #PR number updates the dash docs
    • here is the show and tell thread in plotly dash community

@alexcjohnson

Copy link
Copy Markdown
Collaborator

@jjaraalm you're on a roll 🏎

I very much like your implementation with inline_scripts, that's simpler than what I was envisioning 🎉 - that said if it's going to be in config - and thereby intentionally exposed to users - we would need to (a) include it in the Dash constructor args, and (b) document it fully. For now, unless there are clear use cases for users to add other things to it, let's keep it private and out of config, just set it as something like self._inline_scripts.

I'd like to see if we can get rid of the source kwarg, and just replace the ClientsideFunction first arg with the function source string, as proposed in #956. This would require choosing an implicit namespace and function name. Is there a reason to want explicit names? I suppose in principle that could allow multiple callbacks to use the same function, but that feels to me like an unnecessary optimization that will lead to more confusing code, especially since such sharing would only generally be useful for very short functions. I'd propose a namespace like _dashprivate, and the name could be just the (first) output id and prop, joined with maybe a double underscore, ie output__value?

I'm not a fan of writing JS code directly in a Python function body, and I don't see how it can be a stepping stone without later becoming a breaking change. If it's alright with you, let's leave this flavor out unless and until we're ready to do the transpiled version.

@jjaraalm

Copy link
Copy Markdown
ContributorAuthor

Thanks for the feedback. I wasn't thinking so much about the implications of putting it in config, I just liked the symmetry with external_scripts. I could see why some people might use it in the constructor (to avoid writing simple JS assets), but I agree it probably shouldn't be there, I'll fix that.

I agree with your assessment of the utility of namespace/function name for inline scripts. Really I only see that being used for intricate, overly complex, and probably not well-documented callback ... which I'm guilty of sometimes haha.

I think JS in python feels dirty, but that said I dislike code-in-strings even more. It's more clear that it's a foreign language, but also there's no easy way to get syntax highlighting or linting in editors (that I know of). I've always found code-in-strings to be a pain to write and debug. I understand your points though, and we can leave it out.

@jjaraalm

Copy link
Copy Markdown
ContributorAuthor

I decided to create a new namespace prefixed by _dashprivate_ for each output id. This is to prevent the highly unlikely case where someone creates a custom component with horrible prop names then decides to use even worse id names which result in a conflict.

Comment threaddash/dash.py
argument that describes which JavaScript function to call
(Dash will look for the JavaScript function at
`window[namespace][function_name]`).
`window.dash_clientside[namespace][function_name]`), or it may take

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

good catch! 🔬

Comment threaddash/dash.py Outdated
@alexcjohnson

Copy link
Copy Markdown
Collaborator

Alright, Looks good to me! Just needs a changelog (and updating so it doesn't conflict with your other PR 😏) and we'll be ready to go.

@Marc-Andre-Rivet

Copy link
Copy Markdown
Contributor

@alexcjohnson@jjaraalm Looking into the DashR parity implementation and came to the conclusion that we wanted to prepend the namespace with _dashprivate_ -- glad to see you've already thought of everything! This looks great. As previously stated, just needs a changelog and an update 🎉

@jjaraalm

Copy link
Copy Markdown
ContributorAuthor

Sorry guys, was traveling. This should take care of it I think.

@alexcjohnsonalexcjohnson left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Love it, thanks for the finishing touches. 💃
I'm going to hold off merging for a little bit though in case we want to make a patch release on Monday's 1.6.0

@Marc-Andre-RivetMarc-Andre-Rivet 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.

💃 @jjaraalm Took the liberty of merging in dev changes. Will merge it in once the build passes one last time. Again thanks for the contribution! The next planned release is 1.7.0 around 11/25.

@Marc-Andre-Rivet
Marc-Andre-Rivet merged commit 5d9f578 into plotly:devNov 15, 2019
@ieipi

ieipi commented Dec 18, 2019

Copy link
Copy Markdown

I've updated to dash=1.7 and want to use the new inline callbacks feature, but got the following error:

TypeError: clientside_callback() got an unexpected keyword argument 'source'
(base)

and when i want to use the native_js_callback syntax, it gave the error:

callback() got an unexpected keyword argument 'clientside'

@alexcjohnson

Copy link
Copy Markdown
Collaborator

@ieipi the final implementation is not as described in the lead comment for the PR. See https://community.plot.ly/t/dash-v1-7-0-released/31961 for an example.

HammadTheOne pushed a commit to HammadTheOne/dash that referenced this pull request May 28, 2021
…ted-git-info-2.8.9
Bump hosted-git-info from 2.7.1 to 2.8.9
HammadTheOne pushed a commit that referenced this pull request Jul 23, 2021
…t-info-2.8.9
Bump hosted-git-info from 2.7.1 to 2.8.9
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@jjaraalm@alexcjohnson@Marc-Andre-Rivet@ieipi
, '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

Inline clientside callbacks - #967

Merged
Marc-Andre-Rivet merged 12 commits into
plotly:devfrom
jjaraalm:inline_clientside
Nov 15, 2019
Merged

Inline clientside callbacks#967
Marc-Andre-Rivet merged 12 commits into
plotly:devfrom
jjaraalm:inline_clientside

Conversation

@jjaraalm

@jjaraalmjjaraalm commented Oct 16, 2019

Copy link
Copy Markdown
Contributor

Addresses at least part of #956 and adds support for writing JS clientside callbacks in python source.

Adds explicit JS source injection through a new keyword argument to Dash.clientside_callback:

clientside_callback(
ClientsideFunction('namespace_to_inject_to', 'function_to_inject'),
Output('output', 'value'),
[Input('input', 'value')],
source=""" function(value) { console.log(value); return parseInt(value, 10) + 1; } """
)

Also adds source injection via the Dash.callback decorator. This probably should be explained or documented better so that users don't think they can write arbitrary python code here. Eventually I would like to add support for transpiling python -> JS, but this is just a stepping stone.

@callback(Output('output', 'value'), [Input('input', 'value')],clientside=True)defnative_js_callback(value): # Interior source is JS that will be added to the # dash_clientide._inline_callbacks namespace.# Single-line comments are properly escaped.console.log(value);
returnparseInt(value, 10) +1;

Contributor Checklist

  • I have broken down my PR scope into the following TODO tasks
    • Add inline clientside callbacks to Dash.clientside_callback
    • Add decorated clientside callbacks to Dash.callback
  • I have run the tests locally and they passed. (refer to testing section in contributing)
  • I have added tests, or extended existing tests, to cover any new features or bugs fixed in this PR

optionals

  • I have added entry in the CHANGELOG.md
  • If this PR needs a follow-up in dash docs, community thread, I have mentioned the relevant URLS as follow
    • this github #PR number updates the dash docs
    • here is the show and tell thread in plotly dash community

@alexcjohnson

Copy link
Copy Markdown
Collaborator

@jjaraalm you're on a roll 🏎

I very much like your implementation with inline_scripts, that's simpler than what I was envisioning 🎉 - that said if it's going to be in config - and thereby intentionally exposed to users - we would need to (a) include it in the Dash constructor args, and (b) document it fully. For now, unless there are clear use cases for users to add other things to it, let's keep it private and out of config, just set it as something like self._inline_scripts.

I'd like to see if we can get rid of the source kwarg, and just replace the ClientsideFunction first arg with the function source string, as proposed in #956. This would require choosing an implicit namespace and function name. Is there a reason to want explicit names? I suppose in principle that could allow multiple callbacks to use the same function, but that feels to me like an unnecessary optimization that will lead to more confusing code, especially since such sharing would only generally be useful for very short functions. I'd propose a namespace like _dashprivate, and the name could be just the (first) output id and prop, joined with maybe a double underscore, ie output__value?

I'm not a fan of writing JS code directly in a Python function body, and I don't see how it can be a stepping stone without later becoming a breaking change. If it's alright with you, let's leave this flavor out unless and until we're ready to do the transpiled version.

@jjaraalm

Copy link
Copy Markdown
ContributorAuthor

Thanks for the feedback. I wasn't thinking so much about the implications of putting it in config, I just liked the symmetry with external_scripts. I could see why some people might use it in the constructor (to avoid writing simple JS assets), but I agree it probably shouldn't be there, I'll fix that.

I agree with your assessment of the utility of namespace/function name for inline scripts. Really I only see that being used for intricate, overly complex, and probably not well-documented callback ... which I'm guilty of sometimes haha.

I think JS in python feels dirty, but that said I dislike code-in-strings even more. It's more clear that it's a foreign language, but also there's no easy way to get syntax highlighting or linting in editors (that I know of). I've always found code-in-strings to be a pain to write and debug. I understand your points though, and we can leave it out.

@jjaraalm

Copy link
Copy Markdown
ContributorAuthor

I decided to create a new namespace prefixed by _dashprivate_ for each output id. This is to prevent the highly unlikely case where someone creates a custom component with horrible prop names then decides to use even worse id names which result in a conflict.

Comment threaddash/dash.py
argument that describes which JavaScript function to call
(Dash will look for the JavaScript function at
`window[namespace][function_name]`).
`window.dash_clientside[namespace][function_name]`), or it may take

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

good catch! 🔬

Comment threaddash/dash.py Outdated
@alexcjohnson

Copy link
Copy Markdown
Collaborator

Alright, Looks good to me! Just needs a changelog (and updating so it doesn't conflict with your other PR 😏) and we'll be ready to go.

@Marc-Andre-Rivet

Copy link
Copy Markdown
Contributor

@alexcjohnson@jjaraalm Looking into the DashR parity implementation and came to the conclusion that we wanted to prepend the namespace with _dashprivate_ -- glad to see you've already thought of everything! This looks great. As previously stated, just needs a changelog and an update 🎉

@jjaraalm

Copy link
Copy Markdown
ContributorAuthor

Sorry guys, was traveling. This should take care of it I think.

@alexcjohnsonalexcjohnson left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Love it, thanks for the finishing touches. 💃
I'm going to hold off merging for a little bit though in case we want to make a patch release on Monday's 1.6.0

@Marc-Andre-RivetMarc-Andre-Rivet 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.

💃 @jjaraalm Took the liberty of merging in dev changes. Will merge it in once the build passes one last time. Again thanks for the contribution! The next planned release is 1.7.0 around 11/25.

@Marc-Andre-Rivet
Marc-Andre-Rivet merged commit 5d9f578 into plotly:devNov 15, 2019
@ieipi

ieipi commented Dec 18, 2019

Copy link
Copy Markdown

I've updated to dash=1.7 and want to use the new inline callbacks feature, but got the following error:

TypeError: clientside_callback() got an unexpected keyword argument 'source'
(base)

and when i want to use the native_js_callback syntax, it gave the error:

callback() got an unexpected keyword argument 'clientside'

@alexcjohnson

Copy link
Copy Markdown
Collaborator

@ieipi the final implementation is not as described in the lead comment for the PR. See https://community.plot.ly/t/dash-v1-7-0-released/31961 for an example.

HammadTheOne pushed a commit to HammadTheOne/dash that referenced this pull request May 28, 2021
…ted-git-info-2.8.9
Bump hosted-git-info from 2.7.1 to 2.8.9
HammadTheOne pushed a commit that referenced this pull request Jul 23, 2021
…t-info-2.8.9
Bump hosted-git-info from 2.7.1 to 2.8.9
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@jjaraalm@alexcjohnson@Marc-Andre-Rivet@ieipi