How do you put a line break in a React string? - javascript

I have a text file that I'm reading as a string, and then inserting into the DOM like so:
//the state gets set with the contents of a text file that has proper indentation
state = {
parsedText: null
}
//in the render function, this is how the text comes in
<p>{this.state.parsedText}</p>
When I iterate through the character codes, I can see that the 'enter' codes (i.e., 13) seem to work when I console.log the file (the indentation looks correct). Unfortunately, those character codes don't translate to HTML - the entire string just comes out as one solid chunk of text. So, what I'm trying to do is insert either <br> tags or \n characters or in order to make the breaks work in the HTML.
Here's what that looks like:
rawFile.onreadystatechange = () => {
if (rawFile.readyState === 4) {
if (rawFile.status === 200 || rawFile.status == 0) {
let allText = rawFile.responseText;
for (let i = 0; i < allText.length; i++) {
let uni = allText.charCodeAt(i)
if (uni === 13) {
console.log('enter!!')
allText = allText.substring(0, i) + "\n" + allText.substring(i + 1, allText.length - 1)
}
}
console.log("allText: ", allText);
console.log(typeof allText)
this.setState({
parsedText: allText
});
}
}
};
rawFile.send(null);
}
The \n insertions show up in the text string but are seemingly just ignored in the DOM. Is there a way to make this work? I've tried dangerouslySetInnerHTML and that does nothing.
Any ideas on how to make this work?

Had the same issue. just solved it.
All you need to do is to save the string as array. like so:
let text = "this is text without break down \n this won't work but will be one line"
let text = ["this is text with breakdown",
<br/>,
"this is the second line"];
Hope that helps

If possible, avoid using dangerouslySetInnerHTML.
An alternate solution could be splitting your text into an array then rendering a new div for each "newline".
Of course, you would need to come up with a better key for the text, as there could be duplicate text - though that is a separate question in and of itself.
class Hello extends React.Component {
constructor(props) {
super(props)
this.state = {
parsedText: [
"Some text",
"More text",
"Keep on texting"
]
}
}
render() {
return <div>{this.state.parsedText.map(text => <div key={text}>{text}</div>)}</div>;
}
}
ReactDOM.render(
<Hello/>,
document.getElementById('container')
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.6.3/umd/react.production.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.6.3/umd/react-dom.production.min.js"></script>
<div id="container">
<!-- This element's contents will be replaced with your component. -->
</div>

So you need to dangerouslySetInnerHTML, and as was pointed by FrV, the answer is to insert a <br /> ... turns out I was missing the closing "/". Whoops. Hope this helps somebody running into a similar problem.

If you want to print a <br/> tag in react I wanted to point out that a neat workaround is to use the <Trans> component from i18next.
If you have a variable like this:
const customInviteMessageByCreator = "DEMO APPOINTMENT<br/>This room is to test your video room.";
You can write your variable wrapped in a Trans component like so:
<Trans>{customInviteMessageByCreator}</Trans>
And then it replaces the whitelisted html elements that you can configure in your i18n.js file like this:
transKeepBasicHtmlNodesFor: ["br", "strong", "b", "i"],
I didn't read that this is possible but I was curious if it works because i18next also translates our linebreaks in the translation.json and indeed it does.
I think it is a less hacky and more reliable method than what I've found so far like doing a map on <br> where there could be a / before the >. Plus you can whitelist more than just linebreaks. And of course a smarter choice over dangerouslySetInnerHtml.

Related

Perform text animation by splitting text, but retain line breaks

I have created a function that maps over a sentence (provided by a CMS) and adds containers to each word. Splitting each word at spaces.
Now, some words will require line breaks, but my function is wiping them out, and I am not quite sure how I am going to retain line breaks in this case.
The demo below displays the issue I am having. The second one, although originally having a line break, has it removed when the function runs.
The breaks are coming from WP WYSIWYG, using a return. The field is using ACF and using the 'Automatically add ' option for new lines. Dev tools interprets/displays the original output as
<h1>This Is<br>Line Broken</h1>
Any ideas or advice of how to retain the breaks will be appreciated. For the purpose of my animations, each word will require the markup in the return value of $reformatedText
let $textAreaContent;
let $splitText;
let $reformatedText;
let $delayTime;
let $delayValue;
let $textAreas = document.querySelectorAll('.stagger-text');
function setDelayValue(curCount) {
$delayTime = 0.075;
return parseFloat(curCount * $delayTime).toFixed(3);
}
function createStaggerContainers($textArea) {
$textAreaContent = $textArea;
$splitText = $textAreaContent.innerText.split(/\s|<br>/g);
$reformatedText = $splitText.map((item, i, arr) => {
$delayValue = setDelayValue(i);
return `<span class="stagger__container">
<span class="stagger__text" style="animation-delay: ${$delayValue}s">${arr.length - 1 === i ? item : `${item} `}</span>
</span>`;
}).join('');
$textAreaContent.innerText = '';
$textAreaContent.innerHTML = $reformatedText;
}
if ($textAreas && $textAreas.length) {
$textAreas.forEach(($textArea) => {
createStaggerContainers($textArea);
});
}
<div class="stagger-text">Some text here</div>
<div class="stagger-text">This has line<br> breaks</div>

Displaying text onclicking href [duplicate]

I have an h1 with id of toptitle that is dynamically created, and I am not able to change the HTML.
It will have a different title depends on a page. Now when it is Profile, I want to change it to New word with jQuery.
<h1 id="toptitle">Profile</h1> // Changing only when it is Profile
// to
<h1 id="toptitle">New word</h1>
Note: If the text is Profile, then change it to New word.
This should work fine (using .text():
$("#toptitle").text("New word");
Something like this should do the trick:
$(document).ready(function() {
$('#toptitle').text(function(i, oldText) {
return oldText === 'Profil' ? 'New word' : oldText;
});
});
This only replaces the content when it is Profil. See text in the jQuery API.
Something like this should work
var text = $('#toptitle').text();
if (text == 'Profil'){
$('#toptitle').text('New Word');
}
Could do it with :contains() selector as well:
$('#toptitle:contains("Profil")').text("New word");
example: http://jsfiddle.net/niklasvh/xPRzr/
Cleanest
Try this for a clean approach.
var $toptitle = $('#toptitle');
if ( $toptitle.text() == 'Profile' ) // No {} brackets necessary if it's just one line.
$toptitle.text('New Word');
$('#toptitle').html('New world');
or
$('#toptitle').text('New world');
Pretty straight forward to do:
$(function() {
$('#toptitle').html('New word');
});
The html function accepts html as well, but its straight forward for replacing text.
*In my case i have stored the new Value in the var altText
$('#toptitle').text(altText);
*
And it Worked

What is the aspect of Javascript and DOM scoping and assignment rules which prevent the code from working on the second block of html?

The html code is below is exported form orgmode and the Javascript function is meant to transform the <pre class="src src-emacs-lisp"> block to <pre><code class="lisp"> for it to be syntax-highlighted by highlightjs.
For some Javascript quirk I don't understand the function doesn't work on the second block. I thought it was the syntax of the html code in it, but when I rearrange the blocks, the fault always happens on the second. There must be something about the scoping rules of Javascript that I don't understand. The code to locate the blocks works fine and if I print them out without processing it works they all show up. It is when I apply the transformations that things go wrong. What feature of Javascript am I not comprehending?
The code which just prints them out works fine. They are all detected.
document.addEventListener('DOMContentLoaded', (event) => {
var lispBlocks = document.getElementsByClassName('src src-emacs-lisp');
alert(lispBlocks.length);
for (i=0;i<lispBlocks.length;i++){
var iHtml = lispBlocks[i].outerHTML;
//alert(lispBlocks[i].outerHTML);
alert(iHtml);
};
});
With the code that transforms them, things go south
document.addEventListener('DOMContentLoaded', (event) => {
var lispBlocks = document.getElementsByClassName('src src-emacs-lisp');
alert(lispBlocks.length);
var iHtml;
var lBlockText;
for (i=0;i<lispBlocks.length;i++){
//lispBlocks[i].className = '';
iHtml = lispBlocks[i].innerHTML;
alert(lispBlocks[i].innerHTML);
alert(lispBlocks[i].outerHTML);
lBlockText = '<pre><code class="lisp">' + iHtml + '</code></pre>';
alert(lBlockText);
lispBlocks[i].outerHTML = lBlockText;
//alert(lispBlocks[i].outerHTML);
//hljs.highlightBlock(lispBlocks[i]);
};
});
<!-- language: lang-html -->
<div class="org-src-container">
<pre class="src src-emacs-lisp">;; after splitting a frame automatically, switch to the new window (unless we
;; were in the minibuffer)
(setq split-window-preferred-function 'my/split-window-func)
(defun my/split-window-func (&optional window)
(let ((new-window (split-window-sensibly window)))
(if (not (active-minibuffer-window))
(select-window new-window))))
</pre>
</div>
<div class="org-src-container">
<pre class="src src-emacs-lisp"> (defun split-window--select-window (orig-func &rest args)
"Switch to the other window after a `split-window'"
(let ((cur-window (selected-window))
(new-window (apply orig-func args)))
(when (equal (window-buffer cur-window) (window-buffer new-window))
(select-window new-window))
new-window))
(advice-add 'split-window :around #'split-window--select-window)
</pre>
</div>
<div class="org-src-container">
<pre class="src src-emacs-lisp">;; settings for default frames
(add-to-list 'default-frame-alist '(font . FONT ))
;;or
;; set-face-atribute, ='default nil= for all existing frames and new frames
;; ='default t= for new frames only
;; function sets a number of attributes besides :font see docs
(set-face-attribute 'default nil :font FONT )
;;set frame font
(set-frame-font FONT nil t)
</pre>
</div>
The problem is that getElementsByClassName returns a live list
Therefore, when changing the outerHTML you change the list, because you've changed the class of the element that is in the live list
In the example, you'd have a list of elements, [a, b, c], and i == 0
You change a such that it disappears from the list, now the list is [b, c] but i increments to 1, so the next iteration changes c, skipping b
use
var lispBlocks = document.querySelectorAll('.src.src-emacs-lisp');
instead
Alternatively you could use a while loop
while(lispBlocks.length) {
iHtml = lispBlocks[0].innerHTML; // note, the hard coded `0` here
// etc
}

HTML comment if without javascript

I know that I can display/hide content whether the browser is IE or not or even the version of IE. I was wondering if I can use other expressions too such as
<!--[if 1 == 0]-->
This should be hidden
<!--[endif]-->
The reason behind this is that I'm sending auto generated E-Mails and for me it would be easier to insert such comments in the template E-Mail instead of creating multiple templates.
if you have a template system, then make this in your template. Anyway when you render the template you calculate the condition, but instead of printing "0 == 1" or "0 == 0", use the template's ability to print or not to print the following paragraph
I know this would look like a long answer but I just wanted to divide the code into small functions each does its own job -kind of-, first select each element with a class name of hasComment in an array using querySelectorAll then pass this array to updateHTML() function, loop through its element and call returnComment() function for each item in the array.
The returnComment() function first call hasComment() function on the element passed to it, and using .replace() to get the exact string. Function hasComment() loop through the child nodes of the element and if the nodeType of the child node is 8 it then it's a comment, we return the text between the comment <!-- and -->.
This .replace(/\[|\]/ig, ''); omits the brackets to get value of either show or hide which according to it we "hide" or "show" the child .contentDiv div.
JS Fiddle
var commentDivs = document.querySelectorAll('.hasComment');
updateHTML(commentDivs);
function updateHTML(arr) {
for (var i = 0; i < arr.length; i++) {
var childDiv = arr[i].querySelector('.contentDiv'),
showIt = returnComment(arr[i]);
if (showIt == 'show') {
childDiv.style.display = 'block';
console.log('Div-' + (i + 1) + ': shown');
} else if (showIt == 'hide') {
childDiv.style.display = 'none';
console.log('Div-' + (i + 1) + ': hidden');
}
}
}
function returnComment(element) {
var comment = hasComment(element);
comment = comment.replace(/\[|\]/ig, '');
return comment;
}
function hasComment(element) {
for (var i = 0; i < element.childNodes.length; i++) {
if (element.childNodes[i].nodeType == 8) {
return element.childNodes[i].data;
}
}
}
<div class="hasComment">
<!--[hide]-->
<div class="contentDiv">Div -1: This should be hidden</div>
</div>
<hr>
<div class="hasComment">
<!--[hide]-->
<div class="contentDiv">Div -2: Again, This should be hidden</div>
</div>
<hr>
<div class="hasComment">
<!--[show]-->
<div class="contentDiv">Div -3: But this should be shown</div>
</div>
----------
Notes:
Wrapping the all contents of each .hasComment elements making controlling the content easier.
The above solution only work on the very top level of .hasComment element children, so if you have other comments inside .contentDiv these comments won't be affected.Demo Fiddle
You could probably use [if 1==0] for "templating" like in your code then use eval() or more complex regex to check upon it, but IMHO I think using show and hide look easier and mostly less bugs as you this over and over through your document.
More details about nodeType:
https://developer.mozilla.org/en-US/docs/Web/API/Node/nodeType
https://developer.mozilla.org/fr/docs/Web/API/Node/nodeType
http://www.w3schools.com/xml/dom_nodetype.asp
Since you are developing for email clients, no this isn't possible. You need to figure out how different clients can be targeted. Then set the display property via CSS to whatever is affected.
Ideally, your emails shouldn't need any kind of crazy logic like this. It is a smell that your email is bad. Not to mention, anything you put in the email itself is viewable, all someone needs to do is turn off HTML rendering or view the source.

empty span elements within div block

I'd like to append updated javascript data to 2 span tags, rescued and rescued2, when it's available. Since I'm appending new data, I first need to empty out the tags first so they don't keep reappending data...
function updateHUD() {
$('#bottomDisplay').empty();
$('#rescued').append("Total Animals Rescued: " + rescuedTotal_Array.length);
$('#rescued2').append("Total Animals Rescued lol: " + rescuedTotal_Array.length);
}
<div id="bottomDisplay">
<ul>
<li>Total Animals Rescued: <span id="rescued"></span></li>
<li>Total Animals Rescued2: <span id="rescued2"></span></li>
</ul>
</div>
I'd like to do the clear that way so I don't need to individually clear each span... but instead clear the entire div block then start over. Instead, when I clear the div block, it just erases all spans inside.
Why is this not acceptable?
On another note, I suppose I can just skip the clearing and appending, and just set the text...
$('#rescued').text(rescuedTotal_Array.length);
$('#rescued2').text(rescuedTotal_Array.length);
.empty gets rid of everything inside the selected element. If you want to empty both spans in one go, you'd need to do:
$('#bottomDisplay').find('span').empty();
For reference, here's what .empty looks like:
empty: function() {
var elem,
i = 0;
for ( ; (elem = this[i]) != null; i++ ) {
if ( elem.nodeType === 1 ) {
// Prevent memory leaks
jQuery.cleanData( getAll( elem, false ) );
// Remove any remaining nodes
elem.textContent = "";
}
}
return this;
}
elem.textContent = ""; is what is getting rid of everything inside.
I'm a little confused by the question but I think I understand. Correct me if I'm not understanding you fully.
You are looking for a single line to empty both spans. However, you want to leave the rest of the HTML inside the div intact and you don't want to have to target each individual span. The following line would work:
$('#bottomDisplay span').empty();

Categories

Resources