JS Injection Hack Usefuls (JavaScript)

I thought I’d create a wiki topic to post useful JS “hacks”. It’d be good to include methods using the Markdown component as well as Ben Musson’s Periscope module’s system.perspective.runJavaScript* methods, but either or is ok initially and others can update your wiki reply later.

What I had in mind was to have posts include the JSON for the Markdown component that includes the JS script within it, so that all you need to do is copy the JSON and paste it into your View.

E.g.

Example Markdown JSON
[
  {
    "type": "ia.display.markdown",
    "version": 0,
    "props": {
      "style": {
        "position": "absolute"
      },
      "markdown": {
        "escapeHtml": false
      }
    },
    "meta": {
      "name": "JSInject_PowerChart_XTraceTagColours"
    },
    "position": {
      "shrink": 0,
      "basis": 0
    },
    "custom": {
      "inlineJavascript": "(function() {\n            function getPenColours() {\n                const map = {};\n                document.querySelectorAll('svg.pen-visibility-checkbox[data-pen-name]').forEach(svg => {\n                    const name = svg.getAttribute('data-pen-name');\n                    const fill = svg.style.fill;\n                    if (name && fill) map[name] = fill;\n                });\n                return map;\n            }\n\n            function colourXTraceLabels() {\n                const penColours = getPenColours();\n                document.querySelectorAll('.ia_powerChartComponent__xTrace__box__label').forEach(textEl => {\n                    const boldSpan = textEl.querySelector('tspan[style*="font-weight"]');\n                    if (!boldSpan) return;\n                    const penName = boldSpan.textContent.replace(/:\\s*$/, '').trim();\n                    if (penColours[penName]) {\n                        textEl.style.fill = penColours[penName];\n                    }\n                });\n            }\n\n            if (window._xTraceObserver) window._xTraceObserver.disconnect();\n\n            window._xTraceObserver = new MutationObserver((mutations) => {\n                for (const mutation of mutations) {\n                    const hasXTrace = [...mutation.addedNodes].some(node =>\n                        node.nodeType === 1 && (\n                            node.classList?.contains('ia_timeMarker') ||\n                            node.querySelector?.('.ia_timeMarker')\n                        )\n                    );\n                    const isInsideXTrace = mutation.target.closest?.(\n                        '.ia_timeMarker, .ia_powerChartComponent__xTrace__box'\n                    );\n                    if (hasXTrace || isInsideXTrace) {\n                        colourXTraceLabels();\n                        break;\n                    }\n                }\n            });\n\n            window._xTraceObserver.observe(document.body, {\n                childList: true,\n                subtree: true,\n                characterData: true,\n            });\n\n            colourXTraceLabels();\n        })();"
    },
    "propConfig": {
      "props.source": {
        "binding": {
          "config": {
            "struct": {
              "script": "{this.custom.inlineJavascript}"
            },
            "waitOnAll": true
          },
          "transforms": [
            {
              "code": "#\tcode =  \"<img style='display:none' src='/favicon.ico' onload=\\\"\" + value.script.replace('\"', '&quot;') + '\\\"></img>'\n#\t\n#\tcode = code.replace(\"\\n\", \"\").replace(\"\\t\", \"\").replace(\"\\n\", \"\").replace(\"\\r\", \"\").replace(\"\\r\", \"\").replace(\"  \", \" \").replace(\"  \", \" \").replace(\"  \", \" \").replace(\"  \", \" \")\n#\t\n#\treturn code\n\tscript = value.script\n\tscript = script.replace('\"', '&quot;')\n\tscript = script.replace('\\n', '').replace('\\t', '').replace('\\r', '')\n\t\n\t# Collapse multiple spaces - repeat until stable\n\twhile '  ' in script:\n\t    script = script.replace('  ', ' ')\n\t\n\tcode = '<img style=\"display:none\" src=\"/favicon.ico\" onload=\"{}\"></img>'.format(script)\n\treturn code",
              "type": "script"
            }
          ],
          "type": "expr-struct"
        }
      }
    }
  }
]

This is the template that I use myself for Markdown JS hacking, and I just edit the custom.inlineJavaScript prop value with the JS script.
You need to make sure that the JS script is valid for inserting into a dummy DOM element’s onLoad script.

Please include a screenshot and a brief description of what the code does.

Please make sure to change your post to a wiki after posting so that others can edit if needed.

Contents

Accordian

Power Chart

5 Likes

This is a template you can copy for new posts. Feel free to improve it…

Feature Title

This is a description of the feature.

This is a screenshot of what the feature does.

Instructions

Copy and paste into a View...

Resources

Markdown Version

Periscope Version (N/A yet)

Power Chart - Set X-Trace tag labels to their pen colour

This will set the tag labels and values shown in all x-trace panels to the colour of their chart pens. The script (dodgilly?) grabs the colours from the legend key at the bottom.

I haven’t tested if this works when the legend is collapsed into the smaller version

Instructions

Copy and paste into a View with a Power Chart and your operators will no longer become frustrated trying to eyeball x-trace tags with their corresponding pens on the chart.

Resources

Markdown Version
[
  {
    "type": "ia.display.markdown",
    "version": 0,
    "props": {
      "style": {
        "position": "absolute"
      },
      "markdown": {
        "escapeHtml": false
      }
    },
    "meta": {
      "name": "JSInject_PowerChart_XTraceTagColours"
    },
    "position": {
      "shrink": 0,
      "basis": 0
    },
    "custom": {
      "inlineJavascript": "(function() {\n            function getPenColours() {\n                const map = {};\n                document.querySelectorAll('svg.pen-visibility-checkbox[data-pen-name]').forEach(svg => {\n                    const name = svg.getAttribute('data-pen-name');\n                    const fill = svg.style.fill;\n                    if (name && fill) map[name] = fill;\n                });\n                return map;\n            }\n\n            function colourXTraceLabels() {\n                const penColours = getPenColours();\n                document.querySelectorAll('.ia_powerChartComponent__xTrace__box__label').forEach(textEl => {\n                    const boldSpan = textEl.querySelector('tspan[style*=&quot;font-weight&quot;]');\n                    if (!boldSpan) return;\n                    const penName = boldSpan.textContent.replace(/:\\s*$/, '').trim();\n                    if (penColours[penName]) {\n                        textEl.style.fill = penColours[penName];\n                    }\n                });\n            }\n\n            if (window._xTraceObserver) window._xTraceObserver.disconnect();\n\n            window._xTraceObserver = new MutationObserver((mutations) => {\n                for (const mutation of mutations) {\n                    const hasXTrace = [...mutation.addedNodes].some(node =>\n                        node.nodeType === 1 && (\n                            node.classList?.contains('ia_timeMarker') ||\n                            node.querySelector?.('.ia_timeMarker')\n                        )\n                    );\n                    const isInsideXTrace = mutation.target.closest?.(\n                        '.ia_timeMarker, .ia_powerChartComponent__xTrace__box'\n                    );\n                    if (hasXTrace || isInsideXTrace) {\n                        colourXTraceLabels();\n                        break;\n                    }\n                }\n            });\n\n            window._xTraceObserver.observe(document.body, {\n                childList: true,\n                subtree: true,\n                characterData: true,\n            });\n\n            colourXTraceLabels();\n        })();"
    },
    "propConfig": {
      "props.source": {
        "binding": {
          "config": {
            "struct": {
              "script": "{this.custom.inlineJavascript}"
            },
            "waitOnAll": true
          },
          "transforms": [
            {
              "code": "#\tcode =  \"<img style='display:none' src='/favicon.ico' onload=\\\"\" + value.script.replace('\"', '&quot;') + '\\\"></img>'\n#\t\n#\tcode = code.replace(\"\\n\", \"\").replace(\"\\t\", \"\").replace(\"\\n\", \"\").replace(\"\\r\", \"\").replace(\"\\r\", \"\").replace(\"  \", \" \").replace(\"  \", \" \").replace(\"  \", \" \").replace(\"  \", \" \")\n#\t\n#\treturn code\n\tscript = value.script\n\tscript = script.replace('\"', '&quot;')\n\tscript = script.replace('\\n', '').replace('\\t', '').replace('\\r', '')\n\t\n\t# Collapse multiple spaces - repeat until stable\n\twhile '  ' in script:\n\t    script = script.replace('  ', ' ')\n\t\n\tcode = '<img style=\"display:none\" src=\"/favicon.ico\" onload=\"{}\"></img>'.format(script)\n\treturn code",
              "type": "script"
            }
          ],
          "type": "expr-struct"
        }
      }
    }
  }
]
Periscope Version (N/A yet)

:distorted_face:

Accordian - Allow smooth scrolling to items based on item index

This allows you to scroll an Accordian component to show a specific item, given an item index and the Accordian’s domId.

Instructions

  1. Copy the JSON below and paste it into your View with the Accordian.

  2. To your accordian component, add the meta.domId key and set it to something unique like “Accordian”

  3. Inside the “JSInject_Accordian_ScrollToIndex” component, set the “accordianDomId” to what you set it to. Then you can scroll to a specific item index by setting the custom.index to the index number. You can bind to this or set its value via script

Resources

Markdown Version (Includes 2x Markdown components)

This contains 2x Markdown components:

  • “JSInject_Accordian_ScrollToIndexBase” injects the JS function scrollToAccordionItem
  • “JSInject_Accordian_ScrollToIndex” calls the function with the domID of the accordian to scroll, and the index to scroll to, based on custom props on the component.
[
  {
    "type": "ia.display.markdown",
    "version": 0,
    "props": {
      "style": {
        "position": "absolute"
      },
      "markdown": {
        "escapeHtml": false
      }
    },
    "meta": {
      "name": "JSInject_Accordian_ScrollToIndexBase"
    },
    "position": {
      "shrink": 0,
      "basis": 0
    },
    "custom": {
      "inlineJavascript": "window.scrollToAccordionItem = function(id, index) {\n            const accordion = document.querySelector('#' + id + '.ia_accordionComponent');\n            if (!accordion) {\n                console.warn('Accordion component not found for id: ' + id);\n                return;\n            }\n\n            const wrapper = accordion.querySelector('.ia_accordionComponent__wrapper');\n            const header = wrapper.querySelector('.ia_accordionComponent__header[data-index=&quot;' + index + '&quot;]');\n\n            if (!header) {\n                console.warn('Accordion item with index ' + index + ' not found.');\n                return;\n            }\n\n            const accordionTop = accordion.getBoundingClientRect().top;\n            const headerTop = header.getBoundingClientRect().top;\n            const offset = headerTop - accordionTop;\n\n            accordion.scrollTo({\n                top: accordion.scrollTop + offset,\n                behavior: 'smooth'\n            });\n        };"
    },
    "propConfig": {
      "custom.inlineJavascript": {
        "access": "PRIVATE"
      },
      "props.source": {
        "binding": {
          "config": {
            "struct": {
              "script": "{this.custom.inlineJavascript}"
            },
            "waitOnAll": true
          },
          "transforms": [
            {
              "code": "#\tcode =  \"<img style='display:none' src='/favicon.ico' onload=\\\"\" + value.script.replace('\"', '&quot;') + '\\\"></img>'\n#\t\n#\tcode = code.replace(\"\\n\", \"\").replace(\"\\t\", \"\").replace(\"\\n\", \"\").replace(\"\\r\", \"\").replace(\"\\r\", \"\").replace(\"  \", \" \").replace(\"  \", \" \").replace(\"  \", \" \").replace(\"  \", \" \")\n#\t\n#\treturn code\n\tscript = value.script\n\tscript = script.replace('\"', '&quot;')\n\tscript = script.replace('\\n', '').replace('\\t', '').replace('\\r', '')\n\t\n\t# Collapse multiple spaces - repeat until stable\n\twhile '  ' in script:\n\t    script = script.replace('  ', ' ')\n\t\n\tcode = '<img style=\"display:none\" src=\"/favicon.ico\" onload=\"{}\"></img>'.format(script)\n\treturn code",
              "type": "script"
            }
          ],
          "type": "expr-struct"
        }
      }
    }
  },
  {
    "type": "ia.display.markdown",
    "version": 0,
    "props": {
      "style": {
        "position": "absolute"
      },
      "markdown": {
        "escapeHtml": false
      }
    },
    "meta": {
      "name": "JSInject_Accordian_ScrollToIndex"
    },
    "position": {
      "shrink": 0,
      "basis": 0
    },
    "custom": {
      "accordianDomId": "ME",
      "index": 3
    },
    "propConfig": {
      "custom.accordianDomId": {
        "access": "PRIVATE"
      },
      "custom.index": {
        "access": "PRIVATE"
      },
      "custom.inlineJavascript": {
        "binding": {
          "config": {
            "expression": "if({this.custom.index} != \"\"\r\n\t,\"(function() {scrollToAccordionItem('\" + {this.custom.accordianDomId} + \"',  \" + {this.custom.index} + \");})();\"\r\n \t,\"\"\r\n)"
          },
          "type": "expr"
        },
        "access": "PRIVATE"
      },
      "props.source": {
        "binding": {
          "config": {
            "struct": {
              "script": "{this.custom.inlineJavascript}"
            },
            "waitOnAll": true
          },
          "transforms": [
            {
              "code": "#\tcode =  \"<img style='display:none' src='/favicon.ico' onload=\\\"\" + value.script.replace('\"', '&quot;') + '\\\"></img>'\n#\t\n#\tcode = code.replace(\"\\n\", \"\").replace(\"\\t\", \"\").replace(\"\\n\", \"\").replace(\"\\r\", \"\").replace(\"\\r\", \"\").replace(\"  \", \" \").replace(\"  \", \" \").replace(\"  \", \" \").replace(\"  \", \" \")\n#\t\n#\treturn code\n\tscript = value.script\n\tscript = script.replace('\"', '&quot;')\n\tscript = script.replace('\\n', '').replace('\\t', '').replace('\\r', '')\n\t\n\t# Collapse multiple spaces - repeat until stable\n\twhile '  ' in script:\n\t    script = script.replace('  ', ' ')\n\t\n\tcode = '<img style=\"display:none\" src=\"/favicon.ico\" onload=\"{}\"></img>'.format(script)\n\treturn code",
              "type": "script"
            }
          ],
          "type": "expr-struct"
        }
      }
    }
  }
]
Periscope Version (N/A yet)