Copy half of an HTML child nodes array - javascript

I have an HTML element (let's say a division) that contains a set titles (h2) and paragraphs (p) with other element inside (some links for example, a).
My goal is to cut my HTML element in 2 same size element.
Constrains : paragraphs, titles, and block should not be cut
My idea asbout this was to browse inside the child nodes list and copy childNodes of each paragraphs.
Each time I copy a paragraph, i compute the size in ordre to know if I reached the half size of the division.
Here is some code to explain it:
var elm = document.getElementById('article_colonnes'); // I want to cut this into 2 parts
var paragraphesNumber = paragraphes.length;
var halfSize = elm.innerHTML.length / 2 ;
var col1 = document.getElementById('col1');
var col2 = document.getElementById('col2');
var i=0;
do {
var node = createNodeFromParagraphe(paragraphes[i]);
if(node) {
col1.appendChild(node);
// update the size of the 1st column by updating inner HTML
col1String = col1String + paragraphes[i].innerHTML;
}
i++;
// compute the size of the 1st column
col1Size = col1String.length;
}
while(col1Size < halfSize || i < paragraphesNumber) ;
And I do the same for the 2nd column.
Thanks for your help.

If your trying to make an automatic two column layout; take a look at multicolumnlists

To get a true two column layout, you need the height of the paragraphs (not the characters in it). The problem here: The only way to get the height is to wait for the browser to layout the page unless you specify the width of every element. Otherwise, the user would see an ugly flicker.
So I suggest to try this:
Use a multi column layout which most browser support. If the browser is not IE <= 7, you're done.
For IE 7 and lower, set a fixed width for all elements. Wait for the browser to layout the page (it should be enough to run this code in body.onload or from a script tag at the end of the page). Put all elements into a div with the id "col1".
Use JavaScript to find the element at half the height of the the div "col1". (see here)
If this element is a heading, skip one element.
Move the next element (and all beyond) into the div "col2".
That should give you a longer col1 and a shorter col2.
If moving doesn't work, insert an invisible character after you identified the split point, get col1.innerHtml, split it at the split char and use the resulting two strings as innerHtml for col1 and col2.
If that flickers a lot, try to hide col1 and col2 while your script runs (display='None').

Related

Preserve DOM elements position while removing texts

I am looking for a solution where I can remove texts (or replace texts with some characters) in DOM where the position of all DOM elements remain same.
Background
My project capture full source code of web pages from sensitive web pages, however, those sensitive data does not matter and need to be removed prior to transmitting to the server. Captured source code will be later used to recreate what Administrator was seeing (without texts)
Example
Assume this is a page:
<div>Some text here
<input type="button" value="some other text" />
some more text
</div>
So it will be rendered like this by browser:
some text here [some other text]some more text
I need it to be like this:
------ ------ ------ [------- ------ ------]------- ------- ------
Current buggy approach
Currently, I get texts in DOM, count characters between each space, and replace those characters with a dash. unfortunately, it will render like this:
---- ---- --- [---- ----- ----]---- ---- ----
Which as you can see, the position of button and link is completely different from the original.
Purpose
The main purpose is to recreate DOM later on for UX purposes, but without any texts transmitted to a server that might contain sensitive information. Texts can be completely removed, replaced with any characters (I used - in this example), replaced with other texts such as "Lorem ipsum", as long as it is completely removed from source code while preserving the exact location of DOM.
It is used to record mouse click and mouse move positions (X, Y) and show them as a click/move heat-map.
Restrictions
I am not able to change font or codes on target web pages and each element and page might be using a different font for each element.
Ideas?
Looking for help if anyone can come up with an idea about this?
The issue here is that - have different character width than characters used in the real text.
I have thought of scrambling words in all sentences therefor preserve final total width of the text. however, someone might be able to reshuffle them back to original word and it is a security/privacy risk.
I have thought of replacing with multiple dashes based on each word size (and using it currently), but How to get the size of each word in it's specified DOM element? (as each DOM element might use different font, therefore different size for each character) and it could have big performance issue trying to create a hidden div next to each element with their texts just to try to calculate text width of it.
on parent element which have text on it, get computed style for font-size,font-family and letter-spacing and use it in a new div to detect that font's width for space. then put original text on that div and detect width of original text. then divide original text width to space width for that font to detect how many space need to be there to generate same width, and generate those spaces. Issue here is that on some pages that have too many texts, It will be an overkill to browser performance.
Your idea?
Try with this:
// Select 'div','a' and 'input' elements.
// you can add more elements or even select all '*'
$('div,a,input').each(function() {
var contents = $(this).contents();
if (contents.length > 0) {
if (contents.get(0).nodeType == Node.TEXT_NODE) {
// Remove text from children nodes
var elementText = $(this)
.clone() //clone the element
.children() //select all the children
.remove() //remove all the children
.end() //again go back to selected element
.text();
// Replace text
$(this).text(elementText.replace(/[a-zA-Z0-9]{1}/g, '-')).append(contents.slice(1));
}
}
// From input tags we will replace value
if($(this).is('input'))
$(this).val($(this).val().replace(/[a-zA-Z0-9]{1}/g, '-'));
});
Here is a JSFiddle Demo

How to set HTML Table column widths explicitly in JavaScript

I have hit a scaling problem with an HTML table, whilst manipulating it in JavaScript.
I have a JavaScript code blob that dynamically creates a table from a JSON array.
It fixes the header and allows the table body to scroll, and it works well.
The header is a separate table, and I strongly control cell widths to ensure that the header table's cells match up with the body table cells.
The body table cells all have their width explicitly set.
I iterate the rows in the table, and then interate the cells within that loop, and set each cells width.
This iteration is not scaling, as the rows grow to 100 and the columns get to 40 odd, I get complaints from Firefox that the script is hanging or unresponsive. It is unresponsive, because it is busy setting all the cell widths.
nz.dynatable.setTableColumnWidths = function (table, arrayColumnWidths) {
// Iterate the table and set width settings for each cell
for (var i = 0; i < table.rows.length; ++i) {
for (var j = 0; j < table.rows[i].cells.length; ++j) {
var width = arrayColumnWidths[j] || 0;
table.rows[i].cells[j].style.width = width.toString() + "px";
}
}
}
Q: Is it possible to to set cell widths for a table in one row and have all the other cells in the table fall into line with this? Is Firefox getting whacked because changing the cell widths is causing it to recalc the table size on each loop?
The full code chunk is in a public GitHub repo: https://github.com/Hiblet/DynaTable
Try this: I had have same problem and this is help me:
let tables = document.getElementsByClassName("table");
if(tables.length == 2){
let firstTableCol = tables[0].querySelectorAll('colgroup col');
let secondTableCol = tables[1].querySelectorAll('tr')[0].querySelectorAll('td');
if(firstTableCol.length == secondTableCol.length){
for(let index = 0; index < firstTableCol.length; index++){
firstTableCol[index].style.width = secondTableCol[index].offsetWidth + "px";
}
}else
console.log('cols count mismatch');
}else
console.log('count of tables are bigger than 2');
I tried a few ways to skin this cat and here were my findings. The final solution ended up being very clean and performant, with the caveats that I tested on Chrome, and not other browsers, and with moderately large, but not huge, tables. To summarize, use css such as to following to set the 5th column to width 70, and use javascript to modify that css:
table#my_table th:nth-child(5), table#my_table td:nth-child(5){ max-width:70px; min-width:70px;}
More details:
you need to set both min-width and max-width to avoid columns that you are NOT trying to resize from doing funky stuff in order to keep the table size constant
you can do this using element.style.xxxWidth, but a simple approach such as in the OP doesn't scale well, as the OP notes.
One way to deal with this is to only update the elements that are in a viewable area (let's call this the "viewable area approach"). You can do this by comparing the parent.getBoundingClientRect() results from the parent object and the object you are checking (see e.g. How to tell if a DOM element is visible in the current viewport?)
I was able to get this to work pretty well as long as calls to getBoundingClientRect() (which are expensive) were kept to a minimum (i.e. do it once per column, not per cell). Because I didn't want to maintain a cache of what cells were updated and what were not, after updating viewable cells, I would then update the non-viewable cells using an async function call. This way the UI appeared much more responsive even though it was still doing stuff in the background
However, anything involving direct changes to element.style.xxxWidth felt messy, especially after having to add the code to only update viewable elements. Furthermore, though performance was much better with that change, it still seemed possibly sub-optimal. So the final solution I ended up using was to do the following (let's call this the "dynamic stylesheet approach"):
assume each table will have a unique element ID
initialize the table by creating a new style (document.createElement('style')) for each column. The style that contain one rule that will only select that column for the table with that ID, e.g. for table with id "my_table", column 5 (cellIndex 4), to set width to 70:
table#my_table th:nth-child(5), table#my_table td:nth-child(5){ max-width:70px; min-width:70px;}
add the newly created styleElements to the table element (not the document), so that if the table is removed from the DOM, these extra styles go away also
to change a column width, the only thing that needs to change is the maxWidth / minWidth pieces of rule 0 of the related styleElement (note, you could also do this with a single stylesheet that has multiple rules, but I found it easier to use a separate stylesheet per column with one rule each).
Overall, using the dynamic stylesheet approach was the winner in my view for the following reasons:
it performs well with tables that are large enough to have performance issues under the "naive" approach described in the OP (though I haven't compared the performance of other approaches on very large tables)
it is clean: no element.style changes are required, and it doesn't clutter your basic document DOM as the relevant style elements are attached to the related table (need to test cross-browser)
if its performance isn't already optimized automatically by browsers, its design should make optimization straightforward by leaving the performance challenge up to the browser to handle

Creating a Page Break in HTML

I am creating a resume using html and some elements have multiple lines. For example, an education can have the institute, the date attended, and the degree received on separate lines. However, the institute, the date, and the degree are all part of one record.
I am trying to create a page break when I print using html. I only want the page break to be inserted if the number of lines in the next element is greater than the number of lines remaining on the 8.5 x 11 page. Using the example earlier, I want all of the lines in the education record all on one page. The data is being passed in, so the page breaks can vary from resume to resume.
I have found code to create the page break: #media print {footer {page-break-after: always;}}.
I have also found code that can get the line height of a div:
var element = document.getElementById('content');
document.defaultView.getComputedStyle(element,null).getPropertyValue("lineHeight");
I need to count the number of lines remaining on the 8x11 page and compare that height to the height of the next element. Instead of using page breaks, another option would be to just add enough empty lines to move the element down enough for it to be all on one page, but I still need to be able to count the remaining lines. I would appreciate any suggestions. Thanks.
EDIT: Here is my code so far:
var totalHeight = 1056;
var divHeight = document.getElementById('element').offsetHeight;
totalHeight = totalHeight - divHeight;
if(totalHeight < 0)
{
document.write("<style>");
document.write("#media print {#element {page-break-after: always;}}");
document.write("</style>");
}
However, when I print out the total height it only returns a number slightly over 200. Instead, the number should exceed 1056. Does the offsetHeight method only return the size of the text itself and not the spacing around it or is there another problem that could be causing the drastic difference? Thanks.
I think the trickiest part here is dealing with whatever margin the browser will put on the page when it goes to print. Not sure if you can control that. But if you can, try using in as your sizing unit instead of something like px or em. Then, if you calculate that you're going to go over 11in with what you've got, add your page break.
Here's a pseudo-code algorithm:
var totalHeight = page margin
foreach div:
totalHeight += calculated height in inches
if totalHeight > 11in:
insert page break
rinse and repeat for further pages

How to limit a <td> to only three lines?

I would like to achieve a unified look and feel across all my table rows.
When you look at my example below you can see that the note in the middle goes over 4 lines and thats not so pretty.
I was hoping to limit all <td> to 3 lines.
If there is more to be shown than three lines, then it should cut the content with ... [click for more] and put the content inside a collapseable element, so that when clicked on it it would show the whole content.
The latter shouldn't be a problem, but how do I limit the content to only three lines? Shall I count the characters to make the decision upon that? Is there a better strategy? I am using Django by the way,but I am happy to use javascript, jquery or any css magic instead to solve this.
Update:
The accepted answer is very good. However it comes with a caveat, which isn't easy to solve.
if you have a neighbouring td that already goes over three lines, while the current td is only two lines we will get an infinite while loop.
while($(this).innerHeight() / $(this).css('line-height').slice(0,-2) >= 3){ .. }
Since $(this).innerHeight() can't decrease because of the neighbouring cell holding the height up high. I think if it was possible to get the css of the current td and copy it across the content completely in a separate field, where neighbouring tds can't interfere, we would get the optimal solution.
Update 2:
As Assad mentioned, the solution is to put a div wrapper around the content of td and set the class on the div inside the td rather than on the td itself. It works flawlessly.
Assuming you are using jQuery, you could find all td elements that exceed a certain number of lines using:
$('td').filter(function(){
return $(this).innerHeight() / $(this).css('line-height').slice(0,-2) > 3; //more than 3 lines
});
You could then apply collapsible elements to these td elements.
Here is a demonstration (using paragraphs instead of tds): http://jsfiddle.net/jM4ZY/1/
Here is an example of cutting off content to fit 3 lines, then adding a more button: http://jsfiddle.net/jM4ZY/2/
As far as the edit is concerned, this is easily resolved by using an inner wrapper for your content; possibly a div element. You can then measure the height of this element, which is independent of the height of neighboring cells.
Another jQuery solution is described here
It is described how to change the Text by counting the number of letters in the displayed text. If you are unsure about the number of letters, or want to make it dependent of the text-length you can calculate it by using this snipped
$.fn.textWidth = function(){
var html_org = $(this).html();
var html_calc = '<span>' + html_org + '</span>';
$(this).html(html_calc);
var width = $(this).find('span:first').width();
$(this).html(html_org);
return width;
};
which I took from Calculating text width

Find location of line on screen

So I have HTML text being rendered in a browser (in this case an Android WebView). I want to find out what the (x,y) location in pixels of any given line of text is AFTER it is rendered. The working definition of line I am using is not just all the text contained in a <p> tag or that appears before a <br> tag. I mean a line as it would appear to the user.
I am open to any suggested method.
Is there any CSS property that you are able to find the number of lines in a div and their respective heights? That would provide a workable solution.
Thanks!!
You can't access a line individually. But, with some JavaScript, you can find the position of a line with a known index; here's a basic outline:
var p = document.getElementById("ptag"); //get the text container that contains your line
var nthline = 3; //the line for which you'd like to find the position
var lnheight = parseInt(window.getComputedStyle(p).lineHeight); //get the height of each line
var linepos = [p.offsetLeft, p.offsetTop + lnheight * (nthline - 1)]; //a [left, top] pair that represents the line's position
Note: This assumes the container doesn't have anything but text.
There is no standard way of doing that, you will have to refer to your imagination and invent some hack, right now I can think of two ideas for this:
Enclose each word within a span, like <span
class="word">word</span>, that could easily be done with regex or
string functions, later loop over each <span> reading its
position, add some calculation and you could find out how many
lines, where a line starts (word that incremented its top position
from last one) and when a line ends (last word of line + width of
that word).
Apply some style to first line using :first-line pseudoelement,
like
p:first-line{ background-color: white; /* same existent color so
no affecting display*/ }
later find in DOM what text that style was applied. This idea is not
as good and first one but maybe it can make you think of other ways.

Categories

Resources