I wonder if it is possible (in javascript or jquery but without any plugins) to get all elements (for example table rows tr) in current viewport without looping through each of them? I found a lot of examples how to check if specified element is in current viewport, but what I need is a function returns a list of all elements in current viewport. I need this for virtualization because this table should have an infinite capacity and looping through each row from two millions rows is quite inefficient :)
Is there any reasonable way to do this?
Assuming you're not doing anything fancy with positioning, table rows in the viewport can be found with a binary search. For example, for 200000 rows, about 18 lookups are required to locate the first row on the page (jsfiddle, warning: slow to load). This can be extended to find the last element as well, or you could just loop through the elements starting from the first until you find one that is no longer visible.
var rows = table.children().children();
var start = 0;
var end = rows.length;
var count = 0;
while(start != end) {
var mid = start + Math.floor((end - start) / 2);
if($(rows[mid]).offset().top < document.documentElement.scrollTop)
start = mid + 1;
else
end = mid;
}
Obviously this does not work well if anything is floated, absolutely positioned, etc. In short: The nodes being searched must be in order such that rows[N-1].top <= rows[N].top. For something such as a table, this should be true if no styling is applied and no multi-row cells exist.
Related
I have shift planning table created by JS and shown to user as html. This table is currently filled by user. I have certain set of rules and when user breaks them, he gets warning and he have to fix them.
I want to create script which fills this table automatically. I know how to do this, but i don't know how to do it so that script obeys all that rules and preferences.
For example. I cant give to all users all morning shifts. I need to have it so that one person have no more that 5 shifts in on week, have at least one free weekend, have no more than 20 shifts per month and have them layed out equaly all over the month (not all shifts at the start)
Is this even possible with javascript? If yes could you please give me some idea how?
My current idea is to use 'IF' to obey the rules, but i need to have some rules on same level, so if i use 'IF' and logical operators, but i am kinda stuck on it for days and i am not certain that this is right way to go.
Fiddle with layout of table
Basic draft of my idea to fill table by rules:
function autoFill() {
var numberOfPeople = people.length;
var days = daysInThisMonth();
var ruleOne = ruleOneFunction(); //boolean
var ruleTwo = ruleTwoFunction(); //boolean
for(var i = 0; i < numberOfPeople; i++) {
for(var j = 1; j <= days; j++) {
if(ruleOne === true && ruleTwo === true) {
//fill table here
}
}
}
};
I am trying to build a small CAD app using HTML5 Canvas and Paper.js
Now I am stuck at a problem I'd hate asking for help for since I believe I should solve this on my own, but everytime I try to think about a solution my head hurts.
I am trying to build the following functions:
Align-left
Align-right
Align-center.
The user is supposed to select a bunch of elements on the canvas and click one of the above functions to align them together.
The whole concept is that the user selects a Reference Item in a way. The elements are aligned relative-to the Reference Item. This ensures that the alignments happen in a user predictable way.
There are 2 ways for the user to select elements
Selection-via-Intersection
The user clicks and drags a selection rectangle. While dragging I
can track in an array which items intersected with the selection
rectangle. I can track the first intersected item here just fine, so I
have a Reference Item .Works perfectly so far.
Selection-via-Click+Shift
The user holds Shift and clicks on elements to do a multi-selection.
It's a functionality I'm sure everyone has seen in any modern graphics
editor. I have yet to figure out a way how to track a first-selected
item here, since items can be set as first-selected and then
deselected.
The problem:
The above selection methods can be COMBINED together -
The user can select-via-intersection and then start select/de-select items using Shift+Click.
Which one from the selectedItems is the Reference-Item now?
An example:
Item 2 was the first-selection on selection-via-intersection. Item 2
was de-selected using Shift+Click. Which one is the Reference-Item
now?.
Here is a GIF that illustrates what I am talking about (although it's invinsible in the GIF the items get selected as soon as the intersection rectangle touches them.)
I am aware that the whole thing will involve some array logic but I cannot seem to grasp how to implement the whole thing.
Notes:
Apart from the complexity of this thing, I have performance constraints as well. Intersection Detection which is used for the Selection-via-Intersection is already a heavy operation and it get's fired continously while the user drags a selection rectangle.
This is the code for Select-Via-Intersection
The following function getSortedIntersects get's called continuously while the user drags a selection rectangle. I log in the console the first selected item:
var checkedSegments = new Array();
function getSortedIntersects(rect) {
//children are all the items drawn on the canvas
var children = project.activeLayer.children;
//I clone the children array into another array and I exclude all items other than Paths
//Intersection Detection works only on paths.
var pathsOnCanvas = children.filter(function (el) {
return el instanceof Path
});
var newCheckedSegments = new Array();
var firstSelection = "";
//Start looping over all Paths on the canvas
for (var i = 0; i < pathsOnCanvas.length; i++) {
//create an array which lists all the intersections. The array is called ''intersections''
var intersections = pathsOnCanvas[i].getIntersections(rect);
//If there is at least 1 intersection OR the rect contains a path then proceed
if (intersections.length > 0 || rect.contains(pathsOnCanvas[i].bounds)) {
//Checks if the selected item at index i is in the newSegments array. If so, it continues iterating the loop without going further at this iteration.
if (newCheckedSegments.indexOf(pathsOnCanvas[i]) >= 0)
continue;
pathsOnCanvas[i].selected = true;
newCheckedSegments.push(pathsOnCanvas[i]);
}
}
for (var i = 0; i < newCheckedSegments.length; i++) {
if (checkedSegments.indexOf(newCheckedSegments[i]) < 0)
checkedSegments.push(newCheckedSegments[i]);
firstSelection = (checkedSegments[0].id);
console.log("first selection is " + firstSelection);
}
for (var i = checkedSegments.length - 1; i >= 0; i--) {
if (newCheckedSegments.indexOf(checkedSegments[i]) < 0) {
checkedSegments[i].selected = false;
checkedSegments.splice(i, 1);
}
}
}
Background:
I asked this question yesterday:
How to modify the orientation of a <ul> spanning multiple columns?
asking how to convert a list like this:
a b c
d e f
g h i
j k l
into a list like this:
a e i
b f j
c g k
d h l
and I got this awesome response by beeflavor: http://jsfiddle.net/H4FPw/12/
Problem:
Unfortunately I didn't specify that there could be any number of list items, so his response is hard-coded to 4 rows, and uses a tricky matrices algorithm (read: black magic) that I can't wrap my head around.
I'm poking and prodding at this, trying to add variability but unfortunately it's not coming together for me and today's the deadline for this stuff.
This is an updated example of the problem I'm having: http://jsfiddle.net/H4FPw/13/
Is there anyone out there with a better head for this stuff who can give me a steer in the right direction?
I know it's not as "elegant" as your accepted answer in that question, but the code I linked to in my answer yesterday does work perfectly for you:
http://jsfiddle.net/AcdcD/
If you don't need to handle resizing, it can be simplified slightly:
http://jsfiddle.net/AcdcD/1/
Maybe you can use this if you run out of time?
jQuery plugin .transpose()
Exactly what you want it to do. I needed the same thing so I've written a general jQuery plugin that transposes any floated or inline-blocked elements that seem to be in columns but their order goes in rows.
Check out my detailed blog post with an example for transposing US states, and then head over to GitHub where the plugin is maintained and you can get a minified version (915 bytes as of version 1.2) of it as well.
All you need to do is:
$("yourSelector").transpose();
In your case that would be
$("li").transpose();
The good thing is that plugin checks how many columns are there originally (before transposition) and transposes to the same amount of columns afterwards. It doesn't add any additional HTML elements into DOM (like floated column wrappers or similar) it just rearranges existing elements. This is very good when it comes to CSS settings of the page because it doesn't interfere with them in any way shape or form.
And it also distinguishes between different lists of elements (items that are contained within different containers) as if you'd have two UL elements that need transposition. You don't have to call .transpose() on each because plugin will do that for you. You'd still just use the same selector as previously written.
What about this code:
jsfiddle
// I add a number of new elements to the <ul>, in this case, 'm', 'n', 'o' &'p'
var rowcount = 6; // I increase rowcount from 4 to 6 to accommodate the new elements
var colcount = 3;
var ichild=$('.directory-result-list-bottom').children();
var wf= "<li class='directory-result-text'>";
var wb= "</li>"
var k="";
var u;
var v;
var carryover=0;
$('.directory-result-list-bottom').empty();
for(var i = 0; i<rowcount; i++){
carryover=0;
for(var j=0; j<colcount; j ++){
k=k+wf+(ichild[(j*rowcount)+i].innerHTML)+wb;
}
}
document.getElementById('place').innerHTML=k;
I believe empty LI's are needed to fill the empty space.
This should work:
for(var i = 0; i<rowcount; i++){
for(var j=0; j<colcount; j ++){
var n = (j*rowcount)+i;
if (ichild[n])
k=k+wf+(ichild[n].innerHTML)+wb;
else
k = k + wf + wb
}
}
I think it's simpler to represent the items as a 1-D list (a,b,c...) and think about how to 1) get that from the original array and 2) transform it into the output array.
Step 1 is easy, because the 1-D list is the same order as the <li> elements in the HTML source.
So, suppose we have n elements and we want to put them into R rows and C columns but filling columns first. The element that goes in position (r,c) (0 based) is just element #R*c + r. And, as stated before, the order in the source file is the row-first order, or (0,0), (0,1), (0,2), ...
If the # of elements doesn't fill the RxC grid exactly, then you have to add checks that the computed element number doesn't exceed n (easiest way is to pad the list with empty elements beforehand).
Depending on the design requirements, you can also adapt this to minimize the number of blank elements given a fixed number of rows or columns.
I have build a grid of div's as playground for some visual experiments. In order to use that grid, i need to know the x and y coordinates of each div. That's why i want to create a table with the X and Y position of each div.
X:0 & Y:0 = div:eq(0), X:0 Y:1 = div:eq(1), X:0 Y:2 = div:eq(2), X:0 Y:3 = div:eq(3), X:1 Y:0 = div:eq(4) etc..
What is the best way to do a table like that? Creating a OBJECT like this:
{
00: 0,
01: 1,
02: 2,
etc..
}
or is it better to create a array?
position[0][0] = 0
the thing is i need to use the table in multiple way's.. for example the user clicked the div nb: 13 what are the coordinates of this div or what is the eq of the div x: 12 y: 5.
Thats how i do it right now:
var row = 0
var col = 0
var eq = 0
c.find('div').each(function(i){ // c = $('div#stage')
if (i !=0 && $(this).offset().top != $(this).prev().offset().top){
row++
col = 0
}
$(this).attr({'row': row, 'col': col })
col++
})
I think it would be faster to build a table with the coordinates, instead of adding them as attr or data to the DOM. but i cant figure out how to do this technically.
How would you solve this problem width JS / jQuery?
A few questions:
Will the grid stay the same size or will it grow / shrink?
Will the divs stay in the same position or will they move around?
Will the divs be reused or will they be dynamically added / removed?
If everything is static (fixed grid size, fixed div positions, no dynamic divs), I suggest building two indices to map divs to coordinates and coordinates to divs, something like (give each div an id according to its position, e.g. "x0y0", "x0y1"):
var gridwidth = 20, gridheight = 10,
cells = [], // coordinates -> div
pos = {}, // div -> coordinates
id, i, j; // temp variables
for (i = 0; i < gridwidth; i++) {
cells[i] = [];
for (j = 0; j < gridheight; j++) {
id = 'x' + i + 'y' + j;
cells[i][j] = $('#' + id);
pos[id] = { x: i, y: j };
}
}
Given a set of coordinates (x, y) you can get the corresponding div with:
cells[x][y] // jQuery object of the div at (x, y)
and given a div you can get its coordinates with:
pos[div.attr('id')] // an object with x and y properties
Unless you have very stringent performance requirements, simply using the "row" and "col" attributes will work just fine (although setting them through .data() will be faster). To find the div with the right row/col, just do a c.find("div[row=5][col=12]"). You don't really need the lookup.
Let me elaborate on that a little bit.
If you were to build a lookup table that would allow you to get the row/col for a given div node, you would have to specify that node somehow. Using direct node references is a very bad practice that usually leads to memory leaks, so you'd have to use a node Id or some attribute as a key. That is basically what jQuery.data() does - it uses a custom attribute on the DOM node as a key into its internal lookup table. No sense in copying that code really. If you go the jQuery.data() route, you can use one of the plugins that allows you to use that data as part of the selector query. One example I found is http://plugins.jquery.com/project/dataSelector.
Now that I know what it's for...
It might not seem efficient at first, but I think It would be the best to do something like this:
Generate the divs once (server side), give them ids like this: id="X_Y" (X and Y are obviously numbers), give them positions with CSS and never ever move them. (changing position takes a lot of time compared to eg. background change, and You would have to remake the array I describe below)
on dom ready just create a 2D array and store jquery objests pointing the divs there so that
gridfields[0][12] is a jQuery object like $('#0_12'). You make the array once and never use selectors any more, so it's fast. Moreover - select all those divs in a container and do .each() on them and put them to proper array fields splitting their id attributes.
To move elements You just swap their css attributes (or classes if You can - it's faster) or simply set them if You have data that has the information.
Another superfast thing (had that put to practice in my project some time ago) is that You just bind click event to the main container and check coordinates by spliting $(e.target).attr('id')
If You bind click to a grid 100x100 - a browser will probably die. Been there, did that ;)
It may not be intuitive (not changing the div's position, but swapping contents etc.), but from my experience it's the fastest it can get. (most stuff is done on dom ready)
Hope You use it ;) Good luck.
I'm not 100% sure that I understand what you want, but I'd suggest to avoid using a library such as jQuery if you are concerned about performance. While jQuery has become faster recently, it still does has more overhead than "pure" JS/DOM operations.
Secondly - depending on which browsers you want to support - it may even be better to consider using a canvas or SVG scripting.
JavaScript performance in Internet Explorer sucks. No news there. However there are some tips and tricks to speed it up. For example, there is this three part series. Still I find myself unable to squeeze decent performance out of it. Perhaps some of you have an idea what else to do so it was speedier?
What I want to do is create a medium-size table from scratch in Javascript. Say, 300 rows, 10 cells each. It takes at about 5-6 seconds on my computer to do this. OK, granted, it's a 5 year old rig, but that's still too much. Here's my dummy code:
<html>
<body>
<script type="text/javascript">
function MakeTable(parent)
{
var i, j;
var table = document.createElement('table');
var insertRow = table.insertRow;
for ( i = 0; i < 300; i++ )
{
var row = insertRow(-1);
for ( j = 0; j < 10; j++ )
{
var cell = row.insertCell(-1);
cell.innerHTML = i + ' - ' + j;
}
}
parent.appendChild(table);
}
</script>
<div onclick="MakeTable(this);">Click Me!</div>
</body>
</html>
Added: Hmm, apparently string-concatenation (with array.join) is the only way to go. Well, sad, of course. Was hoping to do it the "proper" DOM-way. :)
Here is an interesting link I found when looking for an answer on this:
The page uses five different scripts / methods to generate a table.
According to their tests, using strings is by far faster than using DOM / Table elements.
http://www.quirksmode.org/dom/innerhtml.html
One of the main reason's for IE's performance issues are DOM operations. You want to do your DOM operations as efficiently as possible. This can include, depending on your situation (benchmark!):
Offline creation of your DOM structure; keep the top level element out of the document (create, but not append) then appending it to the document when it's ready, instead of appending every element into the DOM as you create it
write innerHTML instead of DOM manipulation
You could try 'Duff's Device': Unwinding a loop by repeating the code a number of times:
for (var i = 0; i < count / 4; i++) {
doSomething();
doSomething();
doSomething();
doSomething();
}
Obviously this leaves the remainder when divided by 4, the original Duff's Device had a clever way of jumping to the middle of the loop using a switch statement mixed in with the loop. Javascript does not support this, but you could manually process the rest of your rows. Also the number 4 is random, the number itself can be derived by performance testing.
See also: http://www.websiteoptimization.com/speed/10/10-3.html