How to split HTML content into virtual pages - javascript

I'm using quilljs editor that returns html editor.getContent()
I want to generate a pages preview by split the HTML contents based on the height of the page,
I first tried this solution https://stackoverflow.com/a/54984668/7511165
content is overflowing if its a long paragraph, so I tried to split the content when its larger than the page, But I could not calculate the the splitting index based on the offsceeenDiv,
So I tried the CSS column
https://stackoverflow.com/a/56141110/7511165
This idea is cloning all the content on every page, so it's good for performance at all
Any solution?
note:
The content is HTML (h1, h2, p, pre), not text
update 1
Code I tried with CSS column method :
let newContent =`<h1 class="cht chapterTitleView-${id}">${chapterTitle}</h1>${chapterContent}`
const pageWidth = getComputedStyle(document.documentElement)
.getPropertyValue('--page-width') // 370;
.replace('px', '');
const content = document.getElementById('content');
content.style.display = 'block';
content.innerHTML = newContent;
const totalWidth = content.scrollWidth;
const totalPages = totalWidth / pageWidth;
const html = content.innerHTML;
const container = document.querySelector(`#chapter-preview-${id} .chapterPreviewContainer`)
// 7. create the pages dom
for (let p = 0; p < totalPages; p++) {
let pageWrapper = document.querySelector("#temp-chapter-page").innerHTML.replaceTemplateVars({CID: id, ID:p , PAGE: p + 1});
container.insertAdjacentHTML("beforeend", pageWrapper);
const page = container.querySelector(`#chapter-page-${id}-${p}`);
page.innerHTML = html;
page.style.cssText = `width: ${totalWidth}px; transform: translateX(-${p * pageWidth}px);`;
}

Related

using the DOM to output different sized pyramids to a webpage

Continuing from above, whenever the button is clicked, the pyramid of whatever height the user input into the text box does print. But, if you click the button again, it prints a whole new pyramid without removing the old one. What I need to know is how to add a line of code to the beginning of the drawPyramid Function that clears the old content before creating a new Pyramid in Vanilla Javascript, HTML, and/or CSS. I do have a lot of notes written for the code but that's mostly for me because it's just the way that I learn best is to overexplain everything that's happening in the code which I also would appreciate in responses if possible.
Here is the JavaScript portion of what I have:
function determineHeightAndThenDrawPyramid() {
//Sets height == to input value typed by user
let height = document.getElementById("height").value;
//Creates btn that 'onclick' calls the printPyramid function
let btn = document.getElementById("MyBtn").addEventListener("click", printPyramid(height));
}
//Building the pyramid
function printPyramid(height) {
//Figure out how to clear the old output??
console.clear();
let numBricks = 0;
let numSpaces = 0;
for (let row = 0; row < height; row++) {
let layer = "";
//figure out number of bricks and spaces
numBricks = row + 2;
numSpaces = height - row - 1;
for (let i = 0; i < numSpaces; i++) {
layer += ".";
}
for (let i = 0; i < numBricks; i++) {
layer += "#";
}
//Prints layer to console
console.log(layer);
//Prints layer to DOM
//Creates new paragraph element
let para = document.createElement("p");
//Creates a text node from layer variable
let rowStr = document.createTextNode(layer);
//Para becomes parent of rowStr
para.appendChild(rowStr);
//Grabs pyramid ID from HTML
let element = document.getElementById("pyramid");
//Element becomes parent of para
element.appendChild(para);
//Summation:
//element/para/rowStr.
//Element == ID of pyramid.
//Para(New paragraph tag) == rowStr.
//rowStr == layer variable == string to build pyramid
}
}
<input id="height" />
<button id="MyBtn">Go!</button>
<div id="pyramid">--</div>
Thank you for any advice!
There are some important errors:
When you set the event listener, you need to use a function callback, not a called function
Use append instead of appendChild
You need to get the height just after clicking the button
Then, to remove the previous pyramid before appending the new one, just remove the contents of its container.
Finally (or, better said, initially), the function assigning the event listener needs to be called only once.
Advice: use const instead of let whenever possible.
The code snippet works.
function assignListener() {
//Sets height to the input DOM element
const height = document.getElementById("height");
// Save the button DOM element into btn,
// not the result from addEventListener
const btn = document.getElementById("MyBtn");
// Use a lambda callback which will call printPyramid
// with the current value (when user clicks) of the input `height`
btn.addEventListener("click", () => printPyramid(height.value));
}
//Building the pyramid
function printPyramid(height) {
//Figure out how to clear the old output??
console.clear();
let numBricks = 0;
let numSpaces = 0;
// ==> Find and empty pyramid before starting
//Grabs pyramid ID from HTML
const element = document.getElementById("pyramid");
// ==> Empty `element` contents
element.innerHTML = '';
for (let row = 0; row < height; row++) {
let layer = "";
//figure out number of bricks and spaces
numBricks = row + 2;
numSpaces = height - row - 1;
for (let i = 0; i < numSpaces; i++) {
layer += ".";
}
for (let i = 0; i < numBricks; i++) {
layer += "#";
}
//Prints layer to console
console.log(layer);
//Prints layer to DOM
//Creates new paragraph element
const para = document.createElement("p");
// Creates a text node from layer variable
const rowStr = document.createTextNode(layer);
// Para becomes parent of rowStr
// ==> Use `append` instead of `appendChild`
para.append(rowStr);
// Element becomes parent only of para
// ==> Use `append` instead of `appendChild`
element.append(para);
//Summation:
//element/para/rowStr.
//Element == ID of pyramid.
//Para(New paragraph tag) == rowStr.
//rowStr == layer variable == string to build pyramid
}
}
// ==> Call the event listener assigner only once
assignListener();
<input id="height" />
<button id="MyBtn">Go!</button>
<div id="pyramid">--</div>

why is that using a document fragment can improve performance?

See the codes below. Insert nodes into the page.
var el;
var i = 0;
var body = document.querySelector('body');
var fragment = document.createDocumentFragment();
while (i < 10000) {
el = document.createElement('p');
el.innerText = 'This is item number ' + i;
fragment.appendChild(el);
i++;
}
body.appendChild(fragment);
vs
var el;
var i = 0;
var body = document.querySelector('body');
while (i < 10000) {
el = document.createElement('p');
el.innerText = 'This is item number ' + i;
body.appendChild(el);
i++;
}
As I know, JS excution would block the rendering process. The rendering process should begin right after the JS excution. I think all the newly inserted nodes were rendered at once even though the container appendChilds each node.
How does using a fragment avoid reflows and improve performance?
It's not a appendChild-render-appendChild-render process.

Bootstrap panels not displaying body text

For some reason my bootstrap panels won't show the body text. I have set up all my elements via DOM Manipulation.
My panel header text displays properly, however my body text doesn't show up.
I also noticed that the bootstrap panel body content does not have any elements, just lines of text.
I have tried to add text elements to it but so far nothing has been working. Here is my code:
JS
var searchButton = document.getElementById('search-button');
searchButton.addEventListener('click', function() {
var term = document.getElementById('term').value;
var matched = [];
for (var i = 0; i < hotelRooms.length; i++) {
if (hotelRooms[i].hotel.indexOf(term) !== -1) {
matched.push(hotelRooms[i]);
}
}
for (var i = 0; i < matched.length; i++) {
var roomResults = document.createElement('div');
roomResults.setAttribute('id', 'results');
roomResults.setAttribute('class', 'result-style');
var resultsArea = document.getElementById('results-area');
console.log(matched[i]);
var panelDefault = document.createElement('div');
panelDefault.setAttribute('class', 'panel-default');
var panelHeading = document.createElement('div');
panelHeading.setAttribute('class', 'panel-heading');
var panelBody = document.createElement('div');
panelBody.setAttribute('class', 'panel-body');
var name = document.createElement('h3'); // Hotel Name
name.setAttribute('class', 'hotel-name');
name.textContent = matched[i].hotel;
var price = document.createElement('div'); // Room Price
price.setAttribute('class', 'room-price');
price.textContent = matched[i].price;
roomResults.appendChild(panelDefault);
panelDefault.appendChild(panelHeading);
panelHeading.appendChild(name);
panelBody.appendChild(price);
resultsArea.appendChild(roomResults);
}
});
You are never appending panelBody to panelDefault.
....
roomResults.appendChild(panelDefault);
panelDefault.appendChild(panelHeading);
panelHeading.appendChild(name);
panelBody.appendChild(price);
panelDefault.appendChild(panelBody);
....
You have just created a div, you need to append it to the body tag document.body.appendChild(size)
var size = document.createElement('div');
size.setAttribute('class', 'room-size');
size.textContent ="hello"
document.body.appendChild(size)

How can I make an virtual scroll with angularJS?

I'm trying to make a directive that I can do a virtual scroll, so as the user scrolls the table, the table remove "old" views and add "new" views, kind like of collection repeat but I've been failing, I think I didn't understand the math behind it, can someone help me?
this is my directive code:
BaseModule.directive('myScroll', function() {
return {
restrict:"A",
scope:{
rows:"=",
headers:"="
},
link: function(scope,el) {
var scrollTop = 0;
var scrollLeft = 0;
angular.element(el).on('scroll',function(){
scrollTop = $(el).scrollTop();
scrollLeft = $(el).scrollLeft();
$(el).parent().find(".header").scrollLeft(scrollLeft);
var height = $(el).height();
var numberOfRows = height/23;
var initialRow = scrollTop/23;
var html = "";
for(i=0; i<numberOfRows;i++){
var row = scope.rows[i+initialRow];
html = html + addRow(row,i+initialRow);
}
$(el).find('.tbody-scroll').append(html);
});
scope.$watch('rows',function(rows){
var height = $(el).height();
var numberOfRows = height/23;
var initialRow = scrollTop/23;
var html = "";
for(i=0; i<numberOfRows;i++){
var row = rows[i+initialRow];
html = html + addRow(row,i+initialRow);
}
$(el).find('.tbody-scroll').append(html);
});
var addRow = function(row,index){
var html = "";
var pos = 0;
var totalWidth = 0;
angular.forEach(row,function(col){
var width = scope.headers[pos].width;
totalWidth = totalWidth + width;
html = html + "<span style='width:"+width+"px'>"+col.value+"</span>";
pos++;
});
html = "<div class='row' style='top:"+index*23+"px;width:"+totalWidth+"px;'>"+html;
html = html + "</div>";
return html;
};
}
};
});
<!-- my directive .html -->
<div class="mTable">
<div class="header" ng-style="headerWidth(headers())">
<span ng-repeat="header in headers()" ng-style="widthStyle(header)">
{{::header.label}}
</span>
</div>
<div class="tbody-container" my-scroll headers="headers()" rows="rows()">
<div class="tbody-scroll" ng-style="scrollHeight(rows(),headers())"></div>
</div>
</div>
Giving a full answer with code might require a bit too much of an effort
This library implements virtual scroll on ng-repeat https://github.com/stackfull/angular-virtual-scroll in the description there's also an article on how to implement this feature on your own.
The basic concept is to make two divs, one above and one below the list, which size is determined by the number of elements inside the list (this approach has a limitation since list elements must either have all the same height or their height must be fixed), then you delete elements as they disappear from the viewport and resize the divs according to the number of elements not currently rendered and your position on the list
Not a direct answer to your question, but an alternative: You may want to look at the ui-scroll directive which is a replacement for ng-repeat and has a similar function.
Example
in your controller
$scope.movieDataSource = {
get : function (index, count, callback) {
var i, items = [$scope.userMovies], item;
var min = 1;
var max = 1000;
for (i=index ; i<index + count ; i++) {
if(i < min || i > max) {
continue;
}
item = {
title: $scope.userMovies.title,
imageURL: $scope.userMovies.poster_path
};
items.push (item);
}
callback (items);
}
}
And in your view:
<div ui-scroll="item in movieDataSource">
{{item.title}}
</div>

Finding what the clientHeight will be after layout

I have a div with a series of spans under it (this.content). The div had "word-wrap: break-word", "min-width: 400px" and "white-space: normal" and "overflow-x: auto:".
I need to calculate the number of lines the text will wrap to. I was hoping to go this route:
var hiddenParent = document.createElement("div");
hiddenParent.style.width = width + "px";
hiddenParent.style.height = "20000px";
hiddenParent.style.display = "block";
hiddenParent.style.wordWrap = "break-word";
hiddenParent.style.whiteSpace = "normal";
hiddenParent.style.overflowX = "auto";
hiddenParent.style.maxWidth = hiddenParent.style.width;
var newElement = TreeDataItem.HtmlElementSource.createElement("div");
newElement.style.maxWidth = hiddenParent.style.maxWidth;
hiddenParent.appendChild(newElement);
newElement.innerHTML = this.content;
rows newElement.style.clientHeight/charHeight;
But the clientHeight doesn't get calculated. I don't want it rendered so I was putting it under a hiddentElement.
I'm missing something obvious or I can't do this. Help!

Categories

Resources