Dave Dave - 4 years ago 153
Javascript Question

Continue text into seperate <p> paragraphs dynamically?

The following fiddle allows text to be pasted into a

<textarea>
and generated into equal paragraphs dynamically
<p>
consisting of the same amount of characters.





The problem occurring is; text from previous dynamically generated paragraphs
<p>
overflows within each tag and does not continue to the next dynamic paragraph properly. Therefore, is it possible for the user to press enter and move that content down into the next existing paragraph, while still keeping the existing formatting dynamically and automatically?


If a new Fiddle could be provided, it would be very much appreciated, as I am still new to coding. Once again, the Fiddle can be found here.

Update: Is it possible for once the paragraphs are generated, for the user to press enter and if possible move their content down into the below paragraph seamlessly? And also for the same to apply when the backspace button is pressed, for the content to move up into the above paragraph? The problem occurring is, the text, when pressing enter, seems to hide the text due to the overflow property in css.

Thank You!






$(function() {
$("#Go").on('click', function() {
var theText = $('textarea').val();
var numberOfCharacters = 300;
while (theText.length) {
while (theText.length > numberOfCharacters &&
theText.charAt(numberOfCharacters) !== ' ') {
numberOfCharacters++;
}
$("#text_land").append("<br><\/br><p>" + theText.substring(
0, numberOfCharacters) +
"<\/p><br><\/br>");
theText = theText.substring(numberOfCharacters);
numberOfCharacters = 300;
$('p').attr('contenteditable', 'true');
$("p").addClass("text");
}
})
})
$('select').on('change', function() {
var targets = $('#text_land p'),
property = this.dataset.property;
targets.css(property, this.value);
}).prop('selectedIndex', 0);
(end);

@media print {
p {
page-break-inside: avoid;
}
}

p {
position: relative;
}

@media print {
.no-print,.no-print * {
display: none !important;
}
}

p {
border-style: solid;
color: #000;
display: block;
text-align: justify;
border-width: 5px;
font-size: 19px;
overflow: hidden;
height: 300px;
width: 460px;
word-wrap: break-word;
}

<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>


<div align="center">
<h4 align="center"><u>Paste text in the field below to divide text into
paragraphs.</u></h4><br>
<br>
<textarea placeholder="Type text here, then press the button below." cols="50" id="textarea1" rows="10">
</textarea><br>
<br>
<button id="Go">Divide Text into Paragraphs!</button>
</div>
<hr>
<h2 align="center">Divided Text Will Appear Below:</h2>
<div>
<div align="center" id="text_land" style="font-family: monospace">
</div>
</div>




Answer Source

possible for the user to press enter and move that content down into the next existing paragraph, while still keeping the existing formatting dynamically and automatically

If I understand you correctly, what you want is that once the text is divided into paragraphs, and then the user adds some text into one of them and presses Enter, then the remaining text should flow into the next paragraphs distributing the overflowing text equally as done earlier.

Similarly, when the user presses BackSpace at the start of the paragraph, then the text goes back into the previous paragraph again with the overflowing text distributed into the other paragraphs equally as done earlier.

As an algorithm, what you need is something like this:

  1. Divide initial text into equal chunks and distribute into paragraphs creating those ps dynamically as required.
  2. Listen to keyup event on those p elements
  3. If key pressed is Enter,
    • 3.1 Extract remaining text from where Enter was pressed
    • 3.2 Extract text from next all paragraphs, prepend with the overflowing text extracted above
    • 3.3 Remove next all paragraphs and distribute the combined text just like we did in step 1
  4. If key pressed is BackSpace,
    • 4.1 Check if it is in the beginning of the paragraph and if there is a preceding paragraph
    • 4.2 Extract the text of the paragraph and append to the text from next all paragraphs
    • 4.3 Remove next all paragraphs including the current one and append the extracted text to the preceding paragraph.
    • 4.4 Distribute combined text just like we did in step 1

With that rough algorithm, you can start coding which could look something like this:

Note 1: This is all JavaScript, no jQuery.
Note 2: This is overly simplified, you will need to further optimize and work out all the edge cases.

Cache required elements and bind event handlers:

var btn = document.getElementById('go'), 
    textarea = document.getElementById('textarea1'), 
    content = document.getElementById('content');

btn.addEventListener('click', splitText);
content.addEventListener('keyup', handleKey);

Distribute the initial text from the textarea removing existing paragraphs if any:

function splitText() {
    var text = textarea.value;
    while (content.hasChildNodes()) { content.removeChild(content.lastChild); }
    rearrange(text);
}

Logic for re-aranging / distributing text by creating required number of paragraphs dynamically:

function rearrange(text) {
    var chunks = text.match(/.{1,100}/g) || [];
    chunks.forEach(function(str, idx) {
        para = document.createElement('P');
        para.setAttribute('contenteditable', true);
        para.textContent = str;
        content.appendChild(para);
    });
}

Note 3: I've used 100 characters to split text for this example. Also, this does not take care of spaces and will split the words in between. You will need to do that in your code. (# see edit below)

Event handler for trapping Enter (keycode 13) and BackSpace (keycode 8) keys. Also, see if the element is a p element :

function handleKey(e) {
    var para = e.target, position, 
        key, fragment, overflow, remainingText;
    key = e.which || e.keyCode || 0;
    if (para.tagName != 'P') { return; }
    if (key != 13 && key != 8) { return; }
    ...

Get the cursor position to determine whether BackSpace was pressed at the beginning of the paragraph or not:

position = window.getSelection().getRangeAt(0).startOffset;    

If Enter was pressed, extract text after the last child of the current paragraph (contenteditable will produce a div when Enteris pressed), remove that node, prepend the remaining text of all paragraphs which come after this, and remove the remaining paragraphs.

if (key == 13) {
    fragment = para.lastChild; overflow = fragment.textContent;
    fragment.parentNode.removeChild(fragment); 
    remainingText = overflow + removeSiblings(para, false);
    rearrange(remainingText);
}

If BackSpace was pressed, check if there is a preceding paragraph and that the cursor was at the beginning. If yes, extract remaining text from all succeeding paragraphs (including the current one) while removing them:

if (key == 8 && para.previousElementSibling && position == 0) {
    fragment = para.previousElementSibling;
    remainingText = removeSiblings(fragment, true);
    rearrange(remainingText);
}

Logic to extract text from succeeding paragraphs and removing those:

function removeSiblings(elem, includeCurrent) {
    var text = '', next;
    if (includeCurrent && !elem.previousElementSibling) { 
        parent = elem.parentNode; text = parent.textContent;
        while (parent.hasChildNodes()) { parent.removeChild(parent.lastChild); }
    } else {
        elem = includeCurrent ? elem.previousElementSibling : elem;
        while (next = elem.nextSibling) { 
            text += next.textContent; elem.parentNode.removeChild(next);
        }
    }
    return text;
}

Putting that all together, here is a working snippet:

Snippet:

var btn = document.getElementById('go'), 
	textarea = document.getElementById('textarea1'), 
	content = document.getElementById('content'), 
    chunkSize = 100;
    
btn.addEventListener('click', initialDistribute);
content.addEventListener('keyup', handleKey);

function initialDistribute() {
    var text = textarea.value;
    while (content.hasChildNodes()) {
        content.removeChild(content.lastChild);
    }
    rearrange(text);
}

function rearrange(text) {
    var	chunks = splitText(text, false);
    chunks.forEach(function(str, idx) {
        para = document.createElement('P');
        para.setAttribute('contenteditable', true);
        para.textContent = str;
        content.appendChild(para);
    });
}

function handleKey(e) {
    var para = e.target, position, 
        key, fragment, overflow, remainingText;
    key = e.which || e.keyCode || 0;
    if (para.tagName != 'P') { return; }
    if (key != 13 && key != 8) { return; }
		position = window.getSelection().getRangeAt(0).startOffset;    
    if (key == 13) {
        fragment = para.lastChild;
        overflow = fragment.textContent;
        fragment.parentNode.removeChild(fragment); 
        remainingText = overflow + removeSiblings(para, false);
        rearrange(remainingText);
    }
    if (key == 8 && para.previousElementSibling && position == 0) {
        fragment = para.previousElementSibling;
        remainingText = removeSiblings(fragment, true);
        rearrange(remainingText);
    }
}

function removeSiblings(elem, includeCurrent) {
    var text = '', next;
    if (includeCurrent && !elem.previousElementSibling) { 
        parent = elem.parentNode; 
		text = parent.textContent;
        while (parent.hasChildNodes()) {
            parent.removeChild(parent.lastChild);
        }
    } else {
        elem = includeCurrent ? elem.previousElementSibling : elem;
        while (next = elem.nextSibling) { 
            text += next.textContent;
            elem.parentNode.removeChild(next);
        }
    }
    return text;
}

function splitText(text, useRegex) {
	var chunks = [], i, textSize, boundary = 0;
    if (useRegex) { 
        var regex = new RegExp('.{1,' + chunkSize + '}\\b', 'g');
        chunks = text.match(regex) || [];
    } else {
		for (i = 0, textSize = text.length; i < textSize; i = boundary) {
			boundary = i + chunkSize;
			if (boundary <= textSize && text.charAt(boundary) == ' ') {
				chunks.push(text.substring(i, boundary));
			} else {
				while (boundary <= textSize && text.charAt(boundary) != ' ') { boundary++; }
				chunks.push(text.substring(i, boundary));
			}
		}
	}
	return chunks;
}
* { box-sizing: border-box; padding: 0; margin: 0; }
body { font-family: monospace; font-size: 1em; }
h3 { margin: 1.2em 0; }
div { margin: 1.2em; }
textarea { width: 100%; }
button { padding: 0.5em; }
p { padding: 1.2em 0.5em; margin: 1.4em 0; border: 1px dashed #aaa; }
<div>
  <h3>Paste text in the field below to divide text into
        paragraphs..</h3>
  <textarea placeholder="Type text here, then press the button below." id="textarea1" rows="5" ></textarea><br/><br/>
  <button id="go">Divide Text into Paragraphs</button>
</div>
<hr>
<div>
  <h3>Divided Text Will Appear Below:</h3>
  <div id="content"></div>
</div>

And a Fiddle for you to play with:

Fiddle: https://jsfiddle.net/abhitalks/jwnnn5oy/


Edit:

Fixed the regex for breaking at word boundaries. Also added the non-regex procedural code for the same (on the lines of Op's original code), to demonstrate the Op on how to factor-in and integrate his other code segments into it.

Note 4: Regarding Op's comment of using jQuery, it has got nothing to do with the problem at hand. jQuery is nothing but JavaScript and should be trivial for them to incorporate pieces of snippet into the larger code base.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download