I am having difficulty in selecting only elements that contain a certain value.
if (autoCostBatches) {
if ($('#noCostRecovery').attr('checked')) {
var tester= "";
for (var i = 0; i < autoCostBatches2.length; i++) {
tester = tester + autoCostBatches2[i].items + ", ";
}
var userString = tester.substring(0, tester.length - 2);
console.log(userString);
element = $('td:not(:contains(' + userString + '))');
if (element.length) {
elementParent = element.parent();
checkBox = elementParent.children('td').children('input');
checkBox.prop('checked', true);
}
}
} else {
for (var i = 0; i < autoCostBatches.d.batches.length; i++) {
element = $('td:not(:contains(' + autoCostBatches.d.batches[i].items + '))');
found = $.inArray(element, test) > -1;
if (element.children().length && found === false) {
elementParent = element.parent();
checkBox = elementParent.children('td').children('input');
checkBox.prop('checked', false);
}
}
}
here is the printed userstring to console and appears to be right.
and here are the elements in a table. Note that there is a td element containing 630316 and 632848 split by a comma. This to me looks correct but when I run the function all checkboxes are being checked which should not happen.
Related
I'm kinda new to scripting and all and just wanted to know if anyone could help me edit my code to hide the whole table from the start and then just display the rows that match the keyword.
Right now what the code does is:
You enter a keyword in an inputbox
The script searches through a table looking for rows matching the keyword.
Hide all other rows.
But i would like it to work another way, but don't really know how to.
What i would like it to do is:
Display the inputbox only on the page.(Table is hidden)
Display the results that match the keyword without displaying the whole table.
Code:
var TableSearch = function(searchBoxId, dataTableId, options) {
this.searchBox = document.querySelector('#' + searchBoxId);
this.dataTable = document.querySelector('#' + dataTableId);
this.options = options;
}
TableSearch.prototype = {
init: function(searchBoxId, dataTableId, options) {
// defaults options
var settings = {
firstRowHeader: true,
highlightCss: "style='background-color:yellow'",
noResultsText: null
};
if (this.options) {
for (var key in settings) {
// Update settings if valid option and value was supplied.
if (this.options.hasOwnProperty(key) && (this.options[key] != null && this.options[key].toString() != '')) {
if (key == 'highlightCss') {
settings[key] = "class='" + this.options[key] + "'";
continue;
}
settings[key] = this.options[key];
}
}
}
this.options = settings;
this.searchBox.addEventListener('keyup', this.search.bind(this), false);
},
search: function(e) {
if(e.keyCode == 27) {
this.searchBox.value = ''; // Clear search on Esc
}
this.toggleNoResult('remove');
var keyword = this.escapeSpecialChars(this.searchBox.value);
var textSearchRegex = new RegExp(keyword, "ig"); // case in-sensitive
var rowDisplay, rowObj, rowHtml, match;
var firstRowIndex = (this.options.firstRowHeader == true) ? 1 : 0;
for (var rowIndex = firstRowIndex; rowIndex < this.dataTable.rows.length; rowIndex++) {
rowDisplay = '';
rowObj = this.dataTable.rows.item(rowIndex);
rowHtml = rowObj.innerHTML.replace(/<mark[^/>]*>/g,'').replace(/<\/mark>/g,''); // remove previous highlighting
if (keyword == '')
rowDisplay = 'table-row';
else {
match = rowHtml.replace(/<[^>]*>/g, '').match(textSearchRegex); // strip html tags and search for keyword
if(match) {
// Get unique matches: http://stackoverflow.com/a/21292834/1440057
match = match.sort().filter(function(element, index, array) { return index == array.indexOf(element); });
var tempHtml = rowHtml;
for (var i = 0; i < match.length; i++)
tempHtml = this.highlight(tempHtml, match[i]);
if (tempHtml.search(/<\/mark>/g) > -1) {
rowHtml = tempHtml;
rowDisplay = 'table-row';
}
else // Keyword did not match with any column content
rowDisplay = 'none';
}
else // Keyword did not match even in the row text content
rowDisplay = 'none';
}
rowObj.innerHTML = rowHtml;
rowObj.style.display = rowDisplay;
}
// Check if 'no results' row needs to be added
if (keyword != '' && this.options.noResultsText && this.dataTable.innerHTML.search(/style=\"display: table-row;\"/g) == -1)
this.toggleNoResult('add');
},
highlight: function(rowHtml, match) {
var row = document.createElement('tr');
row.innerHTML = rowHtml;
var textReplaceRegex = new RegExp(this.escapeSpecialChars(match), "g"); // case sensitive
var highlightMarkup = '<mark ' + this.options.highlightCss + '>' + match + '</mark>';
var cell = null;
var htmlOut = '';
for (var i = 0; i < row.cells.length; i++) {
cell = row.cells.item(i);
// Highlighting works only for direct text content, not nested tags.
// e.g. searching "blog" in <td>my blog</td> won't work.
if (cell.children.length == 0) {
if (cell.textContent.indexOf(match) > -1) {
// Match found in this cell, highlight it
htmlOut += '<td>' + cell.textContent.replace(textReplaceRegex, highlightMarkup) + '</td>';
continue;
}
}
htmlOut += '<td>' + cell.innerHTML + '</td>';
}
return htmlOut;
},
escapeSpecialChars: function(inStr) {
return inStr.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
},
toggleNoResult: function(mode) {
var noResultsRow;
if (mode == 'add') {
noResultsRow = this.dataTable.insertRow(this.dataTable.rows.length);
noResultsRow.setAttribute('id', 'noResultsRow');
var noResultsRowCell = noResultsRow.insertCell(0);
noResultsRowCell.setAttribute('colspan', this.dataTable.rows[0].cells.length);
noResultsRowCell.setAttribute('align', 'center');
noResultsRowCell.textContent = this.options.noResultsText;
}
else if (mode == 'remove') {
noResultsRow = this.dataTable.querySelector('#noResultsRow');
if (noResultsRow != null) {
this.dataTable.deleteRow(this.dataTable.rows.length - 1);
}
}
}
}
Thanks in advance :)
I wan to collect all text from a list of elements obtains using
var elements =document.body.getElementsByTagName("*");
What I've done so far:
var text = '';
for (var i = 0; i < elements.length; i++) {
text = text + ' ' + elements[i].innerText
}
This will return duplicated text because it get the own text of each element plus its children's. I want to know if there is a way to get element's owntext using pure javasript?
I think the issue is that nested matching elements of a particular tag are being counted twice. The solution is to check if we've already visited a parent element and to skip the child if that's the case.
var text = '';
var visited = [];
for (var i = 0; i < elements.length; i++) {
var found = false;
for (var e = elements[i]; e != null; e = e.parentNode) {
if (visited.indexOf(e) > -1) {
found = true;
break;
}
}
if (!found) {
text = text + ' ' + elements[i].innerText;
visited.push(elements[i]);
}
}
http://jsfiddle.net/h8k0xx82/
var a = document.querySelectorAll('.post .content div');
var b = a[7].childNodes;
for(i=0;i<b.length;i++){
var exp = /(\b(https?|ftp|file):\/\/[\-A-Z0-9+&##\/%?=~_|!:,.;]*[\-A-Z0-9+&##\/%=~_|])/ig;
if(b[i].nodeType === 3){
var ahref = document.createElement('a');
ahref.className="easyBBurlFetch";
ahref.href=b[i].nodeValue.replace(exp,'$1');
ahref.innerText=b[i].nodeValue.replace(exp,'$1');
b[i].parentNode.insertBefore(ahref,b[i]);
b[i].parentNode.removeChild(b[i].nextSibling);
}
}
Someone gave me the answer as I had this code though it wasn't working correct. Though I have the issue now if my text is like so:
This is just a test so click here www.youtube.com which then becomes
www.youtube.com%20which%20then%20becomes
It doesn't event keep the first line of text, I just need to parse the url while keeping the surrounding text.
In need the output to save the actual surrounding text but parse the urls that are inside the text to html anchor tags <a> so that they can then be clickable and actually follow through to a real website and not have unnessarcy text inside it from what my user was writing about. Thank you
UPDATE
I've got closer to making this work-- But I'm having a problem with the first text in the string is saying Undefined I've been debugging this and can't seem to figure out why this is happening. Here is code
var a = document.querySelectorAll('.post');
var b = a[0].childNodes;
var textArray;
var ahref;
for (i = 0; i < b.length; i++) {
var exp = /(\b(https?|ftp|file):\/\/[\-A-Z0-9+&##\/%?=~_|!:,.;]*[\-A-Z0-9+&##\/%=~_|])/ig;
if (b[i].nodeType === 3) {
var newHTML;
textArray = b[i].textContent.split(" ");
for (var j = 0; j < textArray.length; j++) {
if (textArray[j] !== "" && validURL(textArray[j])) {
ahref = document.createElement('a');
ahref.href = (/^(http:\/\/|https:\/\/)/).test(textArray[j]) ? textArray[j] : "http://" + textArray[j];
ahref.innerText = textArray[j];
ahref.className = "easyURLparse";
textArray[j] = ahref;
}
newHTML+= textArray[j].outerHTML ? textArray[j].outerHTML + " " : textArray[j] + " ";
}
var div = document.createElement('div');
div.innerHTML = newHTML;
newHTML = "";
b[i].parentNode.insertBefore(div, b[i]);
b[i].parentNode.removeChild(b[i].nextSibling);
}
}
function validURL(str) {
var pattern = new RegExp("([a-zA-Z0-9]+://)?([a-zA-Z0-9_]+:[a-zA-Z0-9_]+#)?([a-zA-Z0-9.-]+\\.[A-Za-z]{2,4})(:[0-9]+)?(/.*)?");
if (!pattern.test(str)) {
return false;
} else {
return true;
}
}
Testing Code
Just need to figure out the undefined and why it's adding it
this regexp will do the job
exp = /href="(\b(https?|ftp|file):\/\/[\-A-Z0-9+&##\/%?=~_|!:,.;]*[\-A-Z0-9+&##\/%=~_|])"/ig;
var a = document.querySelectorAll('.post');
var b = a[0].childNodes;
var textArray;
var ahref;
for (i = 0; i < b.length; i++) {
var exp = /(\b(https?|ftp|file):\/\/[\-A-Z0-9+&##\/%?=~_|!:,.;]*[\-A-Z0-9+&##\/%=~_|])/ig;
if (b[i].nodeType === 3) {
var newHTML;
if (validURL(b[i].textContent)) {
textArray = b[i].textContent.split(" ");
for (var j = 0; j < textArray.length; j++) {
if (textArray[j] !== undefined && textArray[j] !== "" && validURL(textArray[j]) && textArray[j] !== null) {
ahref = document.createElement('a');
ahref.href = (/^(http:\/\/|https:\/\/)/).test(textArray[j]) ? textArray[j] : "http://" + textArray[j];
ahref.innerText = textArray[j];
ahref.className = "easyURLparse";
textArray[j] = ahref;
}
newHTML += textArray[j].outerHTML ? textArray[j].outerHTML + " " : textArray[j] + " ";
}
var div = document.createElement('div');
div.innerHTML = newHTML;
div.className = "easyDiv";
b[i].parentNode.insertBefore(div, b[i]);
b[i].parentNode.removeChild(b[i].nextSibling);
}
newHTML = "";
}
}
function validURL(str) {
var pattern = new RegExp("([a-zA-Z0-9]+://)?([a-zA-Z0-9_]+:[a-zA-Z0-9_]+#)?([a-zA-Z0-9.-]+\\.[A-Za-z]{2,4})(:[0-9]+)?(/.*)?");
if (!pattern.test(str)) {
return false;
} else {
return true;
}
}
By taking the textNodes and splitting them into an array I can then change the url into an html element. Then taking the array elements seeing if there is outerHTML or not then placing it in a new string and replacing that textNode now with a workable link.
Working example
I have adapted the following code from a tutorial to filter li elements based upon their contents:
$('#_selectSearch_' + index).keyup(function() {
var filter = $(this).val();
if(filter) {
$('#_selectDrop_' + index).find("li:not(:contains(" + filter + "))").slideUp();
$('#_selectDrop_' + index).find("li:contains(" + filter + ")").slideDown();
} else {
$('#_selectDrop_' + index).find("li").slideDown();
}
});
The code works just fine but when working with large lists is very slow bringing the browser to a grinding halt for seconds with every key-press. I have been looking around and have come to the conclusion that the way to improve this is to somehow cache the list and not operate directly on the DOM but have no idea how to implement this.
If your main concern is performance the following code:
caches element containing filter string.
caches li elements.
doesn't show or hide elements that are already in that state.
uses indexOf which is very fast.
if the user types letters under 500 milliseconds apart the showMatches will not run.
var selectSearch = $("#_selectSearch_" + index );
var li = $("#_selectDrop_" + index + " li");
var currentTimeout;
selectSearch.on( "keyup", function( ) {
if( currentTimeout ) { window.clearTimeout( currentTimeout ) };
currentTimeout = setTimeout( showMatches, 500 );
});
function showMatches( ) {
var txt = selectSearch.val();
for( var i = 0, len = li.length; i < len; i++ ) {
var content = li[i].textContent ? li[i].textContent : li[i].innerText;
if( txt && content.indexOf( txt ) > -1) {
if( li[i].style.display !== "block" ) {
li[i].style.display = "block";
}
} else {
if( li[i].style.display !== "none" ) {
li[i].style.display = "none";
}
}
}
}
Fiddle with 400 li elements here
You can cache this element $('#_selectDrop_' + index + ' li');
$('#_selectSearch_' + index).keyup(function() {
var $li = $('#_selectDrop_' + index + ' li');
var filter = $(this).val();
if (filter) {
$li.not(":contains(" + filter + ")").slideUp();
$li.contains(filter).slideDown();
} else {
$li.slideDown();
}
});
drop = $('#_selectDrop_' + index + ' li');
$('#_selectSearch_' + index).keyup(function() {
var filter = $(this).val();
if(filter) {
drop.find(":not(:contains(" + filter + "))").slideUp();
drop.find(":contains(" + filter + ")").slideDown();
} else {
drop.slideDown();
}
});
Drop will be cached just once, and then will be used at every keyup. Also this uses the minimum possible of find
$('#_selectSearch_' + index).keyup(function() {
var filter = $(this).val();
// by combining and cacheing all the way to the li
// we save a lot of time, since it seems that's where you are doing
// all your searching from
var selectDrop = $('#_selectDrop_' + index + ' li');
if (filter) {
selectDrop.not(':contains("' + filter + '")').slideUp();
selectDrop.contains(filter).slideDown();
}
else {
selectDrop.slideDown();
}
});
I'll give it a go with a somewhat modified (and untested) version:
$('#_selectSearch_' + index).on('keyup', function() {
var filter = this.value,
lis = document.getElementById('_selectDrop_' + index).getElementsByTagName('li'),
len = lis.length,
sup = 'textContent' in this;
if (filter.length) {
for (var i = len; i--) {
var text = sup ? lis[i].textContent : lis[i].innerText;
$(lis[i])[text.indexOf(filter) != -1 ? 'slideDown' : 'slideUp']();
}
} else {
$(lis).slideDown();
}
});
I am having issues figuring out how to resolve the getElementsByClassName issue in IE. How would I best implement the robert nyman (can't post the link to it since my rep is only 1) resolution into my code? Or would a jquery resolution be better? my code is
function showDesc(name) {
var e = document.getElementById(name);
//Get a list of elements that have a class name of service selected
var list = document.getElementsByClassName("description show");
//Loop through those items
for (var i = 0; i < list.length; ++i) {
//Reset all class names to description
list[i].className = "description";
}
if (e.className == "description"){
//Set the css class for the clicked element
e.className += " show";
}
else{
if (e.className == "description show"){
return;
}
}}
and I am using it on this page dev.msmnet.com/services/practice-management to show/hide the description for each service (works in Chrome and FF). Any tips would be greatly appreciated.
I was curious to see what a jQuery version of your function would look like, so I came up with this:
function showDesc(name) {
var e = $("#" + name);
$(".description.show").removeClass("show");
if(e.attr("class") == "description") {
e.addClass("show");
} else if(e.hasClass("description") && e.hasClass("show")) {
return;
}
}
This should support multiple classes.
function getElementsByClassName(findClass, parent) {
parent = parent || document;
var elements = parent.getElementsByTagName('*');
var matching = [];
for(var i = 0, elementsLength = elements.length; i < elementsLength; i++){
if ((' ' + elements[i].className + ' ').indexOf(findClass) > -1) {
matching.push(elements[i]);
}
}
return matching;
}
You can pass in a parent too, to make its searching the DOM a bit faster.
If you want getElementsByClassName('a c') to match HTML <div class="a b c" /> then try changing it like so...
var elementClasses = elements[i].className.split(/\s+/),
matchClasses = findClass.split(/\s+/), // Do this out of the loop :)
found = 0;
for (var j = 0, elementClassesLength = elementClasses.length; j < elementClassesLength; j++) {
if (matchClasses.indexOf(elementClasses[j]) > -1) {
found++;
}
}
if (found == matchClasses.length) {
// Push onto matching array
}
If you want this function to only be available if it doesn't already exist, wrap its definition with
if (typeof document.getElementsByClassName != 'function') { }
Even easier jQuery solution:
$('.service').click( function() {
var id = "#" + $(this).attr('id') + 'rt';
$('.description').not(id).hide();
$( id ).show();
}
Why bother with a show class if you are using jQuery?
Heres one I put together, reliable and possibly the fastest. Should work in any situation.
function $class(className) {
var children = document.getElementsByTagName('*') || document.all;
var i = children.length, e = [];
while (i--) {
var classNames = children[i].className.split(' ');
var j = classNames.length;
while (j--) {
if (classNames[j] == className) {
e.push(children[i]);
break;
}
}
}
return e;
}
I used to implement HTMLElement.getElementByClassName(), but at least Firefox and Chrome, only find the half of the elements when those elements are a lot, instead I use something like (actually it is a larger function):
getElmByClass(clm, parent){
// clm: Array of classes
if(typeof clm == "string"){ clm = [clm] }
var i, m = [], bcl, re, rm;
if (document.evaluate) { // Non MSIE browsers
v = "";
for(i=0; i < clm.length; i++){
v += "[contains(concat(' ', #"+clc+", ' '), ' " + base[i] + " ')]";
}
c = document.evaluate("./"+"/"+"*" + v, parent, null, 5, null);
while ((node = c.iterateNext())) {
m.push(node);
}
}else{ // MSIE which doesn't understand XPATH
v = elm.getElementsByTagName('*');
bcl = "";
for(i=0; i < clm.length; i++){
bcl += (i)? "|":"";
bcl += "\\b"+clm[i]+"\\b";
}
re = new RegExp(bcl, "gi");
for(i = 0; i < v.length; i++){
if(v.className){
rm = v[i].className.match(bcl);
if(rm && rm.length){ // sometimes .match returns an empty array so you cannot use just 'if(rm)'
m.push(v[i])
}
}
}
}
return m;
}
I think there would be a faster way to iterate without XPATH, because RegExp are slow (perhaps a function with .indexOf, it shuld be tested), but it is working well
You can replace getElementsByClassName() with the following:
function getbyclass(n){
var elements = document.getElementsByTagName("*");
var result = [];
for(z=0;z<elements.length;z++){
if(elements[z].getAttribute("class") == n){
result.push(elements[z]);
}
}
return result;
}
Then you can use it like this:
getbyclass("description") // Instead of document.getElementsByClassName("description")