I want to create a DOM element from an HTML string. Inside that string, I have a button that should call a global function called downloadFile with predefined arguments.
My first try:
<button onclick="downloadFile(${message_result})">Download</button>
But this failed to work.
Whole code looks like this
function downloadFile(result){
console.log(result)
}
(() => {
const msg = {user: 'User', message: 'Message', result: {}} // any
var markup = `
<h4>${msg.user}</h4>
<p>${msg.message}</p>
<button onclick="downloadFile(message_result)">Download</button>
`
document.body.innerHTML = markup
})()
the error:
Uncaught ReferenceError: message_result is not defined
at HTMLButtonElement.onclick
any suggestions how i can pass a variable into my function
Let's first solve your problem then talk about a better approach.
If we assume msg.result is a string: You need to wrap it with quote marks.
<button onclick="downloadFile('${message_result}')">Download</button>`;
But if your msg.result is not a simple string or you want to take the right path to the solution, then we need to move next approach.
// This is your downloadFile function
const downloadFile = (data) => {
console.log(data)
}
// This function creates download buttons
const createDownloadButton = (response) => {
const markup = `
<h4>${response.user}</h4>
<p>${response.message}</p>
<button>Download</button>
`
const container = document.createElement("div")
container.innerHTML = markup;
// Here, we assign "click" functionality dynamically even before we inject the element into DOM
container.getElementsByTagName("button")[0].addEventListener('click', () => {
downloadFile(response.result)
})
document.body.append(container)
}
// Fetch your response from an external service
const msg = {
user: 'Username',
message: 'Message',
result: {
file: {
link: 'https://stackoverflow.com'
}
}
}
// Call function to create download button
createDownloadButton(msg)
If message_result is a variable, like msg.message, then you need to reference it similarly.
var markup=`
<h4>${msg.user}</h4>
<p>${msg.message}</p>
<button onclick="downloadFile('${message_result}')">Download</button>`;
Related
I have a problem with a download button. If I reload the page, the onClick={() => ExportAll(data)} works fine the first time when pressing the download button, however when I try it after that the console throws this error:
The function itself looks like this:
const exportAll = (data) => {
const arr = []
data.map((element) => {
element.orderlines = element?.orderlines?.join(' , ')
arr.push(element)
}
);
exportFileAsXLSX(arr, `Meest recente orders-${props.timespan}`);
};
The data object is set using the useState hook
If anyone has any idea why this doesn't work, please let me know!
const exportAll = (data) => {
const arr = []
data.map((element) => {
let newElement = { ...element}
newElement.orderlines = element?.orderlines?.join(' , ')
arr.push(newElement)
}
);
exportFileAsXLSX(arr, `Meest recente orders-${props.timespan}`);
};
At first execution you overwrite original objects orderlines field value. Then at the next time you try to apply join operation to that field value again. Now it is a string not an array. That is the reason for the error.Change the code as above it will work.Above code I create a new obj rather than overwriting it.
Is orderlines an array? Cause you can only use join() if it is indeed an array!
Double check if it's not an object.
In my react electron app, that it is working with an API, I receive JSON values to display data into the components. So for example I have a Features component:
const Features = () => {
const { title } = useSelector(({ titles }) => titles);
let string = title.features;
// the string can contain some html tags. Example bellow:
// sting = 'This is a string containing a href to Google';
string = string.replace(/href="(.*?)"/g, function() {
return `onClick="${() => shell.openExternal('www.google.com')}"`;
});
return (
<>
<Heading>Features</Heading>
<Text content={parsedHTML} />
</>
);
};
What I want is to replace the href attribute with onClick and assign Electron's shell.openExternal() function.
The string.replace() callback function does that, but when I click on the <a> element, the app throws next error:
error: uncaughtException: Expected onClick listener to be a
function, instead got a value of string type.
UPDATE
Also tried this logic and the same error occurs:
global.openInBrowser = openInBrowser; // openInBrowser is basically a function that calls shell.openExternal(url)
const re = new RegExp('<a([^>]* )href="([^"]+)"', 'g');
string = string.replace(re, '<a$1href="#" onClick="openInBrowser(\'$2\')"');
Here's a link to Sandbox rep.
How do I do this correctly?
The onclick not being set on the React element is actually expected behavior.
Because there's an XSS security risk when evaling the onclick string. The recommended solution is to use the replace option in html-react-parser.
you can also use dangerouslySetInnerHTML which involves security risk.
Sandbox Demo
export default function App() {
let string =
'This is a string containing html link to Google';
const re = new RegExp('<a([^>]* )href="([^"]+)"', "g");
let replaced = string.replace(re, "<a onclick=\"alert('$2')\"");
return (
<div className="App">
<p dangerouslySetInnerHTML={{__html: replaced}}></p>
<p>{parse(string, {
replace: domNode => {
if (domNode.name === 'a') {
let href = domNode.attribs.href
domNode.attribs.onClick = () => { alert(href) }
delete domNode.attribs.href
}
}
})}</p>
</div>
);
}
Not sure on specifics of electron, but passing a (function(){functionName()})() would not work in html if there is no functionName variable available on window scope. Being there is a global environment in electron this might answer your question:
const Features = () => {
const { title } = useSelector(({ titles }) => titles);
let string = title.features;
// the string can contain some html tags. Example bellow:
// sting = 'This is a string containing a href to Google';
function runOpen(href){
shell.openExternal(href)
}
global.runOpen = runOpen;
string = string.replace(/href="(.*?)"/g, function() {
return `onClick="runOpen(${'www.google.com'})"`;
});
return (
<>
<Heading>Features</Heading>
<Text content={parsedHTML} />
</>
);
};
if it doesnt you can use something like onclick="console.log(this)" to find out what is the scope the onclick runs in and futher assign your runOpen variable there.
How do I successfully copy to clipboard the SVG content on this page?
https://cdn.dribbble.com/assets/dribbble-ball-icon-e94956d5f010d19607348176b0ae90def55d61871a43cb4bcb6d771d8d235471.svg
I get an error at the select() method that looks like this:
Uncaught TypeError: el.select is not a function
at <anonymous>:1:4
This is my code at the moment that can be run in the console.
function copyClip() {
const docEl = document.documentElement
const string = new XMLSerializer().serializeToString(docEl)
const el = document.createElement('textarea')
docEl.insertAdjacentElement('beforeend', el)
el.value = string
el.select()
document.execCommand('copy')
}
copyClip()
One answer to this is to use a different execution of copying to clipboard but I don't understand why the select method in the original question isn't working. This function works:
const docEl = document.documentElement
const string = new XMLSerializer().serializeToString(docEl)
navigator.clipboard.writeText(string).then(
function () {
alert('The SVG was copied to your clipboard.')
},
function (err) {
alert('Could not copy:', err)
}
)
i am making a request to this url to translate text from english to spanish
URL: https://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=es&dt=t&q=Hello
and efectivelly i´m getting translated text to spanish, so, now i want to get dinamically all innerText in body document and then put again translated text, how can i do this?
In simple words, I want to dynamically translate the website with a button click.
This is my example code to start:
let textToBeTranslate =["hello","thanks","for","help me"]
var url = "https://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=es&dt=t&q="+textToBeTranslate;
fetch(url)
.then(data => data.json()).then(data => {
//Text translated to spanish
var textTranslated = data[0][0][0].split(", ");
console.log(textTranslated)
//output: ["hola gracias por ayudarme"]
//Now i want to dinamically put translated text in body tag again
}).catch(error => {
console.error(error)
});
Try this:
const translateElement = async element => {
const
elementNode = element.childNodes[0],
sourceText = elementNode && elementNode.nodeValue;
if (sourceText)
try {
const
url = 'https://translate.googleapis.com/translate_a/single?client=gtx&sl=en&tl=es&dt=t&q=' + sourceText,
resultJson = await fetch(url),
result = await resultJson.json(),
translatedText = result[0][0][0].split(', ');
elementNode.nodeValue = translatedText;
} catch (error) {
console.error(error);
}
}
}
For a single element - Just call it, like this:
(async () => await translateElement(document.body))();
For all elements in the DOM - You will need to recursively go over all elements starting from the desired parent tag (body, in your case), and call the above function for each element, like this:
(async () => {
const
parent = 'body',
selector = `${parent}, ${parent} *`,
elements = [...document.querySelectorAll(selector)],
promises = elements.map(translateElement);
await Promise.all(promises);
})();
Remarks:
I used childNodes[0].nodeValue instead of innerHtml or
innerText to keep the child elements.
Note that go over the entire DOM is not recommended and can lead to problems like changing script and style tags.
I use Testcafe to test a website which is using the jquery plugin Chosen and
I want to make an assertion in my test code depending on a value returned by an external helper function (getSelectedOption).
This function gets a Chosen Selector as a parameter and should return the selected value to the assertion, but the function always returns the first element of the list instead of the chosen one.
When I use the function code in my test, everything works fine.
It seems that the function doesn't have the actual state about the HTML data and can't see that an element is already selected.
This is a snippet from the test code:
await t
.click(await getOptionByText('salutation', 'Frau'))
.expect(await getSelectedOption('gender')).eql('weiblich')
This is a snippet from the external functions:
export const getChosenSelectorFromName = selectName => `#${selectName}_chosen`;
export const getSelectedOption = async selectName => {
const selectedOptionText = await
Selector(getChosenSelectorFromName(selectName))
.find('.chosen-single')
.innerText;
return selectedOptionText.toLowerCase().trim()
};
export const getOptionByText = async (selectName, optionText) => {
const chosenSelectorString = getChosenSelectorFromName(selectName);
await t.click(Selector(chosenSelectorString));
return await Selector(chosenSelectorString)
.find('.chosen-drop')
.find('li')
.withText(optionText);
};
When I use similar code like the getSelectedOption function inside my test, everything works fine:
const genderSelect = Selector('#gender_chosen);
.click(await getOptionByText('salutation', 'Frau'))
.expect(genderSelect.innerText).eql('WEIBLICH')
If you call await Selector(<some value>) then TestCafe immediately retries the data from the web page at the current moment.
You can tell TestCafe to retry data from web page until it becomes equal to the expected value.
To do it, you need to move the DOM manipulation function into ClientFunction:
import { Selector, ClientFunction } from "testcafe";
fixture `Fixture`
.page('https://harvesthq.github.io/chosen/');
const getChosenSelectorFromName = selectName => `#${selectName}_chosen`;
const getSelectedOption = ClientFunction(selector => {
var choosenDiv = document.querySelector(selector);
var singleValueEl = choosenDiv.querySelector('.chosen-single');
return singleValueEl.innerText;
});
test('test', async t => {
await t.expect(getSelectedOption('.chosen-container')).eql('Choose a Country...');
});