Update reactive data in an iframe in reactjs - javascript

I am using iframe in reactjs. User add some data in an input and then we take the input and compile it with handlebar and then we use the compiled html code in iframe and then iframe loads. The issue is, every time user write something it keeps re rendering which makes it super ugly.
<iframe
srcDoc={compiledHTML}
width={"100%"}
height={"1000px"}
></iframe>
So here, srcDoc has all the compiled html output. Is there any ways we can use reactjs jsx variable and then use/parse them inside the iframe?
Here is the code that we use to compile the data and html
const data = {
stringified_html: stringified_html,
dynamic_data: {
logo: school_logo,
USER_NAME: "{{USER_NAME}}",
body: emailBodyWatcher,
},
};
const compileHTML = useCallback(() => {
try {
let html = Handlebars.compile(data.stringified_html);
return html(data.dynamic_data);
} catch (err) {
console.log(err);
}
}, [data]);
Thank you.

Related

How to have JavaScript functions work across different HTML pages?

I am building a website with several HTML pages, and going to fill up info on different pages through an API. I have added onclick listeners to HTML elements like this:
// ASSIGNING ELEMENTS AS VARIABLES
const EPL = document.getElementById('epl');
const bundesliga = document.getElementById('bundesliga');
const laliga = document.getElementById('laliga');
// ONCLICKS
EPL.onclick = function() {
getStandings('2021');
location.replace('standings.html');
}
bundesliga.onclick = function() {
getStandings('2088');
location.replace('standings.html');
}
laliga.onclick = function() {
getStandings('2224');
location.replace('standings.html');
}
When one of these is clicked, I call a function (getStandings) with its unique argument to fetch some data from the API. I also want to move to another HTML page, for which I used location.replace.
I'm caught in a dilemma: if I use the same JS file for every HTML page, when I get to the new HTML page, I get errors as the new HTML page does not have every element:
main.js:41 Uncaught TypeError: Cannot set property 'onclick' of null
But if I use different JS files, maybe one JS file for each HTML file, I cannot carry forward the bits of information I need. How can I get to the new HTML page, with its own JS file, without stopping and losing everything in the function I'm in currently, under the JS file of the old page? For example, the argument '2021' or '2088' are to be passed into the getStandings() function which will populate the new HTML page with data from an API. If I jump to a new HTML page with a new JS file, this is lost.
Is there a better way to organise my files? 😐😐😐😐😐
You can set your event listeners on the condition that the elements are not null e.g.
const EPL = document.getElementById('epl');
const bundesliga = document.getElementById('bundesliga');
const laliga = document.getElementById('laliga');
if(EPL){
EPL.onclick = function() {
getStandings('2021');
location.replace('standings.html');
}
}
etc...
Solved! As amn said, I can add URL parameters to the end of the URL of the new HTML page, then get the variables from its own URL once I'm on the new HTML page.
I think I would rather use classes instead of IDs to define the listener, and maybe IDs for dedicated action.

Injecting gatsby-image into html created from markdown using ReactDOMServer.renderToString

Minimum Reproducible Example on Github
I'm trying to inject some images into my pages created from markdown. I'm trying to do this using ReactDomServer.renderToString()
const componentCreatedFromMarkdown = ({data}) => {
...
useEffect(() => {
const injectDivs = Array.from(document.getElementsByClassName('injectDivs'))
injectDivs.forEach((aDiv) => {
aDiv.innerHTML = ReactDOMServer.renderToString(<Img fluid={data.allFile.edges[0].node.childImageSharp.fluid} />)
}
})
...
}
The img is showing as a black box, if I right click the image I can open it in a new tab, which shows the image as it is supposed to be.
How I understand the problem
The image html is correctly inserted into the page
gatsby-image loads the lowest quality image and applies some inline
styles. All that information is present in the html) This is done to
enable the blur-up effect on the image for a better UX.
The client side code that would load a higher resolution image and remove the inline styles is never applied.
Useful diagnostic information
The function inside useEffect does not get run on the server-side, but rather on the client after the component has mounted. It's possible to verify this by adding a console.log statement inside the effect and seeing that it is logged on the browser's console.
Source of the problem
ReactDOMServer.renderToString() only builds the HTML that's required by a component, it then requires the component to be hydrated.
Direct fix
You can use ReactDOM.render to put the image on the page, given that the effect will execute client side. I have a PR showing that here.
Recommended approach
You could instead import the image inside the mdx component and directly render it there. Your gatsby config can already support this πŸ‘ In content/blog/hello-world/index.mdx, you can replace the injectImage div with this ![A salty egg, yummm](./salty_egg.jpg) (where the text inside the [] is the alt text)
This documentation might be helpful
https://www.gatsbyjs.org/docs/working-with-images-in-markdown/
https://www.gatsbyjs.org/packages/gatsby-remark-images/
Hope that helps! πŸ˜„
I have faced this problem before with my first Gatsby project.
The problem similar to this chat and the error image issue here.
If you replace GatsbyImageSharpFixed_withWebp_tracedSVG like they talked in spectrum chat above, or this is my current code for example, basically is related to WebP new image format.
export const query = graphql`
query {
allImageSharp {
edges {
node {
fluid(maxWidth: 110) {
aspectRatio
originalName
sizes
src
srcSet
srcWebp
tracedSVG
}
}
}
}
}
`;
Hope this help.
I think the problem could be in the useEffect
const componentCreatedFromMarkdown = ({data}) => {
...
useEffect(() => {
const injectDivs = Array.from(document.getElementsByClassName('injectDivs'))
injectDivs.forEach((aDiv) => {
aDiv.innerHTML = ReactDOMServer.renderToString(<MyComponent args={myArgs} />)
}
},[])
...
}
I think you should try to add useEffect(()=>{},[]), the second argument to refresh the image only when the component is mounted, without this the image will be refreshed each time.

How to initialize value of CodeMirror binding to yjs?

My main problem is initializing the text/value of a code editor(CodeMirror) on my website without it affecting the way I save/send POST requests to my backend. The following is the pug code I use for the POST request:
p
form(id='form' method='POST', action='/docs/edit/'+docs._id)
textarea(name="doo" id="content" style="display: none;")=docs.content
textarea(name="foo" id="editortext" style="display: none;")
input.btn.btn-primary(type='submit' value='Save Doc')
What I'm trying to do here, is send docs.content to textarea with id "content" so that I can use that to initialize the value of my code editor and then put the content of whats in the code editor in the textarea
"editortext" once I click the submit button. Thus, the POST request would fetch me the data from both textareas, where I can then save the content of the "editortext" textarea to my database. The logic of the code editor is referenced in the same pug file to a javascript file after rollup transpilation. The following is a chunk of the pre-compiled code:
/* eslint-env browser */
import * as Y from 'yjs'
import { WebsocketProvider } from 'y-websocket'
import { CodeMirrorBinding } from 'y-codemirror'
import CodeMirror from 'codemirror'
import 'codemirror/mode/clike/clike.js'
window.addEventListener('load', () => {
const ydoc = new Y.Doc()
const provider = new WebsocketProvider(
`${location.protocol === 'http:' ? 'ws:' : 'wss:'}${location.host}`,
'codemirror',
ydoc
)
const yText = ydoc.getText('codemirror')
const editorContainer = document.createElement('div')
editorContainer.setAttribute('id', 'editor')
document.body.insertBefore(editorContainer, null)
let content = document.getElementById("content").value
const editor = CodeMirror(editorContainer, {
mode: 'text/x-java',
lineNumbers: true
})
editor.setValue(content)
document.getElementById("form").onsubmit = function(evt){
document.getElementById("editortext").value = editor.getValue();
}
Most of this code is from the yjs-codemirror demo except for the declaration of the content variable,the invocation of the setValue method, and the document.getElementById("form") block. What this code currently does is send me the right information to my database. However, I am having trouble initializing the value of the code editor when I open up the document. The setValue method doesn't work, neither does doing the following:
const editor = CodeMirror(editorContainer, {
value: content,
mode: 'text/x-java',
lineNumbers: true
})
All of the prior examples fail even if I replace the content variable with some string. The only thing that seems to work is the following:
const editor = CodeMirror(editorContainer, {
mode: 'text/x-java',
lineNumbers: true
}).setValue(content)
However, the problem with this is that for some reason, I get the following errors in the console browser:
TypeError: codeMirror is undefined (y-codemirror.js:160:4)
TypeError: editor is undefined (index.js:28:10)
For reference, the javascript that I have been showing in this question was all from the index.js file. In any case, because the editor is undefined, I can no longer set the value of my "editortext" textarea to the CodeMirror Textarea and I can't save what is written to the code editor to my database. I'm not sure as to why this would happen, I'm not sure if this is particular to the CodeMirrorBinding from yjs but any help on this would be massively appreciated.
The following is quoted from dmonad who is one of the developers of Yjs. For future reference regarding any technical questions about Yjs, you will probably get better luck asking here as there isn't a tag for Yjs yet on StackOverflow.
Hi #notemaster,
I assume that you mean you are unable to set the value of the CodeMirror editor.
The CodeMirrorBinding binds the value of the Y.Text type to a CodeMirror instance. The setValue method works, but the value of the editor is overwritten by the binding:
ytext.insert(0, 'ytext')
const editor = CodeMirror(..)
editor.setValue('my value')
editor.value() // => "my value"
new CodeMirrorBinding(editor, ytext)
editor.value() // => "ytext value"
I suggest that you set the value after it has been bound to the YText type.
Another note: There is nothing like a default value in Yjs. Initially, the Yjs document is empty until it synced with the server. So you might want to wait until the client synced with the server before setting the value.
const setDefaultVal = () => {
if (ytext.toString() === '') {
ytext.insert(0, 'my default value')
}
}
if (provider.synced) {
setDefaultVal()
} else {
provider.once('synced', setDefaultVal)
}
const editor = CodeMirror(editorContainer, {
mode: 'text/x-java',
lineNumbers: true
}).setValue(content)
I assume editor.setValue() returns undefined . This is why the binding won’t work and you can set the initial value of the editor.

Targeting a nested iframe using puppeteer

At the moment I'm trying to create some E2E tests which require logging into Excel online and then uploading an extension.
I was able to login, open Excel and click the upload plugin button, however, I cannot get any further.
So far I've figured out there are 2 iframes, one nested in another.
I access the first one once I open Excel
let targetIFrame = await this.page.frames().find(f => f.name() === 'sdx_ow_iframe');
The tricky part about the second one is that it only appears in the DOM after I click the "Upload Plugin" button and it is nested in the one I accessed above.
I've tried different delays etc, but it just looks like puppeteer does not see it.
Base on my research, you can construct an implementation to find the iframe include in the parent frame.
Please test the code as below:
/**
* #return {!Promise<ElementHandle>}
*/
async ownerFrame() {
if (!this._frame._parentFrame)
throw new Error('No parent frame');
const rootElementHandle = await this._frame.$('*');
const rootElementDescriptionHandle = await this._client.send('DOM.describeNode', { objectId: rootElementHandle._remoteObject.objectId });
const parentsIframes = await this._frame._parentFrame.$$('iframe');
if (!parentsIframes.length)
throw new Error('No iframe elements found in parent');
return parentsIframes.find(async parentsIframe => {
const iframeDescription = await this._client.send('DOM.describeNode', { objectId: parentsIframe._remoteObject.objectId, pierce: true, depth: 2 });
const iframesRootDescription = iframeDescription.node.contentDocument.children[0];
return iframesRootDescription.backendNodeId === rootElementDescriptionHandle.node.backendNodeId;
});
}

Modify dynamic element after script completes

I'm relatively new to JavaScript, so please bear with me.
I run an instance of the Blackboard Learn LMS. Feel sorry for me later. Blackboard displays different modules to end users to show different pieces of information. The Announcements module contains non-editable code that sends an Ajax request to display all system-wide and course-specific announcements for that particular user:
<div id="Announcements">
<div id="div_1_1"> </div>
<script type="text/javascript">
Event.observe(window, 'load', function () {
new Ajax.Request('/webapps/portal/execute/tabs/tabAction', {
method: 'post',
parameters: 'action=refreshAjaxModule&modId=_1_1&tabId=_2830_1&tab_tab_group_id=_155_1',
onSuccess: function (transport) {
try {
var res = transport.responseXML.getElementsByTagName('contents')[0].firstChild.nodeValue;
$('div_1_1').innerHTML = res.stripScripts();
page.globalEvalScripts(res, true);
} catch (e) {
$('div_1_1')
.innerHTML = 'Module information is temporarily unavailable. Please reload the page. <!--' + e.toString()
.escapeHTML()
.gsub('-', '-') + '-->';
}
},
onFailure: function (transport) {
$('div_1_1').innerHTML = 'Error loading module.';
}
});
});
</script>
</div>
The module brings up a lot of redundant information that I'd like to hide. Since there's no way to edit that particular code, I've been trying to figure out a way to do one of the following:
Modify the module's contents with additional scripting from another source on the page.
Copy the module's contents to a new, editable module, and display that one instead,
Both methods have proven impossible because the script in the Announcements module only runs once the page has loaded entirely, so there's no way to run a script afterward or wait until the process has completed.
Any ideas on how I might be able to modify the contents without editing its code directly?
I ended up using the DOMSubtreeModified event:
<script type="text/javascript">
$j = jQuery.noConflict();p
$j("#div_1_1").on("DOMSubtreeModified", function () {
var div = document.getElementById("div_1_1");
var inner = div.innerHTML;
inner = inner.substring(
inner.indexOf("<!-- Display course/org announcements -->")
);
div.innerHTML = inner;
});
</script>

Categories