Pre-formatting text to prevent reflowing - javascript

I've written a fairly simple script that will take elements (in this case, <p> elements are the main concern) and type their contents out like a typewriter, one by one.
The problem is that as it types, when it reaches the edge of the container mid-word, it reflows the text and jumps to the next line (like word wrap in any text editor).
This is, of course, expected behavior; however, I would like to pre-format the text so that this does not happen.
I figure that inserting <br> before the word that will wrap would be the best solution, but I'm not quite sure what the best way to go about doing that is that supports all font sizes and container widths, while also keeping any HTML tags intact.
I figure something involving a hidden <span> element, adding text to it gradually and checking its width against the container width might be on the right track, but I'm not quite sure how to actually put this together. Any help or suggestions on better methods would be appreciated.
Edit:
I've managed to write something that sort of works using jQuery, although it's very sloppy, and more importantly, sometimes it seems to skip words, and I can't figure out why. #content is the name of the container, and #ruler is the name of the hidden <span>. I'm sure there's a much better way to do this.
function formatText(html) {
var textArray = html.split(" ");
var assembledLine = "";
var finalArray = new Array();
var lastI = 0;
var firstLine = true;
for(i = 0; i <= textArray.length; i++) {
assembledLine = assembledLine + " " + textArray[i];
$('#ruler').html(assembledLine);
var lineWidth = $('#ruler').width();
if ((lineWidth >= $('#content').width()) || (i == textArray.length)) {
if (firstLine) { var tempArray = textArray.slice(lastI, i); }
else { var tempArray = textArray.slice(lastI+1, i); }
var finalLine = tempArray.join(" ");
finalArray.push(finalLine);
assembledLine = "";
if (lineWidth > $('#content').width()) { i = i-1; }
lastI = i;
firstLine = false;
}
}
return finalArray.join("<br>");
}

You could use the pre tag: Which displays pre-formatted text, or you could put the content into a div tag, set a fixed width, and script based upon that.

The best way (IMO) would be to add the whole word, but have the un-"typed" letters invisible. E.g:
H<span style="visibility: hidden;">ello</span>
He<span style="visibility: hidden;">llo</span>
Hel<span style="visibility: hidden;">lo</span>
Hell<span style="visibility: hidden;">o</span>
Hello
To make it easier, give the span a name, and delete it from the DOM each time.

A possible approach is to set p display inline (because default display-block will make p to consume all width even if it has just 1 character) and then as you 'type' check the element width.
Set a tolerance in px (25px for example) and once p's width reaches total available width minus width tolerance you insert <br />
I think this should work...

After playing with the code I edited into the question, I managed to get it working decently.
Code:
function formatText(html) {
var textArray = html.split(" ");
var assembledLine = "";
var finalArray = new Array();
var lastI = 0;
var firstLine = true;
for(i = 0; i <= textArray.length; i++) {
assembledLine = assembledLine + " " + textArray[i];
$('#ruler').html(assembledLine);
var lineWidth = $('#ruler').width();
if ((lineWidth >= $('#content').width()) || (i == textArray.length)) {
if (firstLine) { var tempArray = textArray.slice(lastI, i); }
else { var tempArray = textArray.slice(lastI+1, i); }
var finalLine = tempArray.join(" ");
finalArray.push(finalLine);
assembledLine = "";
if (lineWidth >= $('#content').width()) { i = i-1; }
lastI = i;
firstLine = false;
}
}
return finalArray.join("<br>");
}
Not perfect, but it'll do. Thanks, everyone.

Related

Can I Use Google Apps Script to Get the List Preset From One Paragraph and Apply it to Another in Google Slides

I'm trying to write some code in apps script to pull text from a text box in google slides and split it by paragraphs into new text boxes and I'm hitting a snag when I get to preserving lists. I haven't been successful in finding a simple way to get the listPreset from the paragraphs in the original text box to the paragraphs in their own text boxes.
I was able to use ListStyle() methods getGlyph() and isInList() successfully but unless I make a dictionary of which listPreset each glyph is in, I don't seem to able to retrieve the listPreset with any of the listed methods which I would need in order to fill into applyListPreset().
I'd love a getListPreset() function so I could just nab that from the original list. Is there something like that I'm missing?
Here is an example "before" condition.
Here is an example of the expected "after" condition.
Here is an example of what the "after" condition is when I am unable to duplicate the listStyle from the original text box into the new text boxes.
In the following example I copy the text from the original textbox and gather various attributes from it. I attempt to input the listStyle() information into the applyListPreset() method even though I know that's wrong. I just don't know how else to get the List Preset from the original text box.
FYI I've already had a bit of help with this already so it's not all my work.
function myFunction() { // get slides in the presentation
var slides = SlidesApp.getActivePresentation().getSlides();
var numberOfLogs = 5;
for (let slide of slides) { // get the objects on each slide
splitParagraphs(slide);
}
}
function splitParagraphs(slide){
slideShapes = slide.getShapes();
for(let shape of slideShapes){
var shapetype = shape.getShapeType();
if (shapetype == "TEXT_BOX"){ // checks to see if the object is a text box
createSplitShapes(shape, slide);
shape.remove();
}
}
}
function createSplitShapes(shape, slide){
var paragraphs = shape.getText().getParagraphs();
var oldHeight = shape.getHeight();
var width = shape.getWidth();
var newShapeHeight = oldHeight / paragraphs.length;
for (let [index, paragraph] of paragraphs.entries() ){ // make a textbox for each paragraph distributed vertically over the original textbox
createParagraphShapes(shape, index, paragraph, newShapeHeight, width, slide);
}
}
function createParagraphShapes(shape, index, paragraph, shapeheight, width, slide){
var text = paragraph.getRange();
var list = text.getListStyle().isInList();
var style = text.getListStyle();
var glyph = text.getListStyle().getGlyph();
var rawText = text.asString();
var textStyle = text.getTextStyle();
var fontsize = textStyle.getFontSize();
var fontfamily = textStyle.getFontFamily();
var fontweight = textStyle.getFontWeight();
var paragraphStyle = text.getParagraphStyle();
var alignment = paragraphStyle.getParagraphAlignment();
var lineSpacing = paragraphStyle.getLineSpacing();
var indent = paragraphStyle.getIndentStart();
var shapetop = shapeheight * index + shape.getTop();
if ( ! isBlank(rawText) ) {
var t = slide.insertTextBox(rawText);
t.setLeft(shape.getLeft());
t.setTop(shapetop);
t.setWidth(width);
var newTextStyle = t.getText().getTextStyle();
newTextStyle.setFontSize(fontsize);
newTextStyle.setFontFamilyAndWeight(fontfamily, fontweight);
var newParagraphStyle = t.getText().getParagraphStyle();
newParagraphStyle.setParagraphAlignment(alignment);
newParagraphStyle.setLineSpacing(lineSpacing);
newParagraphStyle.setIndentStart(indent);
if (list = true) {
t.getText().getListStyle().applyListPreset(style);
}
}
}
function paragraphLogs(paragraph, numberOfLogs){
if(numberOfLogs > 0){
console.log("\tWhat's in paragraphs?: ");
for (var k = 0; k < paragraph.length; k++){
console.log("\t\t" + ( paragraphs[k].getRange().asString() ) );
}
}
}
function isBlank(str){
return (!str || str.trim().length === 0);
}

Illustrator Script All Text Properties

Currently working a script to make ALL the texts of a document with some specifics. Glad to say it's already functioning but not completely because it's just working on some of the textframes and not all of them.
The intention is to make the changes on every texts on the active document (point, area, frames, etc) and unfortunately, even when it works on some text frame, not all characters are affected because some special ones like - or + don't get the properties of the other normal characters.
Made some research and do believe I should be working with parentStory maybe? But I didn't figure out how to implement.
Here is my code:
var document = app.activeDocument;
var allTexts = document.textFrames[0];
for (i=0; i<allTexts.words.length; i++) {
allTexts.words[i].characterAttributes.autoLeading = true;
allTexts.words[i].characterAttributes.tracking = 250;
allTexts.words[i].characterAttributes.kerningMethod = AutoKernType.AUTO;
allTexts.words[i].characterAttributes.underline = false;
allTexts.words[i].characterAttributes.strikeThrough = false;
allTexts.words[i].characterAttributes.capitalization = FontCapsOption.NORMALCAPS;
allTexts.words[i].characterAttributes.language = LanguageType.UKENGLISH;
allTexts.words[i].characterAttributes.horizontalScale = 100;
allTexts.words[i].characterAttributes.verticalScale = 100;
allTexts.words[i].characterAttributes.baselineShift = 0;
allTexts.words[i].characterAttributes.rotation = 0;
allTexts.words[i].characterAttributes.baselinePosition = FontBaselineOption.NORMALBASELINE;
allTexts.words[i].characterAttributes.baselineDirection = BaselineDirectionType.Standard;
allTexts.words[i].characterAttributes.alternateGlyphs = AlternateGlyphsForm.DEFAULTFORM;
allTexts.words[i].characterAttributes.antialias = TextAntialias.SHARP;
}
already got helped in a different source by adding textRange to it.
var thisTextFrame;
for(var i = 0; i < app.activeDocument.textFrames.length; i++){
thisTextFrame = app.activeDocument.textFrames[i]
thisTextFrame.textRange.characterAttributes.tracking = 250;
// ...
}

display image on another window when click select button using Javascript

Hello there I'm new to the world of Javascript, and I really really need help for school project.
What I want to do, is to display on another windows not only the text with the price, but also the image itself. I tried to add the id on the image and added it to function. Anybody please?
I have this code here in the body element along with other images:
Nike 1
price:$110.99
Size: 9
10
11
<input type= submit value=submit onclick="a()">
This is my script function:
<script>
function a(){
var size ="";
var price = 0;
if(document.getElementById('nike1').checked)
{
price=document.getElementById("nike1").value;
var x =document.getElementById("myimg").src;
}
else if(document.getElementById('nike2').checked)
{
price=document.getElementById('nike2').value;
}
var inputs = document.getElementsByName('r1');
for(var i =0; i<inputs.lenght; i++){
if(inputs[i].checked){
size = inputs[i].value;
}
}
var inputs1 = document.getElementsByName('r2');
for(var i =0; i <inputs1.lenghts;i++){
if(inputs1[i].checked){
size=inputs1[i].value;
}
}
var myWindow = window.open("","MsgWindow","width=200,height=400");
myWindow.document.write("<p><h1>Order Detail</h1> Your ordered Nike shoes<br/> Size:"+size + "<br/>Price:S"+ price + "</p>");
}
Please indent you code for readability. Here it is, a little bit cleaned and with error fixes.
function a() {
var size, price;
if (document.getElementById('nike1').checked) {
price = document.getElementById("nike1").value;
var x = document.getElementById("myimg").src;
} else if (document.getElementById('nike2').checked) {
price = document.getElementById('nike2').value;
}
var inputs = document.getElementsByName('r1');
for (var i = 0; i < inputs.length; i++) {
if (inputs[i].checked) {
size = inputs[i].value;
}
}
var inputs1 = document.getElementsByName('r2');
for (var i = 0; i < inputs1.length; i++) {
if (inputs1[i].checked) {
size = inputs1[i].value;
}
}
var myWindow = window.open("", "MsgWindow", "width=200,height=400");
myWindow.document.write('<p><h1>Order Detail</h1> Your ordered Nike shoes<br/>'+
'<img src="path/to/your/image.png" alt="Nike shoes"></img>'+
'<p>Size: ' + size + '<br/>Price: ' + price + '</p>');
}
To solve your problem, write an img element with a src attribute. Change "path/to/your/image.png" with the real path to the image you want to display.
There were also errors in your code. Your two for loops contained a wrong value: your wrote lenght in the first and lenghts in the second. I fixed it. I tested it and it works. This fiddle is fairly simple and incomplete but shows you the result.

Cannot manage to use innerHTML

I'm new to Stack Overflow and also in JavaScript. So, first of all, hello to everyone and thanks in advance for your help.
I'm using Incomedia Website X5 Evolution to create a website. On one of the page, I want to populate a table with data from a server. So, I've created a table and insert in each cell this HTML code:
<!--#0000,0000-->
Values are representing the row and the column. I managed to write a javascript to change the value of each cell. But when I want to replace the content of the HTML pahe using innerHTML, it does not work. Nevertheless, everything seems correct as the old and the new html content is the same. Even if I just use again the original variable, it still doesn't work.
Could you tell me where is the problem please ?
Here the javascript code:
<script>
var i;
var div = document.getElementById('imTableObject_1');
div = document.getElementsByTagName('table');
var htmlContent = div[0].innerHTML;
var newHtmlContent = div[0].innerHTML;
var test = div[0].innerHTML;
var row,col;
//I can't understand why the scrip stop running at this line. I didn't change anything...
div[0].innerHTML = newHtmlContent ;
for (i=htmlContent.length - 5; i > -1; i--) {
if(htmlContent.charAt(i)=='#') {
//alert(i);
//alert(htmlContent.substring(i+6,i+10));
row = parseInt(htmlContent.substring(i+1,i+5));
col = parseInt(htmlContent.substring(i+6,i+10));
newHtmlContent = insertText(row,col,newHtmlContent,i);
};
};
alert(div[0].innerHTML);
alert(newHtmlContent );
//This does not work
div[0].innerHTML = newHtmlContent ;
alert("Done !");
function insertText (row, col, text, index) {
var length;
var newText;
length = getTextLength (text,index + 13);
//alert(htmlContent.substring(index+13,index+13+length));
newText = text.substring(0,index+13);
newText += "Titi too !";
newText += text.substring(index+13+length,text.length);
//alert(newText);
return newText ;
}
function getTextLength (text,startIndex) {
var i = 0;
for(i = startIndex ; i < text.length ; i++) {
//alert(text.substring(i,i+7));
if(text.substring(i,i+7) == "</span>") {
return i - startIndex ;
};
};
return -1;
}
</script>
You set:
var newHtmlContent = div[0].innerHTML;
And then:
div[0].innerHTML = newHtmlContent ;
You're setting its content to what its content already was. Hence, no change occurs.
Change the 3rd row to
div = document.getElementsByTagName('td');
to look for <td> tags instead of <table> tags. <table>s can't directly store text data so I guess their innerHTML doesn't work as expected either.
I managed to get it working here: http://jsfiddle.net/mgabor/ZMaW6/1/

fading a paragraph in word by word using jquery?

<p class="example">i want to split this paragraph into
words and fade them in one by one</p>
the jquery/js:
$(document).ready(function() {
var $txt = $(".example")
,$words = $txt.text()
,$splitWords = $words.split(" ");
$txt.hide();
for(i = 0; i < $splitWords.length; i++){
// i want fade in each $splitWords[i]
//$splitWords[i].fadeIn(.... - i tried this doesnt work
}
});
im trying to split the paragraph into words, and fade them in one by one, thier might be an easier way to do this without splitting the words, please shed some light on this. thanks
Text by itself can't have an opacity, therefore you must wrap the text with an element that can have opacity (such as a span). You can then fade in those spans.
Try this:
http://jsfiddle.net/6czap/
var $el = $(".example:first"), text = $el.text(),
words = text.split(" "), html = "";
for (var i = 0; i < words.length; i++) {
html += "<span>" + words[i] + " </span>";
}
$el.html(html).children().hide().each(function(i){
$(this).delay(i*500).fadeIn(700);
});
Update for benekastah: http://jsfiddle.net/6czap/3/
var $el = $(".example:first"), text = $.trim($el.text()),
words = text.split(" "), html = "";
for (var i = 0; i < words.length; i++) {
html += "<span>" + words[i] + ((i+1) === words.length ? "" : " ") + "</span>";
};
$el.html(html).children().hide().each(function(i){
$(this).delay(i*200).fadeIn(700);
});
$el.find("span").promise().done(function(){
$el.text(function(i, text){
return $.trim(text);
});
});
You will need to fade in elements, text nodes can't have a opacity.
See demo at jsfiddle.net
var p = $("p.example").hide(); // possible flash! You should add some script
// to the <head> that writes a stylesheet
// to hide them right from the start
(function oneParagraph(i) {
if (p.length <= i)
return;
var cur = p.eq(i),
words = cur.text().split(/\s/),
span = $("<span>"),
before = document.createTextNode("");
cur.empty().show().append(before, span);
(function oneWord(j) {
if (j < words.length) {
span.hide().text(words[j]).fadeIn(200, function() {
span.empty();
before.data += words[j]+" ";
oneWord(j+1);
});
} else {
span.remove();
before.data = words.join(" ");
setTimeout(function(){
oneParagraph(i+1);
}, 500);
}
})(0);
})(0);
If you need only one paragraph, you can leave out all the things beloning to the oneParagraph function - just make cur the selected element.
If you want to have a smoother animation, you'd need to animate multiple words at the same time (demo), or dont fade but append letterwise like in here. Alternatively you could make the fading-time dependent on the length of the current word.
You'll have a couple problems with the suggestions mentioned so far.
First, splitting and then hiding the text in Javascript will result in a Flash of Unstyled Content. Second, the number of reflows is going to be pretty bad for long text.
Rather than hiding the text, consider setting the foreground and background to the same color, and then changing it back.

Categories