I have some static HTML content. I need to format it using multiple columns, which are then presented as pages to the user. I use something quite simple which boils down to this:
bodyID = document.getElementsByTagName('body')[0];
bodyID.style.width = desiredWidth;
totalHeight = bodyID.offsetHeight;
pageCount = Math.ceil(totalHeight/desiredHeight);
bodyID.style.width = desiredWidth*pageCount;
bodyID.style.height = desiredHeight;
bodyID.style.webkitColumnGap = 0;
bodyID.style.webkitColumnCount = pageCount;
Now, my problem is that webKit honors the height attribute as it should and can create more columns than asked for if the content does not fit into pageCount number of columns.
I need to be able to get the number of columns afterwards to implement paging correctly. However the value of document.getElementsByTagName('body')[0].style.webkitColumnCount does not differ from pageCount even if there are more columns.
Any ideas, how to get the total number of rendered columns?
Thanks in advance.
The solution turned out to be much simpler than I thought. It was just a matter of getting the actual width of the page using bodyID.scrollWidth and then dividing by desiredWidth to get the actual number of pages.
Hope this helps somebody.
Related
I'm trying to 'gamify' my website to engage learners. I am still incredibly new to JavaScript so I apologize in advance. There are dozens of similar articles, but none that match my needs closely and I have tried and failed to implement a fix with other similar questions - sorry (I have tried hard!)
Across approximately 10 pages, inside the navbar, I want to insert a counter. The code below is using the ID 'score-counter' to do this, and I've tried using localStorage to retain the score of that learner across each page. I've managed to get it to work (somewhat) but instead of adding 10 to the original value, it's creating an entirely new counter and starting from scratch.
I want each button to only be clickable once (per page refresh) so I have tried to remove the eventListener after each button is clicked. I also want a straight-forward way to add more buttons to the counter system, so I've tried to keep it to the two simple classes 'increment-button' and 'first-click'.
<button class="first-click increment-button">Add 10 to the value!</button>
<div id="score-counter">0</div> <!-- DISPLAYS THE COUNT -->
let score = localStorage.getItem("score") || 0;
const incrementButtons = document.querySelectorAll(".increment-button");
const scoreCounter = document.getElementById("score-counter");
scoreCounter.textContent = score;
for (let button of incrementButtons) {
button.addEventListener("click", function(){
if(this.classList.contains("first-click")) {
score += 10;
localStorage.setItem("score", score);
this.classList.remove("first-click");
}
});
}
The output currently keeps doing this: 201010101010
It's saving the output successfully, but instead of adding a value of 10 to the original score, it's creating a whole new score and starting from 0 again. Does this make sense?
I've tried changing the JavaScript to count up from the value of 0
let score = localStorage.getItem("score") || 0;
I've also tried tweaking the score value
if(this.classList.contains("first-click")) {
score = scoreCounter + 10;
I've tried looking through various StackOverflow and other web resources for similar problems but have struggled to found any answer that fits what I'm trying to achieve. Can anyone offer any advice? Thanks in advance!
You need to convert the string you get from localStorage.getItem to a number. This can be done with the unary plus operator.
let score = +localStorage.getItem("score") || 0;
I've a datatable that export in excel.
Each html cell has some newlines (using br, p or div) so my export in excel need to strip all tags to be compatible with excel format, but keep newline.
After some research and help from datatables users, I get this good result:
https://jsfiddle.net/jx9hom27/4/
Now my problem is to set the height of each row to show all the text inside it.
Actually i've hardcode value of 60 for height, but it must be calculated based on content of row.
My idea was to get the size of splitData array: count is the number of lines.
The biggest value (for each row) must be multiplied for a costant (maybe 30) to get final result to apply to entire row.
The problem is that splitData variable is outside the function that set the style for excel....how can I solve this?
The code to set the height for a row is:
$('row:nth-child('+( firstExcelRow + rowLoop )+')', sheet).attr('ht', 60);
$('row:nth-child('+( firstExcelRow + rowLoop )+')', sheet).attr('customHeight', 1);
Answer myself. This is a working fiddle: https://jsfiddle.net/jx9hom27/5/
counting " inside the entire string, and divide by 2. Then for each row establish the max found and use it for the height
var countLines = ($('is t', this).text().match(/\"/g) || []).length / 2;
I've inherited the job of maintaining and developing an internal journaling system for registering inventory in tables on a local website. It is a website made in PHP, using jquery and handontable to list data from a MySQL database. All fields in the table are editable by the users.
Today the loading of data can be slow (10-15 seconds in the largest tables), which is mainly because of the loops used to populate the table and adjust the column sizes.
What do you think would be the best way to fix this issue? Should I reduce load times by fixing the loops, and keep handsontable as table library? Or should I scrap the old solution and implement something new?
Thanks :)
EDIT
I just saw you're using handsontable so my answer doesn't really provide a solution, as handsontable already uses a kind of list virtualization. I'll leave my answer anyway
Original Answer
What you can probably do is some sort of list virtualization, although this might be a bit tricky with table elements because you need absolute positioning and control of heights. Also it generally assumes that all rows have the same height.
The general idea is you only want to bother with rendering what's currently on the screen. Assuming you can fit 50 rows into your viewport at any time, you're measuring and updating 650 rows that don't matter. If you have 500000 rows, like in the fiddle, you're problem is going to be exponentially out of control.
Without knowing what you're doing exactly, here's a very general approach to the problem:
var elements = [];
var maxLength = 500000; // Number of elements we're going to generate
var itemHeight = 20; // We need a static row height for this to work
var totalHeight = itemHeight * maxLength; // The total height of the content
var $scrollContainer = $('#scroller-container'); // The container that will scroll
var $scrollContent = $('#scroller-contents'); // The content container for our items.
// We need to set the total height of the content so that absolute positioning works and the container receives the correctly sized scroll bar.
$scrollContent.css({ height: `${totalHeight}px` });
// Generate elements.
for (let i = 0; i < maxLength; i++) {
elements.push({
name: `item_${i}`,
value: `value_${i + 100}`
});
}
// By taking some measurements we will find out
// here exactly what items need to be rendered.
function obtainRenderableItems () {
// The size of our scrollable container
var containerHeight = $scrollContainer.height();
// How many items will fit inside the viewable area of our scrollable container
var viewport_count = Math.ceil(containerHeight / itemHeight);
// Where is it currently scrolled to.
var scrollPosition = $scrollContainer.scrollTop();
// The index of the first item in the viewable area
var start = Math.floor(scrollPosition / itemHeight);
// This calculation gives us a number of items to buffer on either side
// which prevents some janky behaviour when scrolling over yet unrendered items
var preScan = start - viewport_count <= 0 ? 0 : start - viewport_count;
// Basically we get the elements visible on the viewports by the current start
// index, and a buffer at the beginning and the end of the same amount of items
// in the viewport.
return elements.slice(preScan, preScan + (viewport_count * 3)).map((element, index) => {
return [preScan + index, element];
});
};
// Convert it to HTML, you can do whatever here, demo only.
function generateHTML (elements) {
return elements.map(el => {
let div = document.createElement('div');
div.className = 'element';
div.style.height = `${itemHeight}px`;
div.style.top = `${el[0] * itemHeight}px`;
div.innerHTML = `${el[1].name} - ${el[1].value}`;
return div.outerHTML;
}).join('');
}
// When we scroll we recalculate what items need to be shown and rerender them
// inside the page.
function onScroll (event) {
let items = obtainRenderableItems();
let htmlContent = generateHTML(items);
$scrollContent.html(htmlContent);
}
$scrollContainer.scroll(onScroll);
// Run at the beginning
onScroll();
The jQuery example above is based on a React component I wrote for exactly this purpose. You'll have to excuse my jQuery I haven't used it in years.
See the fiddle
There are a number of caveats with this approach. The major one being the row height must be the same for all rows, which is not workable for a number of situations. It also relies on a fixed container height, although the flex model can work around this.
I have several fixed position divs with the same class at varying distances from the left edge of the window, and I'd like to increase/decrease that distance by an equal amount on each div when a certain action happens (in this case, the window being resized). I've tried positioning them with CSS and percentages rather than pixels, but it doesn't quite do the job.
Is there a way to store the position of each of those divs in an array and then add/subtract a given amount of pixels?
Here's what I've tried so far - I'm still getting my head around JS so this could be really bad for all I know, but here goes:
roomObjects = $('.object-pos');
var objectCount = 0;
for ( var objectCount = 0; objectCount < 10; objectCount++;) {
roomObjects = rooomObjects[objectCount];
console.log(roomObjects.css("background-position").split(" "));
}
Do you mind sharing why percentages wouldn't work? Usually that's what I would recommend if you're wanting the page to scale correctly on window resizes. I guess if you really wanted to you could do something like:
$(window).resize(function() {
$('#whateverdiv').style.whateverproperty = $('#whateverdiv').style.whateverproperty.toString() + (newPosition - oldPosition);
oldPosition = newPosition;
}
this is obviously not the complete code, but you should be able to fill in the blanks. You'll have to set the oldPosition variable on page load with the original position so that the function works the first time.
edit: you'll also have to strip off the units from the x.style.property string, so that you'll be able to add the value to it
A problem you might well be facing is that when retrieving the current left or top properties, they are returned as a string, with px of % on the end. Try running a parseInt() on the returned values to get a number, then you might well be able to add to the values. Just be sure, when reassigning, that you concatenate "px" or "%" on the end as appropriate.
You could use a bit of jQuery :
var el = $("#id");
var top = el.css("top");
el.css("top", top * 1.2); // increase top by 20%
saves mucking around in the DOM
This might be useful if you want to position things relatively: http://docs.jquery.com/UI/Position
I have some code from someone but wondering why they might have used a function like this.
this.viewable= 45;
getGroups: function() {
return Math.ceil( this.getList().length / this.viewable );
}
Why would they divide the list length by a number viewable.
The result is the amount of items that should be rendered on the screen.
Why not just say 45 be the number. Is it meant to be a percentage of the list. Usually I will divide a large value by a smaller value to get the percentage.
Sorry if this seems like a stupid math question but my Math skills are crap :) And just trying to understand and learn some simple Math skills.
It's returning the number of groups (pages) that are required to display the list. The reason it's declared as a variable (vs. using the constant in the formula) is so that it can be modified easily in one place. And likely this is part of a plugin for which the view length can be modified from outside, so this declaration provides a handle to it, with 45 being the default.
That will give the number of pages required to view them all.
I would guess you can fit 45 items on a page and this is calculating the number of pages.
Or something similar to that?
This would return the total number of pages.
Total items = 100 (for example)
Viewable = 45
100 / 45 = 2.22222....
Math.ceil(2.2222) = 3
Therefore 3 pages
judging by the function name "getGroups", viewable is the capacity to show items (probably some interface list size).
By doing that division we know how many pages the data is to be divided (grouped) in order to be viewed on the interface. The ceil functions guarantees that we don't left out partial pages, if we had come records left that don't fill a complete page, we still want to show them and therefor make them count for a page.