Display .txt file's content in React - javascript

I'm looking to display the contents of a .txt file stored in dropbox via the dropbox API.
I'm already pulling through and displaying images by creating src url blobs
All of the examples I've found through researching either use jQuery or ajax. Since I'm already accessing these files through the API surely there isn't a need for another call up to the dropbox server?
I've tried to use the embed tag but the browser then tries to download the file.
How do you display a txt file's content in react using the Dropbox API?
class Project extends React.Component{
constructor(){
super();
this.state = {
fileSource: [],
}
}
componentDidMount(){
var that = this;
var sources = [];
var link = "/"+this.props.title;
dbx.filesListFolder({path: link})
.then(function(response) {
...
...
//call to dropbox
...
...
var newUrls=window.URL.createObjectURL(response.fileBlob);
sources.push(newUrls);
})
.then(function(){
that.setState({
fileSource: sources,
});
});
}
});
}
render() {
if(!this.state.fileSource.length)
return null;
let text = this.state.fileSource.map((el, i) =>
<embed src={el}/>
)
return (
<div className="projectWrapper">
{text}
</div>
);
}
}

Update: I came across this previous question which creates a fileBlob from the object and then uses FileReader() and readAsText() to display the contents.

Related

Loading HTML file content in a Vue component

I'm having troubles loading the content of an HTML file in a Vue component. Basically i have a Django backend that generates an HTML file using Bokeh and a library called backtesting.py. My frontend is using Nuxt/Vue, so i can't just load the HTML on the page dynamically.
Here is what the HTML file looks like (it was too long to post here): https://controlc.com/aad9cb7f
The content of that file should be loaded in a basic component:
<template>
<div>
<h1>Some content here</h1>
</div>
</template>
<script>
export default {
components: {
},
data() {
return {
}
},
mounted() {
},
methods: {
}
}
</script>
The problem is that i really don't know how to do that. If i just copy and paste the content in the vue component, i'll get a lot of error due to the fact that i'm using a <script> tag in a component. The only thing i managed to do was to load the BokehJS CDN in my index.html file, but even after that i'll get a Bokeh is undefined error in the component.
What can i do to accomplish this? Any kind of advice is appreciated
Tao's answer is spot on and is very similar to how I've solved this issue for myself in the past.
However, I'd like to throw in an alternative iframe approach that could work in case reactivity is important. Here's a codesandbox link
The only difference is that this approach loads the code/HTML via XHR and writes it manually into the iframe. Using this approach, you should be able to add some reactivity if necessary.
<script>
export default {
components: {},
data() {
return {};
},
async mounted() {
this.initialize();
},
methods: {
async initialize() {
const html = await this.loadHTML();
const doc = this.htmlToDocument(html);
this.updateIframe(doc);
},
async loadHTML() {
const response = await fetch("/plot");
const text = await response.text();
return text;
},
htmlToDocument(html) {
const parser = new DOMParser();
const doc = parser.parseFromString(html, "text/html");
return doc;
},
updateIframe(doc) {
const iframe = this.$refs.frame;
const iframeDocument = iframe.contentWindow.document;
iframeDocument.open();
iframeDocument.write(doc.documentElement.innerHTML);
iframeDocument.close();
}
},
};
</script>
In the codesandbox, I've thrown in two additional methods to give you an example of how reactivity can work with this approach:
modify() {
if (this.orig) {
// Only for the purpose of this example.
// It's already been modified. Just short-circuit so we don't overwrite it
return;
}
const bokehDoc = this.$refs.frame.contentWindow.Bokeh.documents[0];
// Get access to the data..not sure if there's a better/proper way
const models = [...bokehDoc._all_models.values()];
const modelWithData = models.find((x) => x.data);
const { data } = modelWithData;
const idx = Math.floor(data.Close.length / 2);
// Store old data so we can reset it
this.orig = data.Close[idx];
data.Close[Math.floor(data.Close.length / 2)] = 0;
modelWithData.change.emit();
},
reset() {
if (!this.orig) {
return;
}
const bokehDoc = this.$refs.frame.contentWindow.Bokeh.documents[0];
// Get access to the data..not sure if there's a better/proper way
const models = [...bokehDoc._all_models.values()];
const modelWithData = models.find((x) => x.data);
const { data } = modelWithData;
const idx = Math.floor(data.Close.length / 2);
data.Close[idx] = this.orig;
modelWithData.change.emit();
delete this.orig;
}
Probably the simplest way is to make your HTML available at the URL of your choice, on your server (regardless of Vue).
Then, in your app, use an <iframe> and point its src to that html. Here's an example, using codesandbox.io, where I placed what you posted into the index.html. Below you can see it working with both <iframe> and <object> tags:
Vue.config.productionTip = false;
Vue.config.devtools = false;
new Vue({
'el': '#app'
})
body {
margin: 0;
}
h1, h3 {padding-left: 1rem;}
object, iframe {
border: none;
height: 800px;
width: 100%;
min-height: calc(100vh - 125px);
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
<h1>This content is placed in Vue</h1>
<h3>Vue doesn't really care.</h3>
<iframe src="https://1gk6z.csb.app/"></iframe>
<h1><code><object></code> works, too:</h1>
<object type="text/html" data="https://1gk6z.csb.app/"></object>
</div>
Note: if the domain serving the graph and the one displaying it differ, you'll need server-side configuration to allow the embed (most domains have it turned off by default).
Strategy:
insert and init bokeh in head tag of public/index.html
read file in a string via ajax/xhr and parse as dom tree
extract each needed dom element from the parsed tree
recreate and append each element
No iframe needed. window.Bokeh is directly accessible.
A skeletal example of reactivity is suggested through the method logBkh that logs the global Bokeh object when clicking on the graph
<template>
<div id="app">
<div id="page-container" #click="logBkh"></div>
</div>
</template>
<script>
// loaded from filesystem for test purposes
import page from 'raw-loader!./assets/page.txt'
// parse as dom tree
const extDoc = new DOMParser().parseFromString(page, 'text/html');
export default {
methods: {
logBkh(){
console.log(window.Bokeh)
}
},
mounted() {
const pageContainer = document.querySelector('#page-container')
// generate and append root div
const dv = document.createElement('div')
const { attributes } = extDoc.querySelector('.bk-root')
for(const attr in attributes) {
dv.setAttribute(attributes[attr].name, attributes[attr].value)
}
pageContainer.append(dv)
for(const _scrpt of extDoc.body.querySelectorAll('script')) {
// generate and append each script
const scrpt = document.createElement('script')
for(const attr in _scrpt.attributes) {
scrpt.setAttribute(
_scrpt.attributes[attr].name,
_scrpt.attributes[attr].value
)
}
scrpt.innerHTML = _scrpt.innerHTML
pageContainer.append(scrpt)
}
}
}
</script>
result:

Printing on Android device with Angular

I have an application where I need to print content that doesn't exist on the page but does exist in the angular template. In the template, I have a div with a click method that will accept an identifier, and then build an iframe based on the content of the id it received. The first snippet is what the clickable element looks like.
<div (click)="printButton('#printThis')" class="menu-item print-menu-item">Race {{ raceSelected?.raceNumber }}</div>
This code snippet is just a basic element with omitted private data. So it will just be an ng-template with an identifier and an iframe that I access from the method pasted below.
<ng-template #printThat>
// content that I will print but not important for this question
</ng-template>
<ng-template #printThis>
// content that I will print but not important for this question
</ng-template>
<iframe #iframe></iframe>
The last code snippet is the method on the component that builds the iframe and brings up the print dialog.
public async printButton(printMode: string): Promise<void> {
let printTemplate;
if (printMode === '#printThat') {
await this.getAllRacesForPrint()
printTemplate = this.printAll;
} else if (printMode === '#printThis') {
printTemplate = this.printSingleRace;
}
const iframe = this.iframe.nativeElement;
this.portalHost = new DomPortalOutlet(
iframe.contentDocument.body,
this.componentFactoryResolver,
this.appRef,
this.injector
);
const portal = new TemplatePortal(
printTemplate,
this.viewContainerRef
);
// Attach portal to host
this.portalHost.attach(portal);
iframe.contentWindow.onafterprint = () => {
iframe.contentDocument.body.innerHTML = '';
};
this._attachStyles(iframe.contentWindow);
iframe.contentWindow.print();
}
These two private functions are invoked in the method above to add the css to the iframe.
private _attachStyles(targetWindow: Window): void {
// Copy styles from parent window
document.querySelectorAll('style').forEach((htmlElement) => {
targetWindow.document.head.appendChild(htmlElement.cloneNode(true));
});
// Copy stylesheet link from parent window.
const styleSheetElement = this._getStyleSheetElement();
targetWindow.document.head.appendChild(styleSheetElement);
}
private _getStyleSheetElement(): HTMLLinkElement {
const styleSheetElement = document.createElement('link');
document.querySelectorAll('link').forEach((htmlElement) => {
if (htmlElement.rel === 'stylesheet') {
const absoluteUrl = new URL(htmlElement.href).href;
styleSheetElement.rel = 'stylesheet';
styleSheetElement.type = 'text/css';
styleSheetElement.href = absoluteUrl;
}
});
return styleSheetElement;
}
This works perfectly on all devices without a problem EXCEPT Android devices. On Android, the print dialog box comes up but the page is blank and has no content. Are there any good solutions for printing for Android that do not require bringing in a package or am I missing something here?

React Portals - Execute function that references new window's DOM

I'm trying to create a React Portal that when mounted, requires running a specific line to load an ActiveReports Designer component.
Here's is my portal code:
constructor(props: IWindowPortalProps) {
super(props);
this.containerEl = document.createElement('div'); // STEP 1: create an empty div
this.containerEl.id = 'designer-host';
this.containerEl.className = styles.designerHost;
this.externalWindow = null;
}
private copyStyles = (sourceDoc: Document, targetDoc: Document) => {
Array.from(sourceDoc.styleSheets).forEach(styleSheet => {
if (styleSheet.cssRules) { // true for inline styles
const newStyleEl = sourceDoc.createElement('style');
Array.from(styleSheet.cssRules).forEach(cssRule => {
newStyleEl.appendChild(sourceDoc.createTextNode(cssRule.cssText));
});
targetDoc.head.appendChild(newStyleEl);
} else if (styleSheet.href) { // true for stylesheets loaded from a URL
const newLinkEl = sourceDoc.createElement('link');
newLinkEl.rel = 'stylesheet';
newLinkEl.href = styleSheet.href;
targetDoc.head.appendChild(newLinkEl);
}
});
}
componentDidMount() {
this.externalWindow = window.open('', '', `height=${window.screen.height},width=${window.screen.width}`);
this.externalWindow.document.body.appendChild(this.containerEl);
this.externalWindow.document.title = 'A React portal window';
this.externalWindow.addEventListener('load', () => {
new Designer('#designer-host');
});
}
render() {
return ReactDOM.createPortal(null, this.containerEl);
}
However, when the new window loads, I get the error
Error: Cannot find the host element. at Function.<anonymous>
which indicates that the designer-host div is not there. I think the load function points to the main DOM and not the new window's one.
Alternatively, I tried appending the ActiveReports .js file by doing in my componentDidMount()
s.type = "text/javascript";
s.src = "../node_modules/#grapecity/activereports/lib/node_modules/#grapecity/ar-js-designer/index.js";
this.externalWindow.document.head.append(s);
and then assigning the Designer instantiation on the onLoad property of the element. Again with no luck.
Is there maybe a way I could run JavaScript a code after the portal has been loaded and point to that DOM?
Thank you
I work for GrapeCity. Could you please go to our support portal and submit a ticket. We will need a full code sample for us to be able to answer this question. Please give us a download link to the sample within the ticket.
Thank you

I want to fetch a file from my code folder to my js file automatically when the page loads

I want to fetch a file from my code folder to my js file automatically when the page loads so I can use in an audio visualizer. I don't have to fetch it from the users computer!
How do I do it?
I was trying to do it using fetch but I can only fetch from URLs now.
I want to use the file like this
document.getElementById('file').addEventListener('change', function(e){
this.fileReader.readAsArrayBuffer(e.target.files[0]);
}.bind(this));
var _this = this;
this.fileReader.onload = function(){
_this.audioContext.decodeAudioData(_this.fileReader.result, function(buffer){
if(_this.source) {
_this.source.stop();
}
_this.source = _this.audioContext.createBufferSource();
_this.source.buffer = buffer;
_this.source.loop = true;
_this.source.connect(_this.analyser);
_this.gainNode = _this.audioContext.createGain();
_this.source.connect(_this.gainNode);
_this.gainNode.connect(_this.audioContext.destination);
_this.source.start(0);
_this.frequencyArray = _this.webgl.sphereG.attributes.aFrequency.array;
_this.indexPosArray = _this.webgl.indexPosArray;
_this.indexPosLength = _this.webgl.indexPosArray.length;
_this.isReady = true;
});
};
}
but instead of getting the user to choose a file from his/her computer I want to fetch a file from my the folder where this code lies automatically.
No need for an <input type="file"/> then.
You might have success with
// if the HTML that's executing the current JavaScript is at
// https://example.com/foo/index.html and audiofile.ext
// is in the same location as index.html (i.e. also inside foo),
// this would work:
fetch('audiofile.ext')
.then(res => res.blob())
.then(blob => {
// do stuff with the blob
});
See the fetch docs on MDN.

How do I add a custom script to React component?

Long story short: I'm trying to add a front-end app to my portfolio site that uses React. I would like to integrate the app into the component as it renders. What I have setup right now is:
React component:
class Giphy extends Component {
constructor(props) {
super(props);
this.state = {src: 1}
this.handleClick = this.handleClick.bind(this);
}
handleClick(event) {
this.setState({src: event.target.value})
}
componentDidMount() {
const script = document.createElement("script");
script.type = "text/javascript";
script.src = "/scripts/giphyLogic.js";
document.body.appendChild(script);
}
...and a bunch of stuff in the render() method that doesn't matter
the script that I want to load involves a bunch of jQuery and simple JS stuff.
function displayButtons() {
$("#buttons").empty();
for (i=0; i<buttonArray.length; i++){
var a = $("<button type='button' class='btn btn-info'>");
var btnID = buttonArray[i].replace(/\s+/g, "+")
a.attr("id", btnID);
a.text(buttonArray[i]);
$("#buttons").append(a);
}
}
$("#addButton").on("click", function() {
var newButton = $(".form-control").val();
buttonArray.push(newButton);
displayButtons();
})
function displayGIFs() {
$(".btn-info").on("click", function() {
$("#resultsContainer").empty();
var subject = $(this).attr("id");
var giphyURL = "http://api.giphy.com/v1/gifs/search?q=" + subject + "&api_key=dc6zaTOxFJmzC";
$.ajax({ url: giphyURL, method: "GET"}).done(function(res) {
for (t=0; t<25; t++) {
var rating = res.data[t].rating;
var image = $("<img>");
var imgURLmoving = res.data[t].images.fixed_height.url;
var imgURLstill = res.data[t].images.fixed_height_still.url;
image.attr("src", imgURLstill);
image.attr("data-still", imgURLstill);
image.attr("data-moving", imgURLmoving);
image.attr("data-state", "still")
image.addClass("gif");
$("#resultsContainer").append("<p>" + rating + "</p");
$("#resultsContainer").append(image);
}
})
$(document.body).on("click", ".gif", function() {
var state = $(this).attr("data-state");
if (state === "still") {
$(this).attr("src", $(this).data("moving"));
$(this).attr("data-state", "moving");
} else {
$(this).attr("src", $(this).data("still"));
$(this).attr("data-state", "still");
}
})
})
}
displayButtons();
displayGIFs();
This all works on a standalone HTML document, but I can't seem to get the script to work properly. When the component loads and I inspect the page,
<script type="text/javascript" src="/scripts/giphyLogic.js"></script>
is there under the bundle.js script tag, but nothing from the script happens.
I get an "Uncaught SyntaxError: Unexpected token <" error that is attributed to giphyLogic.js:1 even though in the actual .js file, that line is blank. I've looked around, and this apparently happens when a file is included that doesn't exist, but the file is definitely there. I've double checked the path (by including an image in the same folder and loading the image successfully on the page) and it's correct.
Is there a way to resolve this, or am I going to have to create methods within the React component that I'm creating?
Do not mix jQuery and react. Learn how to use react properly by reading the well-written documentation. They can guide you through the many examples to get a simple app up and running.
Once again, do NOT use jQuery and react. jQuery wants to manually manipulate the DOM, and react manages a virtual DOM. The two will conflict more often than not, and you're going to have a bad time. If you have a very deep understanding of react, there are very few scenarios in which you could maybe use some jQuery, but nearly all of the time, it is to be avoided at all costs.
Obviously things like $.ajax() are fine, but for anything dealing with DOM manipulation, stay away. And if you only end up using jQuery for $.ajax() calls... you should switch to a leaner library like axios or use the native fetch API.

Categories

Resources