Adding and deleting content between two points on a document - javascript

I currently have a document, with help from StackOverflow users already, that randomly generates questions, adds it to the end of a document, and then has the ability to delete all the questions posted. This is based on deleting everything under a horizontal rule.
Link to GDrive containing example document & code: LINK TO GDRIVE
You can also see what it currently does here: https://imgur.com/QVrOZKu
However, I now want to only want to add content after a certain point in the document, as well as only delete content between two certain points. You can see the two horizontal rules in an image below in which I want to add/delete
content.
The first horizontal rule in the picture is the third horizontal rule in the document.
Has anyone got any ideas how I can delete and add content between those two points? I've tried using child index's but failed miserably.

This is similar to Deleting all content down from the second horizontal line in a document so I adapt the solutions. First function deletes the paragraphs between the 3rd and 4th line. It counts horizontal lines as we loop through paragraphs. When the count reaches 3, start deleting subsequent paragraphs. When it exceeds 3, stop the loop.
function deleteFrom3to4() {
var body = DocumentApp.getActiveDocument().getBody();
body.appendParagraph('');
var para = body.getParagraphs();
var ruleCount = 0;
for (var i = 0; i < para.length - 1; i++) {
if (para[i].findElement(DocumentApp.ElementType.HORIZONTAL_RULE)) {
ruleCount++;
}
else if (ruleCount == 3) {
body.removeChild(para[i]);
}
if (ruleCount > 3) {
break;
}
}
}
And this one inserts a paragraph after the 3rd horizontal line. Again, it loops until the 3rd line is found; inserts a paragraph after it (expressed by body.getChildIndex(para[i]) + 1 child index) and stops.
function insertAfter3() {
var body = DocumentApp.getActiveDocument().getBody();
body.appendParagraph('');
var para = body.getParagraphs();
var ruleCount = 0;
for (var i = 0; i < para.length - 1; i++) {
if (para[i].findElement(DocumentApp.ElementType.HORIZONTAL_RULE)) {
ruleCount++;
}
if (ruleCount == 3) {
body.insertParagraph(body.getChildIndex(para[i]) + 1, "Here is a new paragraph");
break;
}
}
}

Related

Why do some spans not show text when being looped through?

I have a script that runs after the document is loaded that pulls all spans that have the class "Glossary-Word". Then I want to access the innerText of the span so that I can do some checking and add a hidden span to it.
This:
let glossaryWords = document.getElementsByClassName("Glossary-Word");
console.log(glossaryWords)
Shows the list of spans properly. When looping through these elements with:
for (let i = 0; i < glossaryWords.length; i++) {
console.log(glossaryWords[i])
}
it prints this:
A snippet of HTML for that sections is this:
<p class="Body-Text---Indented"><span class="Glossary-Word _idGenCharOverride-1">Y-bends</span> - for making branch line connections at 45°.</p>
<p class="Body-Text---Indented"><span class="Glossary-Word _idGenCharOverride-1">Return Bends</span> - for reversing direction of a pipe run.</p>
Why does the span around Return Bends show the innerText but the one for Y-Bends does not?
Here is the JavaScript and HTML code as a runnable snippet. However, in the snippet the code works as expected: both span elements are logged. What could be the difference between this snippet and my actual code?
let glossaryWords = document.getElementsByClassName("Glossary-Word");
console.log(glossaryWords)
for (let i = 0; i < glossaryWords.length; i++) {
console.log(glossaryWords[i])
}
<p class="Body-Text---Indented"><span class="Glossary-Word _idGenCharOverride-1">Y-bends</span> - for making branch line connections at 45°.</p>
<p class="Body-Text---Indented"><span class="Glossary-Word _idGenCharOverride-1">Return Bends</span> - for reversing direction of a pipe run.</p>
Would advise the following.
var words = [];
var w;
$(".Glossary-Word").each(function(i, el){
w = $(el).text().trim();
if(w.length){
words.push(w);
}
console.log(w);
});
console.log(words);
This will iterate the elements and get the Text content.
If you want a JavaScript solution:
let glossaryWords = document.getElementsByClassName("Glossary-Word");
console.log(glossaryWords)
for (let i = 0; i < glossaryWords.length; i++) {
console.log(glossaryWords[i].innerHTML);
}
References:
https://api.jquery.com/text/
https://api.jquery.com/each/
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/trim

Event listener fails to attach or remove in some contexts

I've created a script that attaches an event listener to a collection of pictures by default. When the elements are clicked, the listener swaps out for another event that changes the image source and pushes the id of the element to an array, and that reverses if you click on the swapped image (the source changes back and the last element in the array is removed). There is a button to "clear" all of the images by setting the default source and resetting the event listener, but it doesn't fire reliably and sometimes fires with a delay, causing only the last element in a series to be collected.
TL;DR: An event fires very unreliably for no discernible reason, and I'd love to know why this is happening and how I should fix it. The JSFiddle and published version are available below.
I've uploaded the current version here, and you can trip the error by selecting multiple tables, pressing "Cancel", and selecting those buttons again. Normally the error starts on the second or third pass.
I've also got a fiddle.
The layout will be a bit wacky on desktops and laptops since it was designed for phone screens, but you'll be able to see the issue and inspect the code so that shouldn't be a problem.
Code blocks:
Unset all the selected tables:
function tableClear() {
//alert(document.getElementsByClassName('eatPlace')[tableResEnum].src);
//numResTables = document.getElementsByClassName('eatPlace').src.length;
tableArrayLength = tableArray.length - 1;
for (tableResEnum = 0; tableResEnum <= tableArrayLength; tableResEnum += 1) {
tableSrces = tableArray[tableResEnum].src;
//alert(tableSrcTapped);
if (tableSrces === tableSrcTapped) {
tableArray[tableResEnum].removeEventListener('click', tableUntap);
tableArray[tableResEnum].addEventListener('click', tableTap);
tableArray[tableResEnum].src = window.location + 'resources/tableBase.svg';
} /*else if () {
}*/
}
resTableArray.splice(0, resTableArray.length);
}
Set/Unset a particular table:
tableUntap = function () {
$(this).unbind('click', tableUntap);
$(this).bind('click', tableTap);
this.setAttribute('src', 'resources/tableBase.svg');
resTableArray.shift(this);
};
tableTap = function () {
$(this).unbind('click', tableTap);
$(this).bind('click', tableUntap);
this.setAttribute('src', 'resources/tableTapped.svg');
resTableArray.push($(this).attr('id'));
};
Convert the elements within the 'eatPlace' class to an array:
$('.eatPlace').bind('click', tableTap);
tableList = document.getElementsByClassName('eatPlace');
tableArray = Array.prototype.slice.call(tableList);
Table instantiation:
for (tableEnum = 1; tableEnum <= tableNum; tableEnum += 1) {
tableImg = document.createElement('IMG');
tableImg.setAttribute('src', 'resources/tableBase.svg');
tableImg.setAttribute('id', 'table' + tableEnum);
tableImg.setAttribute('class', 'eatPlace');
tableImg.setAttribute('width', '15%');
tableImg.setAttribute('height', '15%');
$('#tableBox').append(tableImg, tableEnum);
if (tableEnum % 4 === 0) {
$('#tableBox').append("\n");
}
if (tableEnum === tableNum) {
$('#tableBox').append("<div id='subbles' class='ajaxButton'>Next</div>");
$('#tableBox').append("<div id='cazzles' class='ajaxButton'>Cancel</div>");
}
}
First mistake is in tapping and untapping tables.
When you push a Table to your array, your pushing its ID.
resTableArray.push($(this).attr('id'));
It will add id's of elements, depending on the order of user clicking the tables.
While untapping its always removing the first table.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/shift
resTableArray.shift(this);
So, when user clicks tables 1, 2, 3. And unclicks 3, the shift will remove table 1.
Lets fix this by removing untapped table
tableUntap = function () {
$(this).unbind('click', tableUntap);
$(this).bind('click', tableTap);
this.setAttribute('src', 'http://imgur.com/a7J8OJ5.png');
var elementID = $(this).attr('id');
var elementIndex = resTableArray.indexOf(elementID);
resTableArray.splice(elementIndex, 1);
};
So you were missing some tables after untapping.
Well lets fix tableClear,
You have a array with tapped tables, but you are searching in main array.
function tableClear() {
tableLen = resTableArray.length;
for (var i = 0; i < tableLen; i++) {
var idString = "#" + resTableArray[i];
var $element = $(idString);
$element.unbind('click', tableUntap);
$element.bind('click', tableTap);
$element.attr("src", 'http://imgur.com/a7J8OJ5.png');
}
resTableArray = [];
}
Im searching only tapped tables, and then just untap them and remove handlers.
fiddle: http://jsfiddle.net/r9ewnxzs/
Your mistake was to wrongly remove at untapping elements.

Javascript - Dynamic Expand/Collapse All

I have a jQuery Tree Report that I am trying to create 'expand/collapse all' buttons for.
The following two pieces of code are fired when the corresponding buttons are pressed and work great:
for (i = 1; i < 100; i++) {
var el = $('#dtt_2597807651112537_table tbody tr')[i - 1];
// store current level
var level = Number($(el).attr('dtt_level'));
// change icon
$(el).find('span.dtt_icon').removeClass('dtt_collapsed_span');
$(el).find('span.dtt_icon').addClass('dtt_expanded_span');
while ($($(el).next()).attr('dtt_level') != null) {
var el = $(el).next();
if ($(el).attr('dtt_level') == (level + 1)) {
// change display
el.removeClass('dtt_collapsed_tr');
el.addClass('dtt_expanded_tr');
} else if ($(el).attr('dtt_level') == level) {
break;
}
}
}
for (i = 1; i < 100; i++) {
// get related table row
var el = $('#dtt_2597807651112537_table tbody tr')[i - 1];
// store current level
var level = Number($(el).attr('dtt_level'));
// change icon
$(el).find('span.dtt_icon').addClass('dtt_collapsed_span');
$(el).find('span.dtt_icon').removeClass('dtt_expanded_span');
while ($($(el).next()).attr('dtt_level') != null) {
var el = $(el).next();
if ($(el).attr('dtt_level') > level) {
// change display
el.addClass('dtt_collapsed_tr');
el.removeClass('dtt_expanded_tr');
// change icon
$(el).find('span.dtt_icon').addClass('dtt_collapsed_span');
$(el).find('span.dtt_icon').removeClass('dtt_expanded_span');
} else if ($(el).attr('dtt_level') == level) {
break;
}
}
};
However, I was wondering if anyone had a nice way to:
1) Get the number of rows that need to be looped through - I just put 100 as a large number to prove my code worked and I don't want to just increase this to an even larger number.
2) Get the class name from the page source - The large number in "dtt_2597807651112537_table" is a report ID generated by the application. This is static for now but I want to eliminate any problems if it changes.
Thanks.
This is all wrong. Well, it's working against how jQuery works, in any case.
jQuery's credo is:
Select elements
Do stuff to them
Drop your loops. You don't need them.
For example. To toggle the icon on all span.dtt_icon in your document, do
var collapsed = true;
$("#dtt_2597807651112537_table span.dtt_icon") // select elements
.toggleClass('dtt_collapsed_span', collapsed) // do stuff to them
.toggleClass('dtt_expanded_span', !collapsed);
or, as a function that can both collapse and expand:
function toggleTree(tree, collapsed) {
$(tree).find("span.dtt_icon")
.toggleClass('dtt_collapsed_span', collapsed)
.toggleClass('dtt_expanded_span', !collapsed);
}
To collapse only the currently expanded ones...
$("#dtt_2597807651112537_table span.dtt_icon.dtt_expanded_span")
.toggleClass('dtt_collapsed_span', true)
.toggleClass('dtt_expanded_span', false);
and so on.
You can boil down your entire code into a few lines that way, and you don't need to write a single loop: Use smart element selection (via jQuery selectors and any of jQuerys find, filter and traversal functions) to single out the elements you want to manipulate and then manipulate them all at once in a single step.
To your second question. There are many ways, pick one:
use known page structure to determine the right table (e.g. $("div.main > table:first") or something to that effect)
use known table contents to determine the right table (e.g. $("table:has(span.dtt_icon)"))
use the table's other classes ($("table.treeReport") maybe?) or for example the table's ID with and a "starts-with" selector ($("table[id^=dtt_]")).
Again it's all about selecting your elements smartly. A dive into the jQuery API documentation, in this case the part about selectors, is recommended.

Variables within function is null

I have the following function:
function slideDown() {
//get the element to slide
sliding = document.getElementById('slideDiv1');
//add 1px to the height each time
sliding.style.height = parseInt(sliding.style.height)+1+'px';
t = setTimeout(slideDown,30);
if (sliding.style.height == "401px") {
clearTimeout(t);
}
}
which is called within this function:
function addDiv(nextImageSlide) {
//finds the src attribute of the image nested in the Li
elemChild = nextImageSlide.firstChild;
imageSrc = elemChild.getAttribute('src');
//loops and creates six divs which will be the slices. adds background property etc
for (i = 0, j = 0, k = 1; i< = 19; i++, j++, k++) {
var newDiv = document.createElement('div');
newDiv.setAttribute('class', 'new-div');
newDiv.id='slideDiv' + k;
newDiv.style.height = '1px';
newDiv.style.background = 'url(' + imageSrc +') scroll no-repeat - '+39.5 * j + 'px 0';
var a = document.getElementById('content');
a.appendChild(newDiv);
}
slideDown();
}
Which is called within another function that defines nextImageSlide. It later removes all the divs that it just made.
The idea is for an image gallery. When the user hits the next button, I want slices of the next image to slide down to show the next image. Those slices are then taken away and the new image revealed.
I would like something like this: http://workshop.rs/projects/jqfancytransitions/.
It's for an assignment so we have to write all the code ourself and this is the best way I can think to replicate it. The only problem is that I keep getting an error:
'sliding is null. sliding.style.height = parseInt(sliding.style.height)+1+'px';'
No matter what I do I can't get rid of it. The thing is if I define sliding as a totally different id, (for example I made a random little div outside of everything), it working.
This error shows when I try to access the divs, it just made that it throws a hissy fit.
Anyone see any errors in my code?
Hopefully this is just a typo while pasting into the site here, but:
car a = document.getElementById('content');
^---syntax error, which'll kill your entire script - var?

Implementing Show More - Show Less text on a page

I have a content oriented product. And at one point I display a list of available entities, with their full summary. Now, the said summary is the data entered by user using TinyMCE editor (i.e. it can contain HTML tags like img, p, span, ul, li, etc.). As summary can span a few hundred lines, I want to cleanly implement the Show More-Less feature, using javascript where I load the summary hidden partially by default, and show rest only when user click, 'Show More'.
It would be great to know, how you guys have or would have implemented it. I am thinking to limit the variety of markup entered by user and use regex to split the markup with a span link to load-more (much like facebook does it).
Note: I cannot split the text according to number of characters/words, as it can violate markup. I cannot hide the content by limiting height due to img tags (which loads later and can alter the height of containing div, and in-turn spoil your height calculations.)
http://henrik.nyh.se/2008/02/jquery-html-truncate A very neat implementation by this guy.
You can also see working demonstration here http://henrik.nyh.se/examples/truncator/
I would use the following function to get rid of unwanted html tags.
// Strips HTML and PHP tags from a string
// returns 1: 'Kevin <b>van</b> <i>Zonneveld</i>'
// example 2: strip_tags('<p>Kevin <img src="someimage.png" onmouseover="someFunction()">van <i>Zonneveld</i></p>', '<p>');
// returns 2: '<p>Kevin van Zonneveld</p>'
// example 3: strip_tags("<a href='http://kevin.vanzonneveld.net'>Kevin van Zonneveld</a>", "<a>");
// returns 3: '<a href='http://kevin.vanzonneveld.net'>Kevin van Zonneveld</a>'
// example 4: strip_tags('1 < 5 5 > 1');
// returns 4: '1 < 5 5 > 1'
function strip_tags (str, allowed_tags)
{
var key = '', allowed = false;
var matches = []; var allowed_array = [];
var allowed_tag = '';
var i = 0;
var k = '';
var html = '';
var replacer = function (search, replace, str) {
return str.split(search).join(replace);
};
// Build allowes tags associative array
if (allowed_tags) {
allowed_array = allowed_tags.match(/([a-zA-Z0-9]+)/gi);
}
str += '';
// Match tags
matches = str.match(/(<\/?[\S][^>]*>)/gi);
// Go through all HTML tags
for (key in matches) {
if (isNaN(key)) {
// IE7 Hack
continue;
}
// Save HTML tag
html = matches[key].toString();
// Is tag not in allowed list? Remove from str!
allowed = false;
// Go through all allowed tags
for (k in allowed_array) { // Init
allowed_tag = allowed_array[k];
i = -1;
if (i != 0) { i = html.toLowerCase().indexOf('<'+allowed_tag+'>');}
if (i != 0) { i = html.toLowerCase().indexOf('<'+allowed_tag+' ');}
if (i != 0) { i = html.toLowerCase().indexOf('</'+allowed_tag) ;}
// Determine
if (i == 0) { allowed = true;
break;
}
}
if (!allowed) {
str = replacer(html, "", str); // Custom replace. No regexing
}
}
return str;
}
Usage if content is the editor content. This will keep Spans:
var my_clean_content = strip_tags( content, 'span');
Personally, I'd do this with jQuery and use a plugin such as Text Constrain.
You can download it here, or view other similar plugins on the jQuery site.
The best jQuery plugin I've seen that implements this functionality is Expander. It's also got the advantage of having been around for several years and is still actively maintained as of the time this was written unlike most or all of the other solutions linked to in answers to this question.

Categories