Merging css classes with jQuery/Javascript - javascript

I have a problem. I have a bunch webpage that makes heavy use of multiple css classes.
<div class="class1 class2 class3">foo</div>
Unfortunately, I have a "browser" (for lack of a better term) that can not handle multiple css classes in that manner.
I can identify all the elements with multiple classes but now I need to create new classes that merge them. First attempt was to inline all the styles into the style attribute, however that was far too slow, and bloated the document needlessly.
What I now want to do is find an element with multiple classes. Create a new class which is a combination, and replace the elements class with the newly created one, as well as any other elements with the same class combination.
Any thoughts on how best to approach this.

Loop through all tags. Split the class names into an array. Sort it to get it into a predictable order. Join the string back together.
$(document).ready(function() {
var classList = {};
$("*").each(function() {
var temp;
if (this.className) {
temp = this.className.split(" "); // split into array
temp.sort(); // put in predictable order
this.className = temp.join(""); // put class name back without spaces
classList[this.className] = true; // add to list
}
});
// classList object contains full list of all classNames used
});
FYI, it seems really odd that you'd have a browser that supports jQuery, but doesn't support CSS styles for multiple class names. You do realize that you are going to have to supply completely different stylesheets that work off the concatenated names, right? And, if you can change the stylesheets, it makes me wonder why you can't change the HTML.
Working implementation: http://jsfiddle.net/jfriend00/uPET7/

Summary: This function returns an ordered list of all duplicate class names, which can easily be used to merge classes.
To start off, get a useful list of duplicates:
var multi = {};
$("*[class]").each(function(){
var class = this.className.replace(/^\s+|\s+$/g,"").replace(/\s+/g,".");
if(!/\./.test(class)) return; //Ignore single classes
if(multi[class]){
multi[class]++;
} else {
multi[class] = 1;
}
});
//Now, merge duplicates, because .class1.class2 == .class2.class1
var multi_nodup = {};
for(var classes in multi){
var a_classes = classes.split(".");
var a_classes = a_classes.sort();
var a_classes = a_classes.join(".");
if(multi_nodup[a_classes]){
multi_nodup[a_classes] += multi[classes];
} else {
multi_nodup[a_classes] = multi[classes]
}
}
//Now, multi_npdup is a map of all duplicate classnames
var array_multi = [];
for(var classes in multi_nodup){
array_multi.push([multi_nodup[classes], classes]);
}
array_multi.sort(function(x,y){return y[0]-x[0]});
//array_multi is an array which looks like [["class1.class2.class2", 33],
// ["class3.class4", 30], ...]
// = A list, consisting of multiple class names, where multiple classnames
// are shown, together with the nuber of occurences, sorted according to
// the frequence
Execute my function, and output variable array_multi. This will show you a map of multiple class names, so that you can replace multiple classnames, accordingly.
Because of the special way I stored the class names, you can use $("." + array_multi[n][0]) to access all elements which have a set of classname which equals to the set as described at the nth position in array_multi.
Example of readable output:
//Overwrites current document!
var list = "";
for(var i=0; i<array_multi.length; i++) list += array_multi[i][0] + "\t" + array_multi[i][1];
document.open();
document.write("<pre>"+list+"</pre>")
document.close();
Automatic conversion
A way to automate the merging of the classnames i by adding all separate class properties to a JavaScript string, and add it to an object. This is the most reliable way to get the exact CSS properties, because attempting to get the classnames through the document.styleSheets object can produce slightly different results. Example:
var classStyle = {};
classStyle["class1"] = "border:1px solid #000;";
classStyle["class2"] = "color:red";
//Make sure that each declaration ends with a semicolon:
for(var i in classStyle) if(!/;$/.test(classStyle[i])) classStyle[i] += ";";
//Initialise
var all_styles = {};
for(var i=0; i<array_multi.length; i++){
all_styles[array_multi[i][1]] = "";
}
//This loop takes definition precedence into account
for(var currentCName in classStyle){
var currentClass = new RegExp("(?:^|\\.)" + currentCName + "(?:\\.|$)");
// Rare occasion of failure: url("data:image/png,base64;....")
var separateProps = classStyle[currentCName].split(";");
var prop_RE = {};
for(var p=0; p<separateProps.length; p++){
var cssProperty = separateProps[p];
if(!/:/.test(cssProperty)) continue; //Invalid CSS property
prop_RE[cssProperty] = new RegExp("(^|;)\\s*" + cssProperty.match(/(\S+)\s*:/gi)[1] + "\\s*:[^;]+;?", "gi");
}
for(var class in all_styles){
if(currentClass.test(class)){
for(var k in prop_RE){
all_styles[class] = all_styles[class].replace(prop_RE[k],"$1") + k;
}
}
}
}
//To finish off:
var allClassesToString = "";
for(var class in all_styles){
var newClass = class.replace(/\./g, "_");
$("."+class).each(function(){
this.className = newClass;
});
allClassesToString += "."+newClass + "{" + all_styles[class] + "}\n";
}
// allClassesToString <------- This variable now holds a string of all duplicate CSS classes!
//Example:
var style = $("<style>");
style.text(allClassesToString);
style.appendTo($("head:first"));

Does not seem to crazy to accomplish this,
Loop through every element that has more than 1 class. Sort the classes (doesn't matter how as long as it is consistent) then merge them together to create the new class. Keep a list of all new css classes and check against them in case of duplicates.
To get all the styles from an element see here

Related

Javascript code doesn't load the page

I have a little problem with this javascript code, when I add more site on the list, the page doesn't load. I have to add more than 200 site.
I'm a noob with javascript. Can someone explain what is the problem, what
I'm doing wrong?
<script language="JavaScript" type="text/javascript">
var a = new Array(
'notiziepericolose.blogspot.it',
'ilcorrieredellanotte.it',
'ilmattoquotidiano.it',
'ilfattonequotidiano.com',
'rebubblica.altervista.org',
'coriere.net'
);
var aa = a.slice();
aa.sort();
document.write("<ol>");
document.write("<b>");
for (i = 0; i < a.length; i=i+1) {
document.write('<li id="demo'+i+'">'+a[i]+'</li>');
}
document.write("</b>");
document.write("</ol>");
</script>
I guess the first thing is that document.write is very rarely used now as there a better and more efficient ways of adding things (elements, text etc) to the DOM (more on that later). In addition, in your case, what you don't realise is that document.write is not like echo or println; each time it is used it clears the document, which is probably why you're not seeing anything appear. In other words, The results of multiple document.writes are not cumulative.
The second thing is that there are better ways of "labelling" elements than with ids, particularly if there are a lot of them on the page like you'll have. Again, there are now much better ways of targetting elements, or catching events than there were ten or fifteen years ago.
So, let's talk about your code.
You can quickly create a array using the [] brackets.
var arr = [
'notiziepericolose.blogspot.it',
'ilcorrieredellanotte.it',
'ilmattoquotidiano.it',
'ilfattonequotidiano.com',
'rebubblica.altervista.org',
'coriere.net'
];
You don't have to create a copy of the array in order to sort it - it can be done in place:
arr.sort();
I'm going to keep your loop but show you a different way of concatenating strings together. Some people prefer adding strings together, but I prefer this way, and that's to create an array of the little parts of your string and then join() them together**.
// Set l as the length, and create an output array called list
for (var i = 0, l = arr.length, list = []; i < l; i++) {
// I've changed things here. I've added a class called item
// but also changed the element id to a data-id instead
var li = ['<li class="item" data-id="', i, '">', arr[i], '</li>'];
// Push the joined li array of strings into list
list.push(li.join(''));
}
Assuming you have an element on your page called "main":
HTML
<div id="main"></div>
JS
You can add the list array as an HTML string to main by using [insertAdjacentHTML] method:
var main = document.getElementById('main');
// Note that I've given the ordered list an id called list
var HTML = ['<ol id="list"><b>', list.join(''), '</b></ol>'].join('');
main.insertAdjacentHTML('beforeend', html);
OK, so that's pretty easy. But I bet you're asking how you can target the individual items in the list so that if I click on one of them it alerts what it is (or something).
Instead of adding an event listener to each list item (which we could but it can work out performatively expensive the more items you have), we're going to attach one to the ol element we added that list id to and catch events from the items as they bubble up:
var ol = document.getElementById('list');
Then an event listener is added to the list that tells us what function (checkItem) is called when a click event is raised:
ol.addEventListener('click', checkItem);
Our function uses the event (e) to find out what the event's target was (what item was clicked), and alerts its text content.
function checkItem(e) {
alert(e.target.textContent);
}
You can see all this working in this demo. Hope some of this was of some help.
** Here's another way of sorting, and looping through the array using reduce:
var list = arr.sort().reduce(function (p, c, i) {
return p.concat(['<li class="item" data-id="', i, '">', c, '</li>']);
}, []).join('');
DEMO
if ES6 is possible for you, you can do it like this:
var a = new Array(
'notiziepericolose.blogspot.it',
'ilcorrieredellanotte.it',
'ilmattoquotidiano.it',
'ilfattonequotidiano.com',
'rebubblica.altervista.org',
'coriere.net');
var aa = a.slice();
var mL = document.getElementById('mylist');
aa.sort().map(el => {
var li = document.createElement("li");
var b = document.createElement("b");
var t = document.createTextNode(el);
b.appendChild(t);
li.appendChild(b);
mL.appendChild(li);
});
<ol id="mylist"></ol>
If you're using an Array, you can use a forEach instead of a loop.
var domUpdate = '';
var websites = ['notiziepericolose.blogspot.it','ilcorrieredellanotte.it','ilmattoquotidiano.it','ilfattonequotidiano.com','rebubblica.altervista.org','coriere.net'];
websites.forEach(function(website, index){
domUpdate += '<li id="website-' + ( index + 1 ) + '"><b>' + website + '</b></li>';
});
document.getElementById('hook').innerHTML = '<ol>' + domUpdate + '</ol>';
<div id="hook"></div>
I'm thinking document.write is the wrong choice here, as it seems to be clearing the document. https://developer.mozilla.org/en-US/docs/Web/API/Document/write Probably you want to bind the new content to existing html through document.getElementById or something like that

Assigning javascript array elements class or id for css styling

I'm trying to assign class and id to items in an array I created in js and input into my html. I'm doing this so I can style them in my stylesheet. Each item will not be styled the same way.
I'm a beginner so trying to keep it to code I can understand and make it as clean as possible, i.e. not making each of these items an element in the html.
This part works fine:
var pool =['A','B','3','J','R','1','Q','F','5','T','0','K','N','C','R','U']
var letters = pool.join('');
document.getElementById('key').innerHTML = letters;
This part not so much:
var char1 = letters[1];
char1.classList.add('hoverRed');
There is a similar question here that didn't work for me, it just showed [object][object][object] when I ran it.
Your code attempts to apply a style to an array element, but CSS only applies to HTML. If you wish to style one character in a string, that character must be wrapped in an HTML element (a <span> is the best choice for wrapping an inline value).
This code shows how to accomplish this:
var pool =['A','B','3','J','R','1','Q','F','5','T','0','K','N','C','R','U']
var letters = pool.join('');
// Replace a specific character with the same character, but wrapped in a <span>
// so it can be styled
letters = letters.replace(letters[1], "<span>" + letters[1] + "</span>");
// Insert the letters string into the div
var theDiv = document.getElementById('key');
// Inject the string into the div
theDiv.innerHTML = letters;
// Get a reference to the span:
var theSpan = theDiv.querySelector("span");
// Add the style to the <span> that wraps the character, not the character itself
theSpan.classList.add('hoverRed');
.hoverRed {
color:red;
}
<div id="key"></div>
And, this snippet shows how you could apply CSS to any letter:
var pool =['A','B','3','J','R','1','Q','F','5','T','0','K','N','C','R','U'];
// Leave the original array alone so that it can be manipulated any way needed
// in the future, but create a new array that wraps each array element within
// a <span>. This can be accomplished in several ways, but the map() array method
// is the most straight-forward.
var charSpanArray = pool.map(function(char){
return "<span>" + char + "</span>";
});
// Decide which character(s) need CSS applied to them. This data can come from anywhere
// Here, we'll just say that the 2nd and 5th ones should.
// Loop through the new array and on the 2nd and 5th elements, apply the CSS class
charSpanArray.forEach(function(element, index, array){
// Check for the particular array elements in question
if(index === 1 || index === 4){
// Update those strings to include the CSS
array[index] = element.replace("<span>","<span class='hoverRed'>");
}
});
// Now, turn the new array into a string
var letters = charSpanArray.join('');
// For diagnostics, print the string to the console just to see what we've got
console.log(letters);
// Get a reference to the div container
var theDiv = document.getElementById('key');
// Inject the string into the div
theDiv.innerHTML = letters;
.hoverRed {
color:red;
}
<div id="key"></div>
You're on the right track, but missed one key thing.
In your example, pool contains characters. When you combine them using join, you get a string. Setting that string as the innerHTML of an element doesn't give the string super powers, it's still just a string.
In order to get a classList, you need to change your letters into elements and work with them.
I've included an es6 example (and a working plunker) of how to get the functionality you want below.
let pool = ['A','B','3','J','R','1','Q','F','5','T','0','K','N','C','R','U']
const letterToElement = function(char) {
//Create the element
let e = document.createElement("SPAN");
//Create the text node
let t = document.createTextNode(char);
//Put the text node on the element
e.appendChild(t);
//Add the class name you want
e.className += "hoverRed";
return e;
};
//create your elements from your pool and append them to the "key" element
window.onload = function() {
let container = document.getElementById("key");
pool.map(l => letterToElement(l))
.forEach(e => container.appendChild(e));
}
https://plnkr.co/edit/mBhA60aUCEGSs0t0MDGu

Finding All CSS Rules (including "invalid rules") Via Javascript

I am trying to use document.styleSheets to add a shim for CSS calc functionality.
Basically I am looping over each stylesheet and it's subsequent rules and looking for calc to occur somewhere, and doing the calculation in JavaScript and applying it to the associated element.
This is MUCH faster than looping over every element on the page and trying to find if it has a failed calc rule, but the problem is I do not see a way to pull in errored CSS rules through this method. Both rules and cssRules arrays contain only CSS that the browsers considers valid.
My question is this, is there a way to get these invalid CSS values through this method?
The code I am using is below, and works, but only on browsers that support calc anyways, which is useless to me. I need to be able to get ALL of the css rules as they appeared in the loaded CSS document in order to make this work.
Is this possible or am I barking up an imaginary fantasy tree of cross-browser calc awesomeness?
calcShim = function(){
var stylesheets = document.styleSheets;
var calcs = [];
for (var i=0, j=stylesheets.length; i<j; i++){
var stylesheet = stylesheets[i];
var rules = stylesheet.cssRules;
if (rules && rules.length > 0){
for (var k=0, l=rules.length; k<l; k++){
var rule = rules[k];
var ruleText = String(rule.cssText);
if (ruleText.match("calc")){
calcs.push(ruleText);
};
};
}
};
for (var i=0, j=calcs.length; i<j; i++){
var string = calcs[i];
var reference = calcs[i].match(/^[^{]{1,}/g)[0];
var objects = $(reference);
for (var k=0, l=objects.length; k<l; k++){
var object = $(objects[k]);
var parent = object.parent();
if (parent.is(":visible")){
var parentWidth = parseInt(parent.width());
var percent = parseInt(string.match(/(?:calc\()([0-9]{1,3})(?=%)/)[1])/100;
var operator = string.match(/(?:calc[^)]{1,})([\+\-\*\/])(?=\s[0-9]{1,3}px)/)[1];
var value = parseInt(string.match(/(?:calc[^)]{1,}[\+\-\*\/]\s)([0-9]{1,3})(?=px)/)[1]);
var mathString = (parentWidth*percent) + " " + operator + " " + value;
var result = eval(mathString);
object.css({"width": result});
}
};
};
};
I am aware this code could be better/cleaner, I was just trying to come up with a proof of concept.

Looping over array and comparing to regex

So, I'll admit to being a bit of a JS noob, but as far as I can tell, this should be working and it is not.
Background:
I have a form with 3 list boxes. The list boxes are named app1, db1, and db2. I'm using javascript to allow the user to add additional list boxes, increasing the name tag for each additional select box.
When I add additional app named boxes, the value increments properly for each additional field. If I try to add addtional db named selects, it fails to recognize the 2nd tag on the first loop through the array. This causes me to end up with 2 elements named db2. On each subsequent tag, it is recognized properly and is properly incremented.
Here is the HTML for the db1 tag:
<select name="db1">
*options*
</select>
And db2:
<select name="db2">
*options*
</select>
The tags are identical. Here is the function that I am using to figure out the next number in the sequence (note: tag is either app or db, tags is an array of all select tag names in the DOM, if I inspect tags, it gives me ['app1', 'db1', 'db2', '']):
function return_select_name(tag, tags) {
matches = new Array();
var re = new RegExp(tag + "\\d+", "g");
for (var i = 0; i < tags.length; i++) {
var found = re.exec(tags[i]);
if (found != null) {
matches.push(found[0]);
}
}
matches = matches.sort();
index = parseInt(/\d+/.exec(matches.last())) + 1;
index = tag + index;
return index;
}
If I add an app tag, it will return 'app2'. If I search for a db tag, it will return 'db2' on the first time through, db3 on the 2nd, etc, etc.
So basically, I'm sure I'm doing something wrong here.
I'd handle it by keeping a counter for db and a counter for app to use to generate the names.
var appCounter = 1;//set this manually or initialize to 0 and
var dbCounter = 2;//use your create function to add your elements on pageload
Then, when you go to create your next tag, just increment your counter and use that as the suffix for your name:
var newAppElement = document.createElement('select');
newAppElement.name = 'app' + (++appCounter);
..
// --OR for the db element--
var newDbElement = document.createElement('select');
newDbElement.name = 'db' + (++dbCounter );
..
The problem you are getting is that regex objects are stateful. You can fix your program by putting the regex creation inside the loop.
function return_select_name(tag, tags) {
matches = new Array();
// <-- regex was here
for (var i = 0; i < tags.length; i++) {
var re = new RegExp(tag + "\\d+", "g"); //<--- now is here
var found = re.exec(tags[i]);
if (found != null) {
matches.push(found[0]);
}
}
matches = matches.sort();
index = parseInt(/\d+/.exec(matches[matches.length-1])) + 1; //<--- I dont think matches.last is portable, btw
index = tag + index;
return index;
}
In any case, if I were to do this myself, I would probably prefer to avoid the cmplicated text matching and just store the next tag indices in a variable or hash map.
Another suggestion: if you put parenthesis in your regex:
// /tag(\d+)/
var re = new RegExp(tag + "(\\d+)", "g");
Then you can use found[1] to get your number directly, without the extra step afterwards.
I know this has already been answered, but I put this together as a proof of concept.
http://jsfiddle.net/zero21xxx/LzyTf/
It's an object so you could probably reuse it in different scenarios. Obviously there are ways it could be improved, but I thought it was cool so I thought I would share.
The console.debug only works in Chrome and maybe FF.

javascript - get all anchor tags and compare them to an array

I have been trying forever but it is just not working, how can I check the array of urls I got (document.getElementsByTagName('a').href;) to see if any of the websites are in another array?
getElementByTagName gives you a nodelist (an array of nodes).
var a = document.getElementsByTagName('a');
for (var idx= 0; idx < a.length; ++idx){
console.log(a[idx].href);
}
I really suggest that you use a frame work for this, like jquery. It makes your life so much easier.
Example with jquery:
$("a").each(function(){
console.log(this.href);
});
var linkcheck = (function(){
if(!Array.indexOf){
Array.prototype.indexOf = function(obj){
for(var i=0; i<this.length; i++){
if(this[i]===obj){
return i;
}
}
return -1;
}
}
var url_pages = [], anchor_nodes = []; // this is where you put the resulting urls
var anchors = document.links; // your anchor collection
var i = anchors.length;
while (i--){
var a = anchors[i];
anchor_nodes.push(a); // push the node object in case that needs to change
url_pages.push(a.href); // push the href attribute to the array of hrefs
}
return {
urlsOnPage: url_pages,
anchorTags: anchor_nodes,
checkDuplicateUrls: function(url_list){
var duplicates = []; // instantiate a blank array
var j = url_list.length;
while(j--){
var x = url_list[j];
if (url_pages.indexOf(x) > -1){ // check the index of each item in the array.
duplicates.push(x); // add it to the list of duplicate urls
}
}
return duplicates; // return the list of duplicates.
},
getAnchorsForUrl: function(url){
return anchor_nodes[url_pages.indexOf(url)];
}
}
})()
// to use it:
var result = linkcheck.checkDuplicateUrls(your_array_of_urls);
This is a fairly straight forward implementation of a pure JavaScript method for achieving what I believe the spec calls for. This also uses closures to give you access to the result set at any time, in case your list of urls changes over time and the new list needs to be checked. I also added the resulting anchor tags as an array, since we are iterating them anyway, so you can change their properties on the fly. And since it might be useful to have there is a convenience method for getting the anchor tag by passing the url (first one in the result set). Per the comments below, included snippet to create indexOf for IE8 and switched document.getElementsByTagName to document.links to get dynamic list of objects.
Using Jquery u can do some thing like this-
$('a').each(function(){
if( urls.indexOf(this.href) !- -1 )
alert('match found - ' + this.href );
})
urls is the your existing array you need to compare with.

Categories

Resources