JS: Replace a link with another word. Nested quotes + escape codes - javascript

Begin with a fresh plain HTML document, and only use HTML and Javascript.
Place on it the hyperlinked word ''food''
Upon clicking ''food'', it should be replaced with ''meat and vegetables''
Upon clicking ''meat'', it should be replaced with ''pork with bacon''
Upon clicking ''vegetables'', it should be replaced with ''carrots plus peas''
Upon clicking ''pork'', it should be replaced with ''tough and chewy''
Upon clicking ''tough'', it should be replaced with ''burnt and salty''
(And so on)
I've been trying to do this as far as I can, but I'm having escapecode problems.
Here is my code:
<span id="food">food</span>
Here it is in action: http://jsfiddle.net/jshflynn/L6r5rrfx/
I'm sorry it's not spaced, but that threw up errors.
Notice that ''alert(2)'' has no delimiting characters around it, I don't know how to make it say alert(''Hello'').
I feel there must be some recursive way to do this, but I'm not sure.
Thanks in advance. Especially so if you can do the full problem.

Here you go, you get the idea: http://jsfiddle.net/8bhd8njh/
function bind(obj, evt, fnc) {
// W3C model
if (obj.addEventListener) {
obj.addEventListener(evt, fnc, !1);
return !0;
}
// Microsoft model
else if (obj.attachEvent) {
return obj.attachEvent('on' + evt, fnc);
}
// Browser don't support W3C or MSFT model, go on with traditional
else {
evt = 'on'+evt;
if(typeof obj[evt] === 'function'){
// Object already has a function on traditional
// Let's wrap it with our own function inside another function
fnc = (function(f1,f2){
return function(){
f1.apply(this,arguments);
f2.apply(this,arguments);
}
})(obj[evt], fnc);
}
obj[evt] = fnc;
return !0;
}
}
String.prototype.supplant = function (a, b) {
return this.replace(/{([^{}]*)}/g, function (c, d) {
return void 0!=a[d]?a[d]:b?'':c
})
};
var data = {
food : '{meat} and {vegetables}',
meat : '{pork} and {beef}',
pork : '{tough} and {chewy}',
tough : '{burnt} and {salty}',
vegetables : '{carrots} and {peas}'
};
var classname = 'game-clickable';
var init = function(obj, data) {
var template = '<span class="{classname}">{text}</span>';
obj.innerHTML = obj.innerHTML.replace(/{([^{}]*)}/g, function(a,b) {
return template.supplant({
classname : data[b] ? classname : '',
text : b
}, !0)
});
var objects = document.getElementsByClassName('game-clickable');
for (var i = 0; i < objects.length; i++) {
bind(objects[i], 'click', (function(o) {
return function() {
if (!data[o.innerHTML]) {
return;
}
var parent = o.parentNode;
var span = document.createElement('SPAN');
span.innerHTML = data[o.innerHTML];
parent.insertBefore(span, o);
parent.removeChild(o);
init(parent, data);
}
})(objects[i]));
}
};
init(document.getElementById('word-game'), data);
.game-clickable {
cursor: pointer;
text-decoration: underline;
}
<div id="word-game">
{food}
</div>

I think you are looking for something like the following:
var replacements = {
"food" : "meat and vegetables",
"meat" : "pork with bacon",
"vegetables" : "carrots plus peas",
"pork" : "tough and chewy",
"tough" : "burnt and salty"
};
function replaceAnchor(a) {
var elementType = "";
var lastElementType = "";
var target = a.innerHTML;
var replacement = replacements[target];
var words = replacement.split(' ');
var newElement = {};
for(var i = 0; i < words.length; i++) {
var word = words[i];
if (replacements[word]) {
elementType = "a";
} else {
elementType = "span";
}
if (elementType === "a" || elementType != lastElementType) {
newElement = document.createElement(elementType);
if (elementType === "a") {
newElement.onclick = function(e) {
replaceAnchor(this);
e.preventDefault();
};
}
a.parentNode.insertBefore(newElement, a);
}
if (elementType == "span") {
newElement.innerHTML = newElement.innerHTML + " " + word + " ";
} else {
newElement.innerHTML += word;
}
lastElementType = elementType;
}
a.parentElement.removeChild(a);
return false;
}
a {
text-decoration : underline;
color : blue;
cursor: pointer;
}
<a onclick="return replaceAnchor(this);">food</a>

Related

Fastest and robust way to replace various occurencies of a string in dom objects with JavaScript/jQuery

I have written a function which replace all occurence of "#TransResource....." e.g. "#TransResource.Contact.Send" with the replacement from a json array.
example for occurence could be:
<button class="btn btn-primary btn-block js-send">#TransResource.Contact.Send</button>
<input type="text" class="form-control" id="LastName" name="LastName" placeholder="#TransResource.Contact.LastName">
#TransResource.Contact.LastName"
All went fine, except IE/edge lost some translations and I am unable to figure out why.
Can one solve this and/or has a better or robust approach?
You can see, the fiddle works perfect in Chrome, but the button text translation is missing in edge.
here is a fiddle
my JavaScript code.
var
getLocalResource = function (key) {
try {
var retVal = key;
retVal = StringResource[retVal] === null || StringResource[retVal] === undefined ? retVal : StringResource[retVal];
return retVal;
}
catch (err) {
console.log(arguments.callee.name + ": " + err);
}
},
translate = function (node) {
try {
var pattern = /#TransResource.[a-zA-Z0-9.]+/g;
if (!node) node = $("body *");
node.contents().each(function () {
if (this.nodeType === 3) {
this.nodeValue = this.nodeValue.replace(pattern, function (match, entity) {
return getLocalResource(match.slice(15));
});
}
if (this.attributes) {
for (var i = 0, atts = this.attributes, n = atts.length, arr = []; i < n; i++) {
if (atts[i].nodeValue !== "") { // Ignore this node it is an empty text node
atts[i].nodeValue = atts[i].nodeValue.trim().replace(pattern, function (match, entity) {
return getLocalResource(match.slice(15));
});
}
}
}
});
}
catch (err) {
console.log(arguments.callee.name + ": " + err);
}
};
and the json:
var StringResource = {
"Contact.EmailAddress": "Email address",
"Contact.Headline": "Contact",
"Contact.Description": "please leaf us a message...?",
"Contact.Teasertext": "Please leaf us a message <b>bold text</b>",
"Contact.Telephone": "Telephone",
"Contact.Send": "Send",
"Page.Contact": "Contact"
};
edit (this is now my solution):
thanks to #Chris-G, his comment removes the IE issue and also thanks to #trincot for the perfomance update. All together is now this script:
var
getLocalResource = function (key) {
try {
var retVal = key;
retVal = StringResource[retVal] === null || StringResource[retVal] === undefined ? retVal : StringResource[retVal];
return retVal;
}
catch (err) {
console.log(arguments.callee.name + ": " + err);
}
},
translate = function (node) {
try {
var pattern = /#TransResource.[a-zA-Z0-9.]+/g;
if (!node) node = $("body *");
node.contents().each(function () {
if (this.nodeType === 3 && this.nodeValue.trim().length) {
var s = this.nodeValue.replace(pattern, function (match, entity) {
return getLocalResource(match.slice(15));
});
if (this.nodeValue !== s) this.nodeValue = s;
}
if (this.attributes) {
for (var i = 0, atts = this.attributes, n = atts.length, arr = []; i < n; i++) {
if (atts[i].nodeValue !== "") { // Ignore this node it is an empty text node
atts[i].nodeValue = atts[i].nodeValue.trim().replace(pattern, function (match, entity) {
return getLocalResource(match.slice(15));
});
}
}
}
});
}
catch (err) {
console.log(arguments.callee.name + ": " + err);
}
};
The problem in IE is that for the textarea node, when it has no content, the assignment to node.nodeValue will trigger an exception "Invalid argument". Don't ask me why.
If you even add as little as a space in the HTML between the opening and closing textarea tag, the error disappears, but if you do that the placeholder attribute becomes useless.
But you could also work around the problem in the code, by only assigning to node.nodeValue when the assigned value is different from its current value:
if (this.nodeType === 3) {
var s = this.nodeValue.replace(pattern, function (match, entity) {
return getLocalResource(match.slice(15));
});
if (this.nodeValue !== s) this.nodeValue = s;
}

could not highlight gmail content up to 6

I am developing an extension which is about fetching the list of topics from the server and find if those topics match with the currently opened gmail messages or not, if found then highlight that topic otherwise don't. But if already 6 topics are matched then it should not check or highlight other topics.
I have used the treewalker for crawling the gmail contents so the matched content will get highlighted as follow
function searchPage(topics) {
highlightAllWords(topics);
}
var highlightAllWords = function(topics) {
Object.keys(topics.topics).forEach(function(topic) {
highlightTopic(topic);
})
}
function highlightTopic(topic) {
let found = 0;
if (topic == null || topic.length === 0) return;
var topicRegex = new RegExp(topic, 'gi');
var treeWalker = document.createTreeWalker(
document.body,
NodeFilter.SHOW_TEXT,
{
acceptNode: function(node) {
var result = NodeFilter.FILTER_SKIP;
if (topicRegex.test(node.nodeValue)) {
found += 1
console.log('found', found);
if (found <= 6) {
console.log('foound less than 6', found)
result = NodeFilter.FILTER_ACCEPT
return result;
}
};
}
}, false
)
var skipTagName = {
"NOSCRIPT": true,
"SCRIPT": true,
"STYLE": true
}
var nodeList = [];
while (treeWalker.nextNode()) {
if (!skipTagName[treeWalker.currentNode.parentNode.tagName]) {
nodeList.push(treeWalker.currentNode);
}
}
nodeList.forEach(function (n) {
var rangeList = [];
// find sub-string ranges
var startingIndex = 0;
do {
// console.log(word, startingIndex, n.parentNode, n.textContent);
startingIndex = n.textContent.indexOf(topic, startingIndex + 1);
if (startingIndex !== -1) {
var topicRange = document.createRange();
topicRange.setStart(n, startingIndex);
topicRange.setEnd(n, startingIndex + topic.length);
rangeList.push(topicRange);
}
} while (startingIndex !== -1);
// highlight all ranges
rangeList.forEach(function (r) {
highlightRange(r);
});
});
}
// highlight the keyword by surround it by `i`
var highlightRange = function (range) {
const bgColorCode = '#000000';
var anchor = document.createElement("A");
var selectorName = anchor.className = "highlighted_text";
anchor.classList.add("highlighted_text");
// range.surroundContents(iNode) will throw exception if word across multi tag
if (!ruleExistenceDict[bgColorCode]) {
sheet.insertRule([".", selectorName, " { background: #", bgColorCode, " !important; }"].join(""), 0);
ruleExistenceDict[bgColorCode] = true;
console.log(sheet);
}
anchor.appendChild(range.extractContents());
anchor.href = `https://google.com/?search=${
range.extractContents()
}`;
range.insertNode(anchor);
};
It highlights the matched content in gmail messages but does highlights more than 6 contents. I have taken the screenshot and it is something like this
update after counter increased and checked in treewalker.nextnode()
There are two syntax errors in your code. You are missing semicolon at end of this statement
found += 1;
Secondly, there is also one extra ";" at the end of function(node).
And You can add the counter check in the following code snippet as
var count=1;
while (treeWalker.nextNode() && count<=6) {
if (!skipTagName[treeWalker.currentNode.parentNode.tagName]) {
nodeList.push(treeWalker.currentNode);
count=count+1;
}
}
So, the final script for the function highlightTopic(topic) will look like
function highlightTopic(topic) {
let found = 0;
if (topic == null || topic.length === 0) return;
var topicRegex = new RegExp(topic, 'gi');
var treeWalker = document.createTreeWalker(
document.body,
NodeFilter.SHOW_TEXT,
{
acceptNode: function(node) {
var result = NodeFilter.FILTER_SKIP;
if (topicRegex.test(node.nodeValue)) {
found += 1;
console.log('found', found);
if (found <= 6) {
console.log('foound less than 6', found)
result = NodeFilter.FILTER_ACCEPT
return result;
}
}
}
}, false
)
var skipTagName = {
"NOSCRIPT": true,
"SCRIPT": true,
"STYLE": true
}
var nodeList = [];
var count=1;
while (treeWalker.nextNode() && count<=6) {
if (!skipTagName[treeWalker.currentNode.parentNode.tagName]) {
nodeList.push(treeWalker.currentNode);
count=count+1;
console.log('count:'+count);
}
}
nodeList.forEach(function (n) {
var rangeList = [];
// find sub-string ranges
var startingIndex = 0;
do {
// console.log(word, startingIndex, n.parentNode, n.textContent);
startingIndex = n.textContent.indexOf(topic, startingIndex + 1);
if (startingIndex !== -1) {
var topicRange = document.createRange();
topicRange.setStart(n, startingIndex);
topicRange.setEnd(n, startingIndex + topic.length);
rangeList.push(topicRange);
}
} while (startingIndex !== -1);
// highlight all ranges
rangeList.forEach(function (r) {
highlightRange(r);
});
});
}
Please update me if it worked.
Thanks
Edit:
And also update the function highlightAllWords as follow:
var highlightAllWords = function(topics) {
var count=1;
Object.keys(topics.topics).forEach(function(topic) {
if(count<=6){
highlightTopic(topic);
console.log('counter:'+count);
if (topic != null && topic.length != 0)
count=count+1;
}
})
}

How to take the result of one script and feed it into another?

I'm in way over my head here and need some help to understand what I'm looking at please! (Very new to Javascript!) Here is the situation as I understand it...
I have a script that is selecting a single line from a paragraph of text, and currently produces this alert, where '1' is the selected line:
alert(getLine("sourcePara", 1));
...Instead of triggering an alert I need this selected text to feed into this separate script which is sending data to another browser window. Presently it's taking a text field from a form with the id 'STOCK1', but that can be replaced:
function sendLog() {
var msg = document.getElementById('STOCK1').value;
t.send('STK1', msg);
}
I'm totally confused as to what form this text data is taking on the way out of the first script and have no idea how to call it in as the source for the second... HELP!
All the thanks!
EDIT:
Here is the source code for the Local Connection element;
function LocalConnection(options) {
this.name = 'localconnection';
this.id = new Date().getTime();
this.useLocalStorage = false;
this.debug = false;
this._actions= [];
this.init = function(options) {
try {
localStorage.setItem(this.id, this.id);
localStorage.removeItem(this.id);
this.useLocalStorage = true;
} catch(e) {
this.useLocalStorage = false;
}
for (var o in options) {
this[o] = options[o];
}
this.clear();
}
this.listen = function() {
if (this.useLocalStorage) {
if (window.addEventListener) {
window.addEventListener('storage', this.bind(this, this._check), false);
} else {
window.attachEvent('onstorage', this.bind(this, this._check));
}
} else {
setInterval(this.bind(this, this._check), 100);
}
}
this.send = function(event) {
var args = Array.prototype.slice.call(arguments, 1);
return this._write(event, args);
}
this.addCallback = function(event, func, scope) {
if (scope == undefined) {
scope = this;
}
if (this._actions[event] == undefined) {
this._actions[event] = [];
}
this._actions[event].push({f: func, s: scope});
}
this.removeCallback = function(event) {
for (var e in this._actions) {
if (e == event) {
delete this._actions[e];
break;
}
}
}
this._check = function() {
var data = this._read();
if (data.length > 0) {
for (var e in data) {
this._receive(data[e].event, data[e].args);
}
}
}
this._receive = function(event, args) {
if (this._actions[event] != undefined) {
for (var func in this._actions[event]) {
if (this._actions[event].hasOwnProperty(func)) {
this.log('Triggering callback "'+event+'"', this._actions[event]);
var callback = this._actions[event][func];
callback.f.apply(callback.s, args);
}
}
}
};
this._write = function(event, args) {
var events = this._getEvents();
var evt = {
id: this.id,
event: event,
args: args
};
events.push(evt);
this.log('Sending event', evt);
if (this.useLocalStorage) {
localStorage.setItem(this.name, JSON.stringify(events));
} else {
document.cookie = this.name + '=' + JSON.stringify(events) + "; path=/";
}
return true;
}
this._read = function() {
var events = this._getEvents();
if (events == '') {
return false;
}
var ret = [];
for (var e in events) {
if (events[e].id != this.id) {
ret.push({
event: events[e].event,
args: events[e].args
});
events.splice(e, 1);
}
}
if (this.useLocalStorage) {
localStorage.setItem(this.name, JSON.stringify(events));
} else {
document.cookie = this.name + '=' + JSON.stringify(events) + "; path=/";
}
return ret;
}
this._getEvents = function() {
return this.useLocalStorage ? this._getLocalStorage() : this._getCookie();
}
this._getLocalStorage = function() {
var events = localStorage.getItem(this.name);
if (events == null) {
return [];
}
return JSON.parse(events);
}
this._getCookie = function() {
var ca = document.cookie.split(';');
var data;
for (var i=0; i < ca.length; i++) {
var c = ca[i];
while (c.charAt(0) == ' ') {
c = c.substring(1, c.length);
}
if (c.indexOf(this.name+'=') == 0) {
data = c.substring(this.name.length+1, c.length);
break;
}
}
data = data || '[]';
return JSON.parse(data);
}
this.clear = function() {
if (this.useLocalStorage) {
localStorage.removeItem(this.name);
} else {
document.cookie = this.name + "=; path=/";
}
}
this.bind = function(scope, fn) {
return function () {
fn.apply(scope, arguments);
};
}
this.log = function() {
if (!this.debug) {
return;
}
if (console) {
console.log(Array.prototype.slice.call(arguments));
}
}
this.init(options);
}
If I understand what you are asking for correctly, then I think its a matter of changing your log function to the following:
function sendLog() {
t.send('STK1', getLine("sourcePara", 1));
}
This assumes that getLine is globally accessible.
Alternatively Another approach would be to allow for the sendLog function to take the message as a parameter. In which case, you would change your first script to be:
sendLog(getLine("sourcePara", 1));
And the modified sendLog function would look like this:
function sendLog(msg) {
t.send('STK1', msg);
}
LocalConnection.js should handle transferring the data between windows/tabs. Looks like an an iteresting project:
https://github.com/jeremyharris/LocalConnection.js

Filtering out empty API responses to avoid errors

Basically I've made a program where words are inputted and their definitions are found using Wordnik API. Each word is then displayed dynamically and the definition is shown on click. Here's that code:
function define(arr) {
return new Promise(function(resolve, reject) {
var client = [];
var definitions = {};
for (var i = 0, len = arr.length; i < len; i++) {
(function(i) {
client[i] = new XMLHttpRequest();
client[i].onreadystatechange = function() {
if (client[i].readyState === 4 && client[i].status === 200) {
if (client[i].responseText.length === 0) {
console.log(client[i].responseText);
client.responseText[0] = {
word: arr[i],
text: 'Definition not found'
};
}
definitions[arr[i]] = JSON.parse(client[i].responseText);
if (Object.keys(definitions).length === arr.length) {
resolve(definitions);
}
}
};
client[i].open('GET', 'http://api.wordnik.com:80/v4/word.json/' + arr[i] +
'/definitions?limit=1&includeRelated=false&sourceDictionaries=all&useCanonical=false&includeTags=false&api_key=',
true);
client[i].send();
})(i);
}
});
}
function makeFlashCards() {
var data = document.getElementById('inputText').value;
var wordsToDefine = ignore(makeArr(findUniq(data)));
define(wordsToDefine).then(function(result) {
success(result);
}).catch(function(reason) {
console.log('this shouldnt run');
});
}
function success(obj) {
document.getElementById('form').innerHTML = '';
for (var prop in obj) {
if (obj.hasOwnProperty(prop)) {
addElement('div', obj[prop][0].word);
}
}
attachDefinition(obj);
}
function addElement(type, word) {
var newElement = document.createElement(type);
var content = document.createTextNode(word);
newElement.appendChild(content);
var referenceNode = document.getElementById('form');
document.body.insertBefore(newElement, referenceNode);
newElement.id = word;
newElement.className = "flashcards";
}
function attachDefinition(obj) {
var classArr = document.getElementsByClassName('flashcards');
for (let i = 0, len = classArr.length; i < len; i++) {
classArr[i].addEventListener('click', function() {
cardClicked.call(this, obj);
});
}
}
function cardClicked(obj) {
var el = document.getElementById(this.id);
if (obj[this.id].length !== 0) {
if (this.innerHTML.split(' ').length === 1) {
var img = document.createElement('img');
img.src = 'https://www.wordnik.com/img/wordnik_badge_a2.png';
el.innerHTML = obj[this.id][0].text
+ ' ' + obj[this.id][0].attributionText + '<br>';
el.style['font-weight'] = 'normal';
el.style['font-size'] = '16px';
el.style['text-align'] = 'left';
el.style['overflow'] = 'auto';
el.appendChild(img);
} else {
el.innerHTML = obj[this.id][0].word;
el.style['font-weight'] = 'bold';
el.style['font-size'] = '36px';
el.style['text-align'] = 'center';
el.style['overflow'] = 'visible';
}
}
}
When the define function is given an array with all valid words, the program works as expected however if any word in the array argument is not valid the program doesn't add click event handlers to each element. I think this might have to do with the catch being triggered.
When an invalid word is requested Wordnik API sends back an empty array which might be the root of this problem. I tried to account for this by adding
if (client[i].responseText.length === 0) {
console.log(client[i].responseText);
client.responseText[0] = {
word: arr[i],
text: 'Definition not found'
};
but this conditional never ends up running.
I need some way of filtering out the empty array responses so the catch is not triggered and the program can run smoothly.
When you get to if (client[i].responseText.length === 0) make sure that client[i].responseText is returning empty string. It is probably undefined in which case client[i].responseText.length will throw an error and this will cause the catch block to execute.
function makePromise() {
return new Promise(function(resolve, reject) {
var test = undefined;
if (test.length === 0) {
resolve("resolved");
}
});
}
makePromise().then(console.log).catch(function(res) {
console.log('Error was thrown')
});
Try changing that condition to:
if (client[i].responseText && client[i].responseText.length === 0)

How to add/remove a class in JavaScript?

Since element.classList is not supported in IE 9 and Safari-5, what's an alternative cross-browser solution?
No-frameworks please.
Solution must work in at least IE 9, Safari 5, FireFox 4, Opera 11.5, and Chrome.
Related posts (but does not contain solution):
how to add and remove css class
Add and remove a class with animation
Add remove class?
Here is solution for addClass, removeClass, hasClass in pure javascript solution.
Actually it's from http://jaketrent.com/post/addremove-classes-raw-javascript/
function hasClass(ele,cls) {
return !!ele.className.match(new RegExp('(\\s|^)'+cls+'(\\s|$)'));
}
function addClass(ele,cls) {
if (!hasClass(ele,cls)) ele.className += " "+cls;
}
function removeClass(ele,cls) {
if (hasClass(ele,cls)) {
var reg = new RegExp('(\\s|^)'+cls+'(\\s|$)');
ele.className=ele.className.replace(reg,' ');
}
}
Look at these oneliners:
Remove class:
element.classList.remove('hidden');
Add class (will not add it twice if already present):
element.classList.add('hidden');
Toggle class (adds the class if it's not already present and removes it if it is)
element.classList.toggle('hidden');
That's all! I made a test - 10000 iterations. 0.8s.
I just wrote these up:
function addClass(el, classNameToAdd){
el.className += ' ' + classNameToAdd;
}
function removeClass(el, classNameToRemove){
var elClass = ' ' + el.className + ' ';
while(elClass.indexOf(' ' + classNameToRemove + ' ') !== -1){
elClass = elClass.replace(' ' + classNameToRemove + ' ', '');
}
el.className = elClass;
}
I think they'll work in all browsers.
The simplest is element.classList which has remove(name), add(name), toggle(name), and contains(name) methods and is now supported by all major browsers.
For older browsers you change element.className. Here are two helper:
function addClass(element, className){
element.className += ' ' + className;
}
function removeClass(element, className) {
element.className = element.className.replace(
new RegExp('( |^)' + className + '( |$)', 'g'), ' ').trim();
}
One way to play around with classes without frameworks/libraries would be using the property Element.className, which "gets and sets the value of the class attribute of the specified element." (from the MDN documentation).
As #matías-fidemraizer already mentioned in his answer, once you get the string of classes for your element you can use any methods associated with strings to modify it.
Here's an example:
Assuming you have a div with the ID "myDiv" and that you want to add to it the class "main__section" when the user clicks on it,
window.onload = init;
function init() {
document.getElementById("myDiv").onclick = addMyClass;
}
function addMyClass() {
var classString = this.className; // returns the string of all the classes for myDiv
var newClass = classString.concat(" main__section"); // Adds the class "main__section" to the string (notice the leading space)
this.className = newClass; // sets className to the new string
}
Read this Mozilla Developer Network article:
https://developer.mozilla.org/en/DOM/element.className
Since element.className property is of type string, you can use regular String object functions found in any JavaScript implementation:
If you want to add a class, first use String.indexOf in order to check if class is present in className. If it's not present, just concatenate a blank character and the new class name to this property. If it's present, do nothing.
If you want to remove a class, just use String.replace, replacing "[className]" with an empty string. Finally use String.trim to remove blank characters at the start and end of element.className.
Fixed the solution from #Paulpro
Do not use "class", as it is a reserved word
removeClass function
was broken, as it bugged out after repeated use.
`
function addClass(el, newClassName){
el.className += ' ' + newClassName;
}
function removeClass(el, removeClassName){
var elClass = el.className;
while(elClass.indexOf(removeClassName) != -1) {
elClass = elClass.replace(removeClassName, '');
elClass = elClass.trim();
}
el.className = elClass;
}
The solution is to
Shim .classList:
Either use the DOM-shim or use Eli Grey's shim below
Disclaimer: I believe the support is FF3.6+, Opera10+, FF5, Chrome, IE8+
/*
* classList.js: Cross-browser full element.classList implementation.
* 2011-06-15
*
* By Eli Grey, http://eligrey.com
* Public Domain.
* NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
*/
/*global self, document, DOMException */
/*! #source http://purl.eligrey.com/github/classList.js/blob/master/classList.js*/
if (typeof document !== "undefined" && !("classList" in document.createElement("a"))) {
(function (view) {
"use strict";
var
classListProp = "classList"
, protoProp = "prototype"
, elemCtrProto = (view.HTMLElement || view.Element)[protoProp]
, objCtr = Object
, strTrim = String[protoProp].trim || function () {
return this.replace(/^\s+|\s+$/g, "");
}
, arrIndexOf = Array[protoProp].indexOf || function (item) {
var
i = 0
, len = this.length
;
for (; i < len; i++) {
if (i in this && this[i] === item) {
return i;
}
}
return -1;
}
// Vendors: please allow content code to instantiate DOMExceptions
, DOMEx = function (type, message) {
this.name = type;
this.code = DOMException[type];
this.message = message;
}
, checkTokenAndGetIndex = function (classList, token) {
if (token === "") {
throw new DOMEx(
"SYNTAX_ERR"
, "An invalid or illegal string was specified"
);
}
if (/\s/.test(token)) {
throw new DOMEx(
"INVALID_CHARACTER_ERR"
, "String contains an invalid character"
);
}
return arrIndexOf.call(classList, token);
}
, ClassList = function (elem) {
var
trimmedClasses = strTrim.call(elem.className)
, classes = trimmedClasses ? trimmedClasses.split(/\s+/) : []
, i = 0
, len = classes.length
;
for (; i < len; i++) {
this.push(classes[i]);
}
this._updateClassName = function () {
elem.className = this.toString();
};
}
, classListProto = ClassList[protoProp] = []
, classListGetter = function () {
return new ClassList(this);
}
;
// Most DOMException implementations don't allow calling DOMException's toString()
// on non-DOMExceptions. Error's toString() is sufficient here.
DOMEx[protoProp] = Error[protoProp];
classListProto.item = function (i) {
return this[i] || null;
};
classListProto.contains = function (token) {
token += "";
return checkTokenAndGetIndex(this, token) !== -1;
};
classListProto.add = function (token) {
token += "";
if (checkTokenAndGetIndex(this, token) === -1) {
this.push(token);
this._updateClassName();
}
};
classListProto.remove = function (token) {
token += "";
var index = checkTokenAndGetIndex(this, token);
if (index !== -1) {
this.splice(index, 1);
this._updateClassName();
}
};
classListProto.toggle = function (token) {
token += "";
if (checkTokenAndGetIndex(this, token) === -1) {
this.add(token);
} else {
this.remove(token);
}
};
classListProto.toString = function () {
return this.join(" ");
};
if (objCtr.defineProperty) {
var classListPropDesc = {
get: classListGetter
, enumerable: true
, configurable: true
};
try {
objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
} catch (ex) { // IE 8 doesn't support enumerable:true
if (ex.number === -0x7FF5EC54) {
classListPropDesc.enumerable = false;
objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
}
}
} else if (objCtr[protoProp].__defineGetter__) {
elemCtrProto.__defineGetter__(classListProp, classListGetter);
}
}(self));
}
Improved version of emil's code (with trim())
function hasClass(ele,cls) {
return !!ele.className.match(new RegExp('(\\s|^)'+cls+'(\\s|$)'));
}
function addClass(ele,cls) {
if (!hasClass(ele,cls)) ele.className = ele.className.trim() + " " + cls;
}
function removeClass(ele,cls) {
if (hasClass(ele,cls)) {
var reg = new RegExp('(\\s|^)'+cls+'(\\s|$)');
ele.className = ele.className.replace(reg,' ');
ele.className = ele.className.trim();
}
}
function addClass(element, classString) {
element.className = element
.className
.split(' ')
.filter(function (name) { return name !== classString; })
.concat(classString)
.join(' ');
}
function removeClass(element, classString) {
element.className = element
.className
.split(' ')
.filter(function (name) { return name !== classString; })
.join(' ');
}
Just in case if anyone would like to have prototype functions built for elements, this is what I use when I need to manipulate classes of different objects:
Element.prototype.addClass = function (classToAdd) {
var classes = this.className.split(' ')
if (classes.indexOf(classToAdd) === -1) classes.push(classToAdd)
this.className = classes.join(' ')
}
Element.prototype.removeClass = function (classToRemove) {
var classes = this.className.split(' ')
var idx =classes.indexOf(classToRemove)
if (idx !== -1) classes.splice(idx,1)
this.className = classes.join(' ')
}
Use them like:
document.body.addClass('whatever') or document.body.removeClass('whatever')
Instead of body you can also use any other element (div, span, you name it)
add css classes: cssClassesStr += cssClassName;
remove css classes: cssClassStr = cssClassStr.replace(cssClassName,"");
add attribute 'Classes': object.setAttribute("class", ""); //pure addition of this attribute
remove attribute: object.removeAttribute("class");
A easy to understand way:
// Add class
DOMElement.className += " one";
// Example:
// var el = document.body;
// el.className += " two"
// Remove class
function removeDOMClass(element, className) {
var oldClasses = element.className,
oldClassesArray = oldClasses.split(" "),
newClassesArray = [],
newClasses;
// Sort
var currentClassChecked,
i;
for ( i = 0; i < oldClassesArray.length; i++ ) {
// Specified class will not be added in the new array
currentClassChecked = oldClassesArray[i];
if( currentClassChecked !== className ) {
newClassesArray.push(currentClassChecked);
}
}
// Order
newClasses = newClassesArray.join(" ");
// Apply
element.className = newClasses;
return element;
}
// Example:
// var el = document.body;
// removeDOMClass(el, "two")
https://gist.github.com/sorcamarian/ff8db48c4dbf4f5000982072611955a2

Categories