How to implement multiple item selection? - javascript

I'm working on a drawing application where the user can create and delete shapes and select them with the mouse to drag them. Should selected shapes be referenced in a "selection" array or should they each have an isSelected property? Is there any advantage to one method over the other? Here's what I noticed so far (I will update it with whatever people find). My main concerns are programming simplicity and performance. This question is language independent for the most part, but the app is in javascript and rendering is done on an html5 canvas.
Checking for selection state
While we know instantaneously if an item is selected through the boolean property, the array-based solution requires looping through the array to search for the item's reference. This verification is quite common considering the cursor should change to a "dragging" icon when the mouse hovers a selected shape or a "pointing hand" icon when it hovers over a non-selected item.
Deselecting items
Selecting and deselecting specific items is instantaneous with the boolean property. With the array-selection, however, we must first loop through the selection to see if the item is there before adding or removing it from the selection. This makes "toggle selection" and "add to selection" options much slower. However, the most common action in such a drawing program is to clear the selection before selecting specific elements. With the array method, clearing the selection is as simple as replacing the array, while the boolean method requires setting the isSelected property to false on all items.
Deleting items
It is worth mentioning that an item must be removed from the selection array before it gets deleted. This detail doesn't show up in the isSelected approach.
Calling on all selected items
Actions which apply to all selected items require minimal code with an array as we only have to loop through the selection and call the method on each element. If the selection is small compared to the total amount of items, having to loop only through the selection can be a considerable time gain. With a boolean property, the time required to call an action on all selected items depends on the total amount of items instead of the selection's size.
Drawing time
Selected items commonly have colored borders that can be seen through other elements. It means that selection borders must be drawn in front of all other elements. Given the number of items "n" and the number of selected items ("s")...
The array solution takes from O(n) to 2*O(n) to render.
The boolean solution takes about 2*O(n) to render.
While you might think that this justifies opting for the array method on its own, keep in mind that redrawing is only done after actions are triggered, not 60 times a second. Checking the pointed shape to know if it is selected or not is more common then drawing. The only features that might noticeably slow down are dragging, stretching and making rectangle selections. Since the application is used to represent real life items, people usually enter the size they want on their keyboard instead of actually dragging items.
Encapsulation
The encapsulation is stronger with a boolean property, as items know whether they are selected or not without having to look at an application-scope variable. That difference can mean a lot in strict-scope environments, but it isn't that big of a deal in javascript. I suppose one could think that it isn't the shapes' role to select themselves.

I have implemented the boolean isSelected property and as the application grew, I built more and more functions which applied to all elements. In each function, I had to put a for loop which would make sure to only alter selected items. I'll write some kind of pseudo-code to keep this solution language agnostic again.
for each item {
if item is selected
do stuff
}
Having the same loop everywhere annoyed me, it felt wrong. When you copy-paste code parts between functions, it's never a good sign. So I made a getSelection() function which returned an array of the selected elements. That seemed to solve the problem I had before by extracting the horrible loop.
function getSelection() {
selection = new empty array
for each item in selection {
if item is selected
add item to selection array
}
return selection
}
function doStuffOnSelection() {
selection = getSelection()
for each item in selection {
do stuff
}
}
But this only revealed something much worse. Manipulating arrays (constantly pushing items into an array, creating an array, etc) is much slower than just looking through them. This change slowed down the application so much that in Firefox, I couldn't even have 100 items at once while I used to be able to have 2000 without a frame drop. Such a slowdown all because I now create an array. That's because the getSelection function needs to be called on every frame of the program to draw a blue border on all selected elements!
When I posted this question, I wasn't sure if it was premature optimization, now I know it wasn't.
The major downsides of keeping track of selected items with an array
- You need to look at all selected elements to determine if a specific element is selected and to change that state.
- To remove selected elements from the array of all elements, you need array researching, which is especially slow when either array is big.
The major downside of keeping track of selected items with a per-item boolean
- You need to look at all elements to know which elements are selected and to change that state.
So I've settled on a much better way to deal with all the negative sides: do both. I'll keep an array of selected items in the background, which gets updated when things are selected and deselected. This gets rid of all the bad sides of array-only selection, as I no longer need to search through any array to know if a specific element is selected (I can look at its boolean). And all functions which apply to selected items but do not select and deselect items (such as move, resize, draw) can just loop through the selection array. The best of both worlds!

Related

Slickgrid: value entered in a cell disappears after moving out of cell

In a Slickgrid cell, i click and enter a value. Once i move out of this cell using a mouse click the value entered disappears. But if i enter the value in the cell and tab out then the value stays in the cell. I would like to know if this should be manually handled.
OnClick event i am calling
grid.updateRow(grid.getDataItem(args.row));
grid.invalidate();
grid.render();
Thanks,
Asha
You're doing something wrong, updateRow() argument is a row index in the grid but you're passing an item object so that won't work, also if I remember correctly updateRow is only used for re-rendering or refreshing a specific row index, it's not for updating the data but more for rendering purposes (perhaps the naming is confusing).
I personally only use the SlickGrid DataView and the functions to call for updating items are different, you can see how to that in this other Stack Overflow answer Change slickgrid cell data after edit
If you're using the grid object, as it's written in the other SO answer I referenced earlier, then it would be
grid.invalidateRow(args.row);
data[args.row][grid.getColumns()[args.cell].field] = a.msg;
grid.render();
Personally I always use the SlickGrid DataView as it's much more user friendly and comes with a lot more benefit like data grouping and many other functionalities. Again take a look at the other SO answer that I wrote in previous paragraph, that is the correct way to do it

Jquery: how to get mutual parent div from array of divs (for equal height)

Current scenario:
I have a page that is divided into columns.
There are different blocks on my page that have the ".equalHeight" class assigned to them: 2 of them in first column, 1 in the middle and 2 in the third column.
When I select all blocks using the $(".equalHeight") selector, I get an array of elements.
How can I find the mutual parent for the ones in the first column, the second and the last column?
I need the height from the first column, the second column and the third column, to assign the the children.
Is there an efficient way to do this using jquery?
Also keep in mind that once the first and second element of the array are found, you won't need to do the check for the second one in the array, because it's already updated by the first one, thus making the loop faster...
Any ideas?
Thanks
M.
Basically you're going to iterate over the row checking .outerHeight and determine the biggest one. Link to jquery documentation
jquery-match-height does a great job of this with jquery (github page). It does a lot more than it sounds you need (both features-wise and in terms of browser compatability), but check out jquery.matchHeight.js's lines 221-284

javascript function using objects

I am designing a sort of game where there are team bases that surround a hexagonal board. The idea is that when a team base is clicked, it will be that team's turn. I have:
$('.team').click(function(){
var teamID=$(this).attr('id');
explore(teamID);
});
I then use the teamID to find the index of the team that was clicked, which is stored as an object from a json file with attributes such as teamname, score, etc.
function explore(index){ // the game portion
var turn=file[index]; // finds the team's info from json file
$('.hex').click(function(){ // when a hex is clicked.... play the game
alert(turn.teamname);
// game elements
}
This always works the first time around, however if I click on a different team box and then a hex, oftentimes it thinks that it is the turn of the box I clicked before. I added the alert(turn.teamname) to try to debug. If I'm clicking a different box, it will always alert the first box, and then send a second alert with the different box. I can't figure out why there would be two alerts? So it will always alert 'team1' and then 'team1','team2'. as I click more boxes it keeps alerting until it just alerts all of them. Additionally, if I have clicked more than two boxes before, even if I keep clicking the same hex it seems like it alternates between alerting me that it is 'team1' and 'team2'.
This is my first stackoverflow post so I hope it makes sense! Thanks!
The reason you get this behavior is that you add event handlers to dom elements but never remove them. You need to change the way you handle clicks on the hexes. You may add an event handler once with http://api.jquery.com/one/ to the parent element that holds all hexes and check which hex is clicked inside the event handler.
Or you can try a more trivial solution where you add an event listener once to the hexes and check if there is a selected team:
var turn;
var teamSelected = false;
function explore(index){ // the game portion
teamSelected = true;
turn=file[index]; // finds the team's info from json file
}
$('.hex').click(function(){ // when a hex is clicked.... play the game
if (teamSelected) {
alert(turn.teamname);
// game elements
teamSelected = false;
}
}
For something like this, I would recommend getting into meteor. The reactive programming model is much cleaner than an imperative one (especially in a game where it can quickly get complex).
I feel that this example illustrates what can be done very quickly using this framework (closest to your question's example).
To dive in, I'd recommend looking at the intro video, then proceed to the docs and a recent book about it.

Multy-filter page with transitions - Isotope & Quicksand JS problems

(note: edited since I've just realized the question are somehow correlated, at least in my mind!)
I want to create a multi-filter page in which the result will be animated...
I'm trying with 2 different plugin (quicksand and Isotope) and with both solution I'm having problems...
---ISOTOPE--- (original)
With Isotope I need to filter data based on active class, or based on IDs of filters, which I've already stored in JS, does anyone know how can I do that?
I set up a page with 2 different filter like 'color' (red, blue, orange...) and 'type' (square, round...)
I already have a Javascript that assign class active to the 2 filtering lu based on selection, if all color are selected shift the 'active' class to 'all', and more than one sub-filter can be activated. And this also save the list of the id of the active li items in a string for color filter and another string for shape filter
I also already set up the page like the combination filter Isotope demo at this link: http://isotope.metafizzy.co/demos/combination-filters.html and it is working fine but doesn't allow to select more than one sub-filter at the same time.
I saw the demo at this link http://fiddle.jshell.net/desandro/JB45z/ with filtering combination, but it is based on radio button that I'd like to avoid.
I'm not sure if what I'm trying to do is easy or not... is like, how to tell to Isotope to filter based on the sub-filter that have active class or based on the sum of the li with the ID saved in my two string?
Thanks for any help, as you can easily understand I'm not skilled in js at all and english is not my first language!
--- QUICKSAND --- (edited)
I've just realized that I didn't explain why I stored the IDs of the selected items in the js string. And this is also about the different js question.
I was trying to set up the same system with Quicksand instead of Isotope.
But since quicksand need to have a starting li and a destination li to display the animation I set up the js to pass an array to a different temporary php page that use the array to display a destination li.
All until here is working fine but I'm not able to get back the li data in the original page to let Quicksand perform the animation. The last part of my js appear to have problems that I'm not able to fix (too many days trying with no success), the last part of the js is:
$.post( 'destination_li_filtered.php', {
colorString,
shapeString,
$('#ids').attr('val')
},
function(data) { // should contains the resulting data from the request (?)
$('.list').quicksand( $(data).find('li'),
{ adjustHeight: 'auto' },
function() {
callbackCode();
}
);
e.preventDefault();
});
the external page destination-li-filtered is displaying the results but the original page is not able to take back the data...
Obviously I need to set op the page with isotope or quicksand, not both.
but I'm also wondering witch is the best plugin to display 100's of results with about 20 filters (without considering the combinations). And of course, which is the easiest to use!
You should see the radio button and the change in view as separate things.
It's a common misconception that you should store all your state in the DOM (ie. which checkbox is checked). The DOM is a view, you don't keep state in a view.
You should have a state that lives in your JavaScript.
Like:
var state = "all_selected"; // green, red, blue
Then, when you check the radio button, it will set the appropriate state and update the list (show/hide elements based on state).
This allows you to make any control you want, be it a radio button, a checkbox or something completely custom.

Sort multiple selected items with jQueryUI

I want to make something that makes much sense, but I think its not actually included in the jQueryUI.
I have a list of items, that are both sortable and selectable.
I can sort them one at a time, and I can select multiple items.
The question is, how can I sort multiple selected items together ?
I got this code atm,
$( "#centermainlist" ).sortable({
scroll: false,
handle: ".dots",
appendTo: 'body',
helper: 'clone',
});
what should I add, to make it sort all selected items ?
Thanks in advance :)
All the best,
Alexander
This functionality is not included in jQueryUI, so there's nothing you can just add to make it work the way you want. You will need to write your own code or search for plugins if you want to sort multiple items like that.
I imagine the reason this isn't in jQuery is due to the high number of situational variables involved. You're probably trying to do something like select three sequential items, then drag to move them all up or down - but this isn't the only possibility. What if the items selected aren't in a row? How do you handle that?
Example:
Item 1
Item 2 - selected
Item 3
Item 4 - selected
Item 5 - selected
Item 6
How do you move these items? Do you enforce the space in between the selected items? If so, do you stop moving items when the user moves Item 2 to the first position (since you have to keep the space)? Or in that case do you collapse them? Then do you remember that there was a hole if they move back down again? Maybe you just collapse them to begin with (easiest to implement, but maybe not the desired functionality)? Or maybe you enforce sequential selection for the move - if the items are all next to each other you can move them, if there are holes you can't?
So there are a lot of things to consider here. This is easy to implement with buttons (select items, click up or down buttons to move - just loop through and apply the move to each selected item) but much more difficult with drag reordering.

Categories