Feature Request
Edit: For anyone finding this issue checkout - dioxus-use-js
We should improve the experience for executing js code. The current way is not very ergonomic, especially with all the escaping. e.g.
pub async fn get_visible_html_content(root_id: Option<&str>) -> Result<Vec<String>> {
let js = format!( r#"
function getVisibleText(rootElementId = null) {{
let rootElement = null;
if (rootElementId) {{
// If specific ID provided, use that element
rootElement = document.getElementById(rootElementId);
}} else if (document.body) {{
// Use body if available
rootElement = document.body;
}} else {{
// Fallback: find first non-head element
const allRootElements = document.querySelectorAll(':root > *');
for (const el of allRootElements) {{
if (el.tagName.toLowerCase() !== 'head') {{
rootElement = el;
break;
}}
}}
}}
if (!rootElement) {{
throw new Error('No suitable root element found');
}}
// Query elements within the specified root
const elements = rootElement.querySelectorAll('*');
const visibleElements = Array.from(elements).filter(el => {{
// Skip script and style elements entirely
if (el.tagName.toLowerCase() === 'script' ||
el.tagName.toLowerCase() === 'style' ||
el.tagName.toLowerCase() === 'noscript') {{
return false;
}}
// Skip elements with display:none or visibility:hidden
const style = window.getComputedStyle(el);
if (style.display === 'none' || style.visibility === 'hidden') {{
return false;
}}
// Check if element is in viewport
const rect = el.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= window.innerHeight &&
rect.right <= window.innerWidth
);
}});
// Extract only text content from visible elements
const visibleText = visibleElements.map(el => {{
// Get direct text content of this element (excluding child elements)
let textContent = '';
for (let node of el.childNodes) {{
if (node.nodeType === Node.TEXT_NODE) {{
const trimmedText = node.textContent.trim();
if (trimmedText) {{
textContent += trimmedText + ' ';
}}
}}
}}
return textContent.trim();
}}).filter(text => text !== '');
return visibleText;
}}
const visibleText = getVisibleText({});
return visibleText;
"#,
root_id.map(|e| format!("\"{e}\"")).unwrap_or(String::new())
);
let eval = document::eval(js.as_str());
serde_json::from_value(eval.await?)
}
A better way would be to use macros with the existing manganis system:
pub async fn get_visible_html_content(root_id: Option<&str>) -> Result<Vec<String>> {
document::module_eval!(
"path/to/asset.js", // asset that is loaded as a module
"getVisibleText", // function to invoke inside the module
root_id // arguments
).await?
}
Expands to something like:
pub async fn get_visible_html_content(root_id: Option<&str>) -> Result<Vec<String>> {
{
const MODULE: Asset = asset!("path/to/asset.js");
let js = const_format::formatcp!(r#"
import getVisibleText from "{MODULE}";
let arg1 = await dioxus.recv();
return getVisibleText(arg1);
"#);
let eval = document::eval(js);
eval.send(root_id)?;
serde_json::from_value(eval.await?)
}.await?
}
path/to/asset.js
function getVisibleText(rootElementId = null) {
let rootElement = null;
if (rootElementId) {
// If specific ID provided, use that element
rootElement = document.getElementById(rootElementId);
} else if (document.body) {
// Use body if available
rootElement = document.body;
} else {
// Fallback: find first non-head element
const allRootElements = document.querySelectorAll(':root > *');
for (const el of allRootElements) {
if (el.tagName.toLowerCase() !== 'head') {
rootElement = el;
break;
}
}
}
if (!rootElement) {
throw new Error('No suitable root element found');
}
// Query elements within the specified root
const elements = rootElement.querySelectorAll('*');
const visibleElements = Array.from(elements).filter(el => {
// Skip script and style elements entirely
if (el.tagName.toLowerCase() === 'script' ||
el.tagName.toLowerCase() === 'style' ||
el.tagName.toLowerCase() === 'noscript') {
return false;
}
// Skip elements with display:none or visibility:hidden
const style = window.getComputedStyle(el);
if (style.display === 'none' || style.visibility === 'hidden') {
return false;
}
// Check if element is in viewport
const rect = el.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= window.innerHeight &&
rect.right <= window.innerWidth
);
});
// Extract only text content from visible elements
const visibleText = visibleElements.map(el => {
// Get direct text content of this element (excluding child elements)
let textContent = '';
for (let node of el.childNodes) {
if (node.nodeType === Node.TEXT_NODE) {
const trimmedText = node.textContent.trim();
if (trimmedText) {
textContent += trimmedText + ' ';
}
}
}
return textContent.trim();
}).filter(text => text !== '');
return visibleText;
}
This new proposed alternative allows loading the js code once, no escaping, easy integration of modules in js files, auto generate send/recv, compile time validation (js is valid, function exists, and number of args are correct), and ide/syntax highlighting support since js files are used.
A future version could even support callbacks. e.g.
document::module_eval!(
"path/to/asset.js",
"functionName",
arg1,
callback!(arg2),
callback!(arg3)
).await?
For args with callback!, the necessary wrapping code would be generated and use dioxus.send, dioxus.recv, eval.send, eval.recv for communication.
I am interested in implementing this if accepted.
Feature Request
Edit: For anyone finding this issue checkout - dioxus-use-js
We should improve the experience for executing js code. The current way is not very ergonomic, especially with all the escaping. e.g.
A better way would be to use macros with the existing manganis system:
Expands to something like:
path/to/asset.jsThis new proposed alternative allows loading the js code once, no escaping, easy integration of modules in js files, auto generate
send/recv, compile time validation (js is valid, function exists, and number of args are correct), and ide/syntax highlighting support since js files are used.A future version could even support callbacks. e.g.
For args with
callback!, the necessary wrapping code would be generated and usedioxus.send,dioxus.recv,eval.send,eval.recvfor communication.I am interested in implementing this if accepted.