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;
}
Having problem overriding Sizzle()
We encountered "SCRIPT70: Permission denied" exception in IE apparently, our fix is to hack our current version of jquery however we dont want to modify original Sizzle()
(function ($) {
$.fn.Sizzle = function (selector, context, results, seed) {
var match, elem, m, nodeType,
// QSA vars
i, groups, old, nid, newContext, newSelector;
// This try/catch seems to fix IE 'permission denied' errors as described here:
// http://bugs.jquery.com/ticket/14535
try {
document === document; //may cause permission denied
}
catch (err) {
document = window.document; //resets document, and no more permission denied errors.
}
if ((context ? context.ownerDocument || context : preferredDoc) !== document) {
setDocument(context);
}
context = context || document;
results = results || [];
if (!selector || typeof selector !== "string") {
return results;
}
if ((nodeType = context.nodeType) !== 1 && nodeType !== 9) {
return [];
}
if (!documentIsXML && !seed) {
// Shortcuts
if ((match = rquickExpr.exec(selector))) {
// Speed-up: Sizzle("#ID")
if ((m = match[1])) {
if (nodeType === 9) {
elem = context.getElementById(m);
// Check parentNode to catch when Blackberry 4.6 returns
// nodes that are no longer in the document #6963
if (elem && elem.parentNode) {
// Handle the case where IE, Opera, and Webkit return items
// by name instead of ID
if (elem.id === m) {
results.push(elem);
return results;
}
} else {
return results;
}
} else {
// Context is not a document
if (context.ownerDocument && (elem = context.ownerDocument.getElementById(m)) &&
contains(context, elem) && elem.id === m) {
results.push(elem);
return results;
}
}
// Speed-up: Sizzle("TAG")
} else if (match[2]) {
push.apply(results, slice.call(context.getElementsByTagName(selector), 0));
return results;
// Speed-up: Sizzle(".CLASS")
} else if ((m = match[3]) && support.getByClassName && context.getElementsByClassName) {
push.apply(results, slice.call(context.getElementsByClassName(m), 0));
return results;
}
}
// QSA path
if (support.qsa && !rbuggyQSA.test(selector)) {
old = true;
nid = expando;
newContext = context;
newSelector = nodeType === 9 && selector;
// qSA works strangely on Element-rooted queries
// We can work around this by specifying an extra ID on the root
// and working up from there (Thanks to Andrew Dupont for the technique)
// IE 8 doesn't work on object elements
if (nodeType === 1 && context.nodeName.toLowerCase() !== "object") {
groups = tokenize(selector);
if ((old = context.getAttribute("id"))) {
nid = old.replace(rescape, "\\$&");
} else {
context.setAttribute("id", nid);
}
nid = "[id='" + nid + "'] ";
i = groups.length;
while (i--) {
groups[i] = nid + toSelector(groups[i]);
}
newContext = rsibling.test(selector) && context.parentNode || context;
newSelector = groups.join(",");
}
if (newSelector) {
try {
push.apply(results, slice.call(newContext.querySelectorAll(
newSelector
), 0));
return results;
} catch (qsaError) {
} finally {
if (!old) {
context.removeAttribute("id");
}
}
}
}
}
// All others
return select(selector.replace(rtrim, "$1"), context, results, seed);
};
}(jQuery));
Ref: https://bugs.jquery.com/ticket/14535
https://bugs.jquery.com/ticket/14535#comment:11
Everything works perfectly with modern browsers but for ie8 I get this error for this line:
tabValues.push(tabParams[i].split(attribute_anchor_separator));
Here the whole function:
function checkUrl()
{
if (original_url != window.location || first_url_check)
{
first_url_check = false;
url = window.location + '';
// if we need to load a specific combination
if (url.indexOf('#/') != -1)
{
// get the params to fill from a "normal" url
params = url.substring(url.indexOf('#') + 1, url.length);
tabParams = params.split('/');
tabValues = [];
if (tabParams[0] == '')
tabParams.shift();
for (var i in tabParams)
tabValues.push(tabParams[i].split(attribute_anchor_separator));
product_id = $('#product_page_product_id').val();
// fill html with values
$('.color_pick').removeClass('selected');
$('.color_pick').parent().parent().children().removeClass('selected');
count = 0;
for (var z in tabValues)
for (var a in attributesCombinations)
if (attributesCombinations[a]['group'] === decodeURIComponent(tabValues[z][0])
&& attributesCombinations[a]['attribute'] === tabValues[z][1])
{
count++;
// add class 'selected' to the selected color
$('#color_' + attributesCombinations[a]['id_attribute']).addClass('selected');
$('#color_' + attributesCombinations[a]['id_attribute']).parent().addClass('selected');
$('input:radio[value=' + attributesCombinations[a]['id_attribute'] + ']').attr('checked', true);
$('input[type=hidden][name=group_' + attributesCombinations[a]['id_attribute_group'] + ']').val(attributesCombinations[a]['id_attribute']);
$('select[name=group_' + attributesCombinations[a]['id_attribute_group'] + ']').val(attributesCombinations[a]['id_attribute']);
}
// find combination
if (count >= 0)
{
findCombination(false);
original_url = url;
return true;
}
// no combination found = removing attributes from url
else
window.location = url.substring(0, url.indexOf('#'));
}
}
return false;
}
Any ideas?? Thx!
I noticed you are using a for..in over an Array however using for..in was meant to be used for iteration over objects (not arrays):
for ( var prop in obj1 ) {
if ( obj1.hasOwnProperty(prop) ) {
// loop body goes here
}
}
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