Skip to content

Instantly share code, notes, and snippets.

@thomasballinger
Created February 1, 2020 08:15
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save thomasballinger/2c9718defa7c89daae163b6470a81f8b to your computer and use it in GitHub Desktop.
Save thomasballinger/2c9718defa7c89daae163b6470a81f8b to your computer and use it in GitHub Desktop.
sketch of noticing that a cell has been removed or reexecuted. Useful for stopping timers, removing event listeners, etc.
Display the source blob
Display the rendered blob
Raw
{
"cells": [
{
"cell_type": "code",
"execution_count": 1,
"metadata": {},
"outputs": [
{
"data": {
"application/javascript": [
"\n",
"// Find deepest DOM ancestor that does not change on reexecute\n",
"const findParentOutputDiv = (el) => {\n",
" let candidate = el;\n",
" while (candidate) {\n",
" candidate = candidate.parentElement\n",
" if (candidate.className === 'output') {\n",
" return candidate;\n",
" }\n",
" }\n",
" throw Error(\"parent output div not found\");\n",
"}\n",
"\n",
"// Find shallowest DOM ancestor that is removed on delete\n",
"const findParentCell = (el) => {\n",
" let candidate = el;\n",
" while (candidate) {\n",
" candidate = candidate.parentElement\n",
" if (candidate.classList.contains('cell')) {\n",
" return candidate;\n",
" }\n",
" }\n",
" throw Error(\"cell not found not found\");\n",
" }\n",
"\n",
"const onCellReexecuteOrDelete = (element, cb) => {\n",
" \n",
" const outputDiv = findParentOutputDiv(element);\n",
" const cellDiv = findParentCell(element);\n",
" const notebookContainer = cellDiv.parentElement;\n",
" let onEither = () => { throw Error('callback not initialized yet'); }\n",
"\n",
" const reexecuteDetector = function(mutationsList, observer) {\n",
" for(let mutation of mutationsList) {\n",
" if (mutation.type === 'childList') {\n",
" onEither('cell reexecute');\n",
" return;\n",
" }\n",
" }\n",
" };\n",
" \n",
" const cellDeleteDetector = function(mutationsList, observer) {\n",
" for (let mutation of mutationsList) {\n",
" if (mutation.type === 'childList') {\n",
" for (let node of mutation.removedNodes) {\n",
" if (node === cellDiv) {\n",
" onEither('cell delete')\n",
" return;\n",
" }\n",
" }\n",
"\n",
" }\n",
" }\n",
" };\n",
"\n",
" const outputMutationObserver = new MutationObserver(reexecuteDetector);\n",
" const notebookMutationObserver = new MutationObserver(cellDeleteDetector);\n",
" \n",
" onEither = (reason) => {\n",
" outputMutationObserver.disconnect();\n",
" notebookMutationObserver.disconnect();\n",
" cb(reason);\n",
" }\n",
" \n",
" outputMutationObserver.observe(outputDiv, { childList: true });\n",
" notebookMutationObserver.observe(notebookContainer, { childList: true });\n",
"}\n",
"\n",
"window.onCellReexecuteOrDelete = onCellReexecuteOrDelete;\n"
],
"text/plain": [
"<IPython.core.display.Javascript object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"%%javascript\n",
"\n",
"// Find deepest DOM ancestor that does not change on reexecute\n",
"const findParentOutputDiv = (el) => {\n",
" let candidate = el;\n",
" while (candidate) {\n",
" candidate = candidate.parentElement\n",
" if (candidate.className === 'output') {\n",
" return candidate;\n",
" }\n",
" }\n",
" throw Error(\"parent output div not found\");\n",
"}\n",
"\n",
"// Find shallowest DOM ancestor that is removed on delete\n",
"const findParentCell = (el) => {\n",
" let candidate = el;\n",
" while (candidate) {\n",
" candidate = candidate.parentElement\n",
" if (candidate.classList.contains('cell')) {\n",
" return candidate;\n",
" }\n",
" }\n",
" throw Error(\"cell not found not found\");\n",
" }\n",
"\n",
"const onCellReexecuteOrDelete = (element, cb) => {\n",
" \n",
" const outputDiv = findParentOutputDiv(element);\n",
" const cellDiv = findParentCell(element);\n",
" const notebookContainer = cellDiv.parentElement;\n",
" let onEither = () => { throw Error('callback not initialized yet'); }\n",
"\n",
" const reexecuteDetector = function(mutationsList, observer) {\n",
" for(let mutation of mutationsList) {\n",
" if (mutation.type === 'childList') {\n",
" onEither('cell reexecute');\n",
" return;\n",
" }\n",
" }\n",
" };\n",
" \n",
" const cellDeleteDetector = function(mutationsList, observer) {\n",
" for (let mutation of mutationsList) {\n",
" if (mutation.type === 'childList') {\n",
" for (let node of mutation.removedNodes) {\n",
" if (node === cellDiv) {\n",
" onEither('cell delete')\n",
" return;\n",
" }\n",
" }\n",
"\n",
" }\n",
" }\n",
" };\n",
"\n",
" const outputMutationObserver = new MutationObserver(reexecuteDetector);\n",
" const notebookMutationObserver = new MutationObserver(cellDeleteDetector);\n",
" \n",
" onEither = (reason) => {\n",
" outputMutationObserver.disconnect();\n",
" notebookMutationObserver.disconnect();\n",
" cb(reason);\n",
" }\n",
" \n",
" outputMutationObserver.observe(outputDiv, { childList: true });\n",
" notebookMutationObserver.observe(notebookContainer, { childList: true });\n",
"}\n",
"\n",
"window.onCellReexecuteOrDelete = onCellReexecuteOrDelete;"
]
},
{
"cell_type": "code",
"execution_count": 6,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div id=\"wheeeeee\">\n",
"hello\n",
"</div>\n",
"\n",
"<script>\n",
"onCellReexecuteOrDelete(document.getElementById('wheeeeee'), (reason) => console.log(\"cleanup because\", reason))\n",
"</script>\n"
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"%%html\n",
"<div id=\"wheeeeee\">\n",
"hello\n",
"</div>\n",
"\n",
"<script>\n",
"onCellReexecuteOrDelete(document.getElementById('wheeeeee'), (reason) => console.log(\"cleanup because\", reason))\n",
"</script>"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {},
"outputs": [
{
"data": {
"text/html": [
"<div id=\"another-one\">\n",
"hello\n",
"</div>\n",
"\n",
"<script>\n",
"onCellReexecuteOrDelete(document.getElementById('another-one'), (reason) => console.log(\"cleanup because\", reason))\n",
"</script>\n"
],
"text/plain": [
"<IPython.core.display.HTML object>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"%%html\n",
"<div id=\"another-one\">\n",
"hello\n",
"</div>\n",
"\n",
"<script>\n",
"onCellReexecuteOrDelete(document.getElementById('another-one'), (reason) => console.log(\"cleanup because\", reason))\n",
"</script>"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.7.3"
}
},
"nbformat": 4,
"nbformat_minor": 2
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment