Select elements between A and B efficiently - javascript

I have a table with a lot of td's. What would be the most efficient way to select selection 1 to selection 2 and everything in-between?
On selection 1 i give the selected td an id and on selection 2 i give that selection another id in order to later on look for those id's.
Current code:
// small helpers
function _for(e,f) { var i, len=e.length; for(i=0;i<len;i++){ f(e[i]); }}
function _id(e) { return document.getElementById(e); }
// VARs
var main = _id('monthTables'),
td = main.querySelectorAll('td'),
go = false,
stop = false,
i = 0,
s1i = 0,
s2i = 999;
// Loop throught td's
_for(td, function(e){
if( e.id == 'sel1' ) { go = 1; s1i = i; }
if( e.id == 'sel2' ) { stop = 1; s2i = i; }
if( s1i < s2i && go ) {
if(go) { e.classList.add('range'); }
}
if( stop ) { go = 0; }
}) // end loop
Live example (select two dates):
http://cdn.rawgit.com/tommiehansen/lightRange/master/test.html

The code is good in my opinion, maybe you could add few optimizations (like stopping the loop when found the second selected id )
Example for the loop:
function _for(e,f) {
var i, len=e.length;
for(i=0;i<len;i++){ if(f(e[i]) === false) break; }
}
so it will break when the function f returns false
// Loop throught td's
_for(td, function(e){
//...
if( stop ) return false;
}) // end loop
Also i suggest you to use getElementsByTagName instead of querySelectorAll if you want a bit more performance ..but maybe that's an evil micro optimization
An other optimization would be to start looping from the first selected td, maybe storing the index when you select the td and using that value in that code you posted as initial index

Related

Dynamically Assign Variables after looping through JSON object

Probably a beginner question, but I have an array json object that I'm looping through to pull out certain values. If these values equal "NO", then I want to hide a div using jquery. This should be simple enough, but I don't know how many items the array can contain so I'll have to make sure to dynamically grab all of the number/value pairs.
My JSON is similar to below:
"account" : [{
"ID":1,
"transferAllowed": "NO"
},{
"ID":2,
"transferAllowed": "YES"
}]
My loop:
//define variable that references the values I need from the json object
var account = this.json.account;
for ( var i = 0; i < account.length; i++ ) {
//compare all values of transferAllowed however many that may be. If they all equal No, then hide div with jquery $("#div").hide()
}
Any help is appreciated. Thanks.
Try flipping the problem, like defining a "null hypothesis" for a science experiment: to disprove that all items say "NO", you need only find a single value that doesn't say "NO".
This means that you only need a simple check in the loop to find your answer:
var account = this.json.account;
// Start with the hypothesis that everything will say "NO"
var allDisallowed = true;
for ( var i = 0; i < account.length; i++ ) {
if ( account[i].transferAllowed != "NO" ) {
// Hypothesis disproved!
allDisallowed = false;
}
}
if ( allDisallowed ) {
$("#div").hide();
}
Since you only need one value to be allowed, you can actually stop at the first one you see using a break; statement:
for ( var i = 0; i < account.length; i++ ) {
if ( account[i].transferAllowed != "NO" ) {
// Hypothesis disproved!
allDisallowed = false;
// No need to finish the loop
break;
}
}
Alternatively, if you wrap the loop up in a function, you can save defining some variables:
function areAllDisallowed( account ) {
for ( var i = 0; i < account.length; i++ ) {
if ( account[i].transferAllowed != "NO" ) {
// At least one element is not a "NO", so result is false
return false;
}
}
// We will only reach this line if we never return false
// Therefore, we know that all elements say "NO"
return true;
}
if ( areAllDisallowed(this.json.account) ) {
$("#div").hide();
}
(As pointed out in a comment, the position of var i in the for loop could cause confusion later, but I've left it as-is to make only the changes that answer your specific question.)
try this :)
var account = this.json.account;
var hide_count = 0;
for ( var i = 0; i < account.length; i++ ) {
if(account[i].transferAllowed=="NO") hide_count++;
}
if(hide_count == account.length){
$("#div").hide();
}

FInd if an option has been 'selected' OR 'de-selected' via change event of Select box

I have a html multi-select select box. When i select/de-select values in a change event is fired on it.
Whats the best way to know from the event that the event is because of an element being selected or deselected? I don't want its value, just whether the change event is because of 'selection' or 'de-selection'.
Making a few assumptions here, you have a multi-select and you just need to know if there has been a selection or not.
Then, you just need to check the length of selected options with a cached value.
See this fiddle: http://jsfiddle.net/2uudx/1/
Not the most elegant or optimized way, but a direction.
var sel = 0;
$('#mySelect').on("change", function() {
var newsel = $(this).find(":selected").length;
if (newsel > sel) {
// selected
} else {
if (newsel < sel) {
// de-selected
} else {
// no-change
}
}
sel = newsel;
});
If you really need to compare the values which were selected or de-selected, then instead of caching the length, you cache the selected values in an array. On change, compare the currently selected values to the cached array.
Hope that gives a suitable pointer.
Update:
Using values to compare more accurately, as mentioned above by way of arrays.
Here is a sample fiddle: http://jsfiddle.net/u9XE4/3/
var sel = [];
$('#s1').on("change", function() {
var newsel = $(this).val();
var same = compareArray(newsel, sel);
if (same) {
// no-change
} else {
if ((!same) && ((newsel.length == sel.length) || (newsel.length < sel.length))) {
// de-selected
} else {
// selected
}
}
sel = newsel;
});
function compareArray(a, b) {
$.each( a, function( key, value ) {
var index = $.inArray( value, b );
if( index != -1 ) {
return true;
}
});
return false;
}
hope that helps.

For Loop/Each Loop variable storage and comparison (jQuery or Javascript

I have an each loop with jquery (I wouldn't mind using a for loop if the code works better) and it's cycling through all divs with the class="searchMe". I would like to store the current div in a variable that I can use in the next iteration of the loop to compare a value with the new current div. Some code is removed (all working) to simplify things, but here's my code:
$('.searchMe').each(function(){
var id = $(this);
sortUp += 1,
sortDown -= 1;
if (test) {
id.attr("name", ""+sortUp+"");
}
else {
id.attr("name", ""+sortDown+"");
}
if ( id.attr("name") > lastId.attr("name") ) {
id.insertBefore(lastId);
}
lastId = id; //this doesn't work, but illustrates what I'm trying to do
});
Everything is working properly except the last 3 lines.
is this possible with an each/for loop?
I don't know why sort up is needed when you are comparing backwards alone. you can replace the last three lines with...
if ( parseInt(id.attr("name")) > parseInt(id.prev().attr("name")) ) {
id.insertBefore(id.prev()).remove();
}
you can use index in $.each()
like
$('.searchMe').each(function(index){
var id = $(this);
sortUp += 1,
sortDown -= 1;
if (test) {// don't know what is test, let it is predefined
id.attr("name", sortUp);// no need to add ""
}
else {
id.attr("name", sortDown);
}
if ($('.searchMe').eq(index-1).length && id.attr("name") > $('.searchMe').eq(index-1).attr("name") ) {
id.insertBefore($('.searchMe').eq(index-1));
}
});
Or Alternatively you can define lastid like
var lastId='';// let it be global
$('.searchMe').each(function(index){
var id = $(this);
sortUp += 1,
sortDown -= 1;
if (test) {// don't know what is test, let it is predefined
id.attr("name", sortUp);// no need to add ""
}
else {
id.attr("name", sortDown);
}
if (id.attr("name") > lastId.attr("name") ) {
id.insertBefore(lastId);
}
lastId=id;// assign here the current id
});
Read eq() and $.each()

javascript not removing undefined objects from array

I've got an in page text search using JS, which is here:
$.fn.eoTextSearch = function(pat) {
var out = []
var textNodes = function(n) {
if (!window['Node']) {
window.Node = new Object();
Node.ELEMENT_NODE = 1;
Node.ATTRIBUTE_NODE = 2;
Node.TEXT_NODE = 3;
Node.CDATA_SECTION_NODE = 4;
Node.ENTITY_REFERENCE_NODE = 5;
Node.ENTITY_NODE = 6;
Node.PROCESSING_INSTRUCTION_NODE = 7;
Node.COMMENT_NODE = 8;
Node.DOCUMENT_NODE = 9;
Node.DOCUMENT_TYPE_NODE = 10;
Node.DOCUMENT_FRAGMENT_NODE = 11;
Node.NOTATION_NODE = 12;
}
if (n.nodeType == Node.TEXT_NODE) {
var t = typeof pat == 'string' ?
n.nodeValue.indexOf(pat) != -1 :
pat.test(n.nodeValue);
if (t) {
out.push(n.parentNode)
}
}
else {
$.each(n.childNodes, function(a, b) {
textNodes(b)
})
}
}
this.each(function() {
textNodes(this)
})
return out
};
And I've got the ability to hide columns and rows in a table. When I submit a search and get the highlighted results, there would be in this case, the array length of the text nodes found would be 6, but there would only be 3 highlighted on the page. When you output the array to the console you get this:
So you get the 3 tags which I was expecting, but you see that the array is actually consisting of a [span,undefined,span,undefined,undefined,span]. Thus giving me the length of 6.
<span>
<span>
<span>
[span, undefined, span, undefined, undefined, span]
I don't know why it's not stripping out all of the undefined text nodes when I do the check for them. Here's what I've got for the function.
performTextSearch = function(currentObj){
if($.trim(currentObj.val()).length > 0){
var n = $("body").eoTextSearch($.trim(currentObj.val())),
recordTitle = "matches",
arrayRecheck = new Array(),
genericElemArray = new Array()
if(n.length == 1){
recordTitle = "match"
}
//check to see if we need to do a recount on the array length.
//if it's more than 0, then they're doing a compare and we need to strip out all of the text nodes that don't have a visible parent.
if($(".rows:checked").length > 0){
$.each(n,function(i,currElem){
if($(currElem).length != 0 && typeof currElem != 'undefined'){
if($(currElem).closest("tr").is(":visible") || $(currElem).is(":visible")){
//remove the element from the array
console.log(currElem)
arrayRecheck[i] = currElem
}
}
})
}
if(arrayRecheck.length > 0){
genericElemArray.push(arrayRecheck)
console.log(arrayRecheck)
}
else{
genericElemArray.push(n)
}
genericElemArray = genericElemArray[0]
$("#recordCount").text(genericElemArray.length + " " +recordTitle)
$(".searchResults").show()
for(var i = 0; i < genericElemArray.length; ++i){
void($(genericElemArray[i]).addClass("yellowBkgd").addClass("highLighted"))
}
}
else{
$(".highLighted").css("background","none")
}
}
If you look at the code below "//check to see if we need to do a recount on the array length. ", you'll see where I'm stripping out the text nodes based off of the display and whether or not the object is defined. I'm checking the length instead of undefined because the typeof == undefined wasn't working at all for some reason. Apparently, things are still slipping by though.
Any idea why I'm still getting undefined objects in the array?
My apologies for such a big post!
Thanks in advance
I've modified your eoTextSearch() function to remove dependencies on global variables in exchange for closures:
$.fn.extend({
// helper function
// recurses into a DOM object and calls a custom function for every descendant
eachDescendant: function (callback) {
for (var i=0, j=this.length; i<j; i++) {
callback.call(this[i]);
$.fn.eachDescendant.call(this[i].childNodes, callback);
}
return this;
},
// your text search function, revised
eoTextSearch: function () {
var text = document.createTextNode("test").textContent
? "textContent" : "innerText";
// the "matches" function uses an out param instead of a return value
var matches = function (pat, outArray) {
var isRe = typeof pat.test == "function";
return function() {
if (this.nodeType != 3) return; // ...text nodes only
if (isRe && pat.test(this[text]) || this[text].indexOf(pat) > -1) {
outArray.push(this.parentNode);
}
}
};
// this is the function that will *actually* become eoTextSearch()
return function (stringOrPattern) {
var result = $(); // start with an empty jQuery object
this.eachDescendant( matches(stringOrPattern, result) );
return result;
}
}() // <- instant calling is important here
});
And then you can do something like this:
$("body").eoTextSearch("foo").filter(function () {
return $(this).closest("tr").is(":visible");
});
To remove unwanted elements from the search result. No "recounting the array length" necessary. Or you use each() directly and decide within what to do.
I cannot entirely get my head around your code, but the most likely issue is that you are removing items from the array, but not shrinking the array afterwards. Simply removing items will return you "undefined", and will not collapse the array.
I would suggest that you do one of the following:
Copy the array to a new array, but only copying those items that are not undefined
Only use those array items that are not undefined.
I hope this is something of a help.
Found the answer in another post.
Remove empty elements from an array in Javascript
Ended up using the answer's second option and it worked alright.

Check and control the number of checked check boxes with JavaScript

I am validating some check boxes and would like for the user to be able to select only 4 (from 7 possible) and disable the others if the current box is being checked(if there are already 3 checked) or enable the everything if the current box is being unchecked. I'm really not sure where's the problem. This is my first experience with JavaScript...
function verify_selected(selected_check_box_id) {
var count = 0;
var selected_check_boxes = new Array();
var check_boxes = new Array();
var inputs = document.getElementsByTagName("input");
for( var i in inputs ) {
if( inputs[i].type == "checkbox" ) check_boxes.push( inputs[i] );
}
// get current checkbox
for( var i in check_boxes ) if( check_boxes[i].id == selected_check_box_id ) var current_check_box = check_boxes[i];
var current_check_box_is_checked = current_check_box.checked;
// get all "checked"
for( var i in check_boxes ) {
if( check_boxes[i].checked ) {
selected_check_boxes.push( check_boxes[i] );
count += 1;
}
}
if( current_check_box_is_checked ) {
// checking
if( count < 4 ) {
current_check_box.checked = true;
// count = 4 - disabling
if( count == 4 ) {
for( var i in check_boxes ) {
if( !check_boxes[i].checked ) check_boxes[i].disabled = true;
}
}
}
else current_check_box.checked = false;
} else {
// unchecking
// count is < 4 -> enabling
for( var i in check_boxes ) {
check_boxes[i].disabled = false;
}
}
}
Any help is welcome,
thanks in advance.
There were a couple of things wrong. Lets give the good version first.
I also put up a demo at: http://jsbin.com/ajimi
function verify_selected(currentCheckbox) {
var count = 0;
var selected_check_boxes = []; // this will be fine...
var check_boxes [];
var inputs = document.getElementsByTagName("input");
for( var i in inputs ) {
if( inputs[i].type == "checkbox" ) check_boxes.push( inputs[i] );
}
// get all "checked"
for( var i in check_boxes ) {
if( check_boxes[i].checked ) {
count += 1;
}
}
if( currentCheckbox.checked && (count == 4)) {
for( var i in check_boxes )
if( !check_boxes[i].checked )
check_boxes[i].disabled = true;
} else {
for( var i in check_boxes )
check_boxes[i].disabled = false;
}
}
In the original version, you've got a piece of code which looked like:
if (count < 4) {
if (count == 4) {
Not gonna happen. So, that was corrected.
As you saw also in another answer, we changed the function to take out looking for an ID. Rather than figuring out the ID in some separate function (I assume you're tracking the "last clicked" by some other function which occurs), just use the this modifier to pass it into the function.
Alright, last but not least, what this would look like in jQuery. Hopefully this will help a little as to understanding how it works and why it's worth using:
(see example: http://jsbin.com/ihone)
function limitSelected(e) {
// get all of your checkboxes
var checkBoxes = $(e.currentTarget).parent().children().filter('input:checkbox');
// get the number of checkboxes checked, if 4, we'll disable
var disableCheckBoxes = (checkBoxes.filter(':checked').length == 4);
// enable checkboxes if we have < 4, disable if 4
checkBoxes.filter(':not(:checked)').each(function() {
this.disabled = disableCheckBoxes;
});
}
// when the document is ready, setup checkboxes to limit selection count
// if you have a particular div in which these checkboxes reside, you should
// change the selector ("input:checkbox"), to ("#yourDiv input:checkbox")
$(function() {
$('input:checkbox').click(limitSelected);
});
The other thing I will note about this version is that it works on the group of checkboxes within a div, as opposed to your version which will pick up checkboxes on the entire page. (which is limiting.
From a brief skim, your code seems much too complex for the task.
Can I suggest using something like jquery? You can quite easily select the relevant check boxes using the psudeo-selector ':checked'. Also, have a look at this check box tutorial.
If you don't want to use a library, I'd suggest first creating a function that can count the number of checked check boxes. Then create a function that can disable or enable all unchecked check boxes. Finally, combine the two, and register a function to trigger on the click event for the check boxes.
As cofiem said, your code looks rather complex for what you want to achieve; I recommend breaking it down into a few, smaller functions, to re-use code and make less complex.
First, implement a function to get all of the checkboxes on the page:
function getCheckboxes()
{
var inputs = document.getElementsByTagName("input");
var checkboxes = new Array();
for(var i=0;i<inputs.length;++i) {
if(inputs[i].type=="checkbox")
checkboxes.push(inputs[i]);
}
return checkboxes;
}
Then a function to enable/disable the checkboxes:
function setDisabled(state) {
var checkboxes = getCheckboxes();
for(i=0;i<checkboxes.length;++i) {
//Only unchecked checkboxes will need to be enabled/disabled
if(!checkboxes[i].checked)
checkboxes[i].disabled = state;
}
}
Now implement your function to verify whether the checkboxes need to be enabled or disabled:
function verify_selected(checkbox) {
var checkboxes = getCheckboxes();
var count=0;
for(i=0;i<checkboxes.length;++i) {
if(checkboxes[i].checked)
count++;
}
if(count>=4)
setDisabled(true);
else
setDisabled(false);
}
I have changed your function declaration to pass the actual checkbox object rather than an identifier string; this is much easier to call the function:
<input type="checkbox" onClick="verify_selected(this);">
//Insert 7 of these..
As you can see the code is much easier to read and maintain, and it is much less complex.

Categories

Resources