I was planning on creating my own symbol font for use on my website and I had initially intended on using ligatures and OT but support is spotty at best, and non-existant for older browsers, especially IE (I know, big surprise). I then started taking a close look at how SymbolSet works. It's actually pretty clever and while it would be easy for me to change a couple array variables and be done with it, the javascript is proprietary and I can't use it without permission. I'd love to be able to create my own symbol fonts a la SymbolSet but I'd need an open javascript file to let me do this. Are there any open jQuery plug-ins or javascript libraries that do this?
I have found this http://labs.adamdscott.com/ligatures/ligaturejs.html. Basically it walks the dom, and replaces the known letter combinations with ligatures. I've modified the script to use native ligature support when available:
// ligature.js v1.0
// http://code.google.com/p/ligature-js/
// with modifications by sabof
var ligature = (function() {
var testDiv = document.createElement('div'),
nativeLigatureSupport = testDiv.style.textRendering !== undefined;
if (nativeLigatureSupport) {
return function (node, extended) {
if (!node) {
ligature(document.body, extended);
} else {
node.style.textRendering = 'optimizeLegibility';
}
}
}
return function(node, extended) {
if (!node) {
ligature(document.body, extended);
} else {
if (node.nodeType == 3 && node.parentNode.nodeName != 'SCRIPT') {
node.nodeValue = node.nodeValue
.replace(/ffl/g, 'ffl')
.replace(/ffi/g, 'ffi')
.replace(/fl/g, 'fl')
.replace(/fi/g, 'fi')
.replace(/ff/g, 'ff')
.replace(/ij/g, 'ij')
.replace(/IJ/g, 'IJ');
if (extended) {
node.nodeValue = node.nodeValue
.replace(/ae/g, 'æ')
.replace(/A[Ee]/g, 'Æ')
.replace(/oe/g, 'œ')
.replace(/O[Ee]/g, 'Œ')
.replace(/ue/g, 'ᵫ')
.replace(/st/g, 'st');
}
}
if (node.childNodes) {
for (var i=0; i < node.childNodes.length; i++) {
ligature(node.childNodes.item(i), extended);
}
}
}
};
}());
Related
When I run event.path[n].id in Firefox, I get this error. It works in other browsers.
event.path undefined
The path property of Event objects is non-standard. The standard equivalent is the composedPath method. But it was new when the question was asked (2016); it's well-established as of this update in January 2023.
So you may want to try composedPath and fall back to path (or just use composedPath now it's established):
// Written in ES5 for compatibility with browsers that weren't obsolete
// yet when the question was posted, although they are now
var path = event.composedPath ? event.composedPath() : event.path;
if (path) {
// You got some path information
} else {
// This browser doesn't supply path information
}
Obviously that won't give you path information if the browser doesn't supply it, but it allows for both the old way and the new, standard way, and so will do its best cross-browser.
Example:
// Written in ES5 for compatibility with browsers that weren't obsolete
// yet when the question was posted, although they are now
document.getElementById("target").addEventListener("click", function (e) {
// Just for demonstration purposes
if (e.path) {
if (e.composedPath) {
console.log("Supports `path` and `composedPath`");
} else {
console.log("Supports `path` but not `composedPath`");
}
} else if (e.composedPath) {
console.log("Supports `composedPath` (but not `path`)");
} else {
console.log("Supports neither `path` nor `composedPath`");
}
// Per the above, get the path if we can, first using the standard
// method if possible, falling back to non-standard `path`
var path = event.composedPath ? event.composedPath() : event.path;
// Show it if we got it
if (path) {
console.log("Path (" + path.length + ")");
Array.prototype.forEach.call(path, function(entry) {
console.log(entry === window ? "window" : entry.nodeName);
});
}
});
.as-console-wrapper {
max-height: 100% !important;
}
<div id="target">Click me</div>
According to MDN, all major browsers support composedPath as of January 2023. Chrome (and other Chromium-based browsers) supported both path (it was a Chrome innovation) and composedPath until v109 when path was removed. (The obsolete browsers IE11 and Legacy Edge [Microsoft Edge prior to v79 when it became a Chromium-based browser] didn't support either of them.)
If you ran into a browser that doesn't support either of them, I don't think you can get the path information as of when the event was triggered. You can get the path via e.target.parentNode and each subsequent parentNode, which is usually the same, but of course the point of composedPath is that it's not always the same (if something modifies the DOM after the event was triggered but before your handler got called).
You can create your own composedPath function if it's not implemented in the browser:
function composedPath (el) {
var path = [];
while (el) {
path.push(el);
if (el.tagName === 'HTML') {
path.push(document);
path.push(window);
return path;
}
el = el.parentElement;
}
}
The returned value is equivalent to event.path of Google Chrome.
Example:
document.getElementById('target').addEventListener('click', function(event) {
var path = event.path || (event.composedPath && event.composedPath()) || composedPath(event.target);
});
This function serves as a polyfill for Event.composedPath() or Event.Path
function eventPath(evt) {
var path = (evt.composedPath && evt.composedPath()) || evt.path,
target = evt.target;
if (path != null) {
// Safari doesn't include Window, but it should.
return (path.indexOf(window) < 0) ? path.concat(window) : path;
}
if (target === window) {
return [window];
}
function getParents(node, memo) {
memo = memo || [];
var parentNode = node.parentNode;
if (!parentNode) {
return memo;
}
else {
return getParents(parentNode, memo.concat(parentNode));
}
}
return [target].concat(getParents(target), window);
}
Use composePath() and use a polyfill for IE:
https://gist.github.com/rockinghelvetica/00b9f7b5c97a16d3de75ba99192ff05c
include above file or paste code:
// Event.composedPath
(function(e, d, w) {
if(!e.composedPath) {
e.composedPath = function() {
if (this.path) {
return this.path;
}
var target = this.target;
this.path = [];
while (target.parentNode !== null) {
this.path.push(target);
target = target.parentNode;
}
this.path.push(d, w);
return this.path;
}
}
})(Event.prototype, document, window);
and then use:
var path = event.path || (event.composedPath && event.composedPath());
I had the same issue. I need the name of the HTML element. In Chrome I get the name with path. In Firefox I tried with composedPath, but it returns a different value.
For solving my problem, I used e.target.nodeName. With target function you can retrieve the HTML element in Chrome, Firefox and Safari.
This is my function in Vue.js:
selectFile(e) {
this.nodeNameClicked = e.target.nodeName
if (this.nodeNameClicked === 'FORM' || this.nodeNameClicked === 'INPUT' || this.nodeNameClicked === 'SPAN') {
this.$refs.singlefile.click()
}
}
I won't bother you with too much "blahblahblah" as most of you will know the following script all too well. A lot of questions popped up around this topic after Google let this beast out to play in the wild.
var elements = [
"script1.js",
"script2.js"
];
var downloadJSAtOnload = function(elements) {
if (toString.call(elements) !== "[object Array]") {
return false
}
var i, element;
for (i = 0; i < elements.length; i++) {
element = document.createElement("script");
element.src = elements[i];
document.body.appendChild(element)
}
return true
};
if (window.addEventListener) {
window.addEventListener("load", function() {
downloadJSAtOnload(elements)
}
, false)
} else {
if (window.attachEvent) {
window.attachEvent("onload", function() {
downloadJSAtOnload(elements)
})
} else {
window.onload = function() {
downloadJSAtOnload(elements)
}
}
};
This script is doing what it has to do in all browsers but IE10 and 11 from what I can see. I tested with browserstack and real machines with the same result.
May be it is due to minification of the script so I will also give you the minified version of the above script, as we are using it in our live environment:
var elements=["script1.js","script2.js"],downloadJSAtOnload=function(n){if("[object Array]"!==toString.call(n))return!1;var t,e;for(t=0;t<n.length;t++)e=document.createElement("script"),e.src=n[t],document.body.appendChild(e);return!0};window.addEventListener?window.addEventListener("load",function(){downloadJSAtOnload(elements)},!1):window.attachEvent?window.attachEvent("onload",function(){downloadJSAtOnload(elements)}):window.onload=function(){downloadJSAtOnload(elements)};
Anything I was doing wrong here? As it is indeed doing its thing in FF, Chrome, Safari and Opera I was expecting IE (you can call me a fool, now) to behave... at least once.
Seems the Error is on "[object Array]"!==toString.call(n).
The IE can't referer to the function toString() directly when using .call( something ). You can use following code to fix your Problem:
not minifed
if( Object.prototype.toString.call(elements) !== "[object Array]") {
return false;
}
minified
if("[object Array]"!==Object.prototype.toString.call(n)) return !1;
See more Informations about the toString()-Function here: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/toString under "Using toString() to detect object class".
I want to serve different javascript files depending on if browser supports CSS3 transition or not. Is there a better way to detect transition support than my code below?
window.onload = function () {
var b = document.body.style;
if(b.MozTransition=='' || b.WebkitTransition=='' || b.OTransition=='' || b.transition=='') {
alert('supported');
} else {
alert('NOT supported')
}
}
I also think including Modernizr is an overkill. The function below should work for any feature.
function detectCSSFeature(featurename){
var feature = false,
domPrefixes = 'Webkit Moz ms O'.split(' '),
elm = document.createElement('div'),
featurenameCapital = null;
featurename = featurename.toLowerCase();
if( elm.style[featurename] !== undefined ) { feature = true; }
if( feature === false ) {
featurenameCapital = featurename.charAt(0).toUpperCase() + featurename.substr(1);
for( var i = 0; i < domPrefixes.length; i++ ) {
if( elm.style[domPrefixes[i] + featurenameCapital ] !== undefined ) {
feature = true;
break;
}
}
}
return feature;
}
var hasCssTransitionSupport = detectCSSFeature("transition");
Inspired by https://developer.mozilla.org/en-US/docs/CSS/Tutorials/Using_CSS_animations/Detecting_CSS_animation_support
Modernizr will detect this for you. Use this link to create a custom download build that only contains CSS3 2D and/or 3D transitions.
Once it's run, you can either test for the csstransitions class on the html tag (CSS), or in JavaScript, test if Modernizr.csstransitions is true.
More docs: http://modernizr.com/docs/#csstransitions
Here is another testing code. Maybe it is an overkill, but the function tries to set the CSS property to DOM object and then read back from it.
Never tested this code on large amount of exotic browsers, but it is safer than just checking for the CSS property availability. Ah, yes, it can distinguish 2D transform support from 3D transform support! Just pass CSS property values you want to test!
The plus of this code is that it detects the vendor prefix supported (if any). Possible return values:
false, when feature unsupported, or
{
vendor: 'moz',
cssStyle: '-moz-transition',
jsStyle: 'MozTransition'
}
when feature supported
/**
* Test for CSS3 feature support. Single-word properties only by now.
* This function is not generic, but it works well for transition and transform at least
*/
testCSSSupport: function (feature, cssTestValue/* optional for transition and transform */) {
var testDiv,
featureCapital = feature.charAt(0).toUpperCase() + feature.substr(1),
vendors = ['', 'webkit', 'moz', 'ms', 'o'],
jsPrefixes = ['', 'Webkit', 'Moz', 'ms', 'O'],
defaultTestValues = {
transition: 'left 2s ease 1s',
transform: 'rotateX(-180deg) translateZ(.5em) scale(0.5)'
// This will test for 3D transform support
// Use other values if you need to test for 2D support only
},
testFunctions = {
transition: function (jsProperty, computed) {
return computed[jsProperty + 'Delay'] === '1s' && computed[jsProperty + 'Duration'] === '2s' && computed[jsProperty + 'Property'] === 'left';
},
transform: function (jsProperty, computed) {
return computed[jsProperty].substr(0, 9) === 'matrix3d(';
}
};
/* test given vendor prefix */
function isStyleSupported(feature, jsPrefixedProperty) {
if (jsPrefixedProperty in testDiv.style) {
var testVal = cssTestValue || defaultTestValues[feature],
testFn = testFunctions[feature];
if (!testVal) {
return false;
}
testDiv.style[jsPrefixedProperty] = testVal;
var computed = window.getComputedStyle(testDiv);
if (testFn) {
return testFn(jsPrefixedProperty, computed);
}
else {
return computed[jsPrefixedProperty] === testVal;
}
}
return false;
}
//Assume browser without getComputedStyle is either IE8 or something even more poor
if (!window.getComputedStyle) {
return false;
}
//Create a div for tests and remove it afterwards
if (!testDiv) {
testDiv = document.createElement('div');
document.body.appendChild(testDiv);
setTimeout(function () {
document.body.removeChild(testDiv);
testDiv = null;
}, 0);
}
var cssPrefixedProperty,
jsPrefixedProperty;
for (var i = 0; i < vendors.length; i++) {
if (i === 0) {
cssPrefixedProperty = feature; //todo: this code now works for single-word features only!
jsPrefixedProperty = feature; //therefore box-sizing -> boxSizing won't work here
}
else {
cssPrefixedProperty = '-' + vendors[i] + '-' + feature;
jsPrefixedProperty = jsPrefixes[i] + featureCapital;
}
if (isStyleSupported(feature, jsPrefixedProperty)) {
return {
vendor: vendors[i],
cssStyle: cssPrefixedProperty,
jsStyle: jsPrefixedProperty
};
}
}
return false;
}
Github: https://github.com/easy-one/CSS3test
if (window.TransitionEvent){
}
With Modernizr 3.0 (alpha), you can generate custom builds locally. This may resolve the aforementioned "overkill" concern - although i'm not entirely clear on that concern in the first place (but i'm assuming it's size). The new api provides a 'build' method, to which you can pass json containing the tests that you would like to include in the build.
I use something like this in my gulp file but gulp is not needed - a simple node script will do.
gulp.task('js:modernizr', function() {
var modConfig = JSON.parse(fs.readFileSync('modernizr-config.json', {
encoding: 'utf8'
}));
modernizr.build(modConfig, function(res) {
fs.writeFileSync('modernizr.js', res);
return true;
});
});
And an example of the 'modernizr-config.json' file would be
{
"classPrefix": "",
"options": [
"addTest",
"atRule",
"domPrefixes",
"hasEvent",
"html5shiv",
"html5printshiv",
"load",
"mq",
"prefixed",
"prefixes",
"prefixedCSS",
"setClasses",
"testAllProps",
"testProp",
"testStyles"
],
"feature-detects": [
"css/transforms",
"css/transforms3d",
"css/transformstylepreserve3d",
"css/transitions",
"touchevents",
"workers/webworkers",
"history"
]
}
The full config file is included in the Modernizr package.
With this approach, you can take advantage of the well maintained Modernizr test suite via package installers and easily add/remove tests as needed. Less tests, smaller file obviously.
The 'setClasses' option will add the related test class to your html but you can also take advantage of the 3.0 async events like so:
Modernizr.on('csstransitions', function(bool) {
if (bool === true) // do transition stuffs
}
Can I do the following?
function contains(element) {
// if the element is a Vertex object, do this
if (element instanceof Vertex) {
var vertex = element;
for ( var index in self.verticies) {
if (self.verticies[index].id == vertex.id) {
return true;
}
}
return false;
}
// else if the element is an Edge object, do this
else if (element instanceof Edge) {
var edge = element;
for ( var index in self.verticies) {
if (self.verticies[index].id == edge.id) {
return true;
}
}
return false;
} else {
// shouldn't come here
return false;
}
};
Basically... I want to be able to call contains() and pass it either a Vertex object or an Edge object but I don't want to have duplicate code. Is this the right way to do it? Furthermore, am I handling the assignment var vertex = element / var edge = element correctly? I want to assign element to another Vertex/Edge object and use that for my look up.
Let me know if I need to clarify.
Thanks,
Hristo
Your code should work fine.
Note, however, that there is no point (other than clarity, which is a good thing) in writing var edge = element.
Javascript variables are untyped; there is no difference between edge and element.
Also, you should probably throw an exception instead of
// shouldn't come here
return false;
Finally, why are you searching self.verticies for an Edge?
Note, by the way, that you still have duplicate code.
You can rewrite your function like this:
function contains(element) {
var searchSet;
// if the element is a Vertex object, do this
if (element instanceof Vertex)
searchSet = self.verticies;
else if (element instanceof Edge)
searchSet = self.edges;
else
throw Error("Unexpected argument");
for (var i = 0; i < searchSet.length; i++) {
if (searchSet[i].id == element.id)
return true;
}
return false;
}
Here's an approach that has a couple of advantages:
Smaller functions (no big if/else if chain)
Produces an appropriate error for missing functions without any additional coding
See what you think:
function contains(element) {
window['contains_' + typeof element](element);
};
contains_string = function(element) {
alert('string: ' + element);
};
contains('hi!'); // produces alert
contains(3); // error: 'undefined is not a function'
It has some downsides too.
The error message isn't terribly informative (not much worse than default behavior though)
You 'pollute' the 'window' object here a little (it'd work better as part of an object)
etc
If you open a text file (.txt, .js, .css, ...) in your browser, it will get wrapped up in a nice DOM tree.
For example, open this .txt file and enter
javascript:alert(document.documentElement.innerHTML);
into your address bar. Nice... every major browser supports DOM manipulation on this wrapped text files, which is a great thing for writing powerful bookmarklets or user scripts.
However, Firefox fails on assigning any element's innerHTML. For example,
javascript: document.body.innerHTML = document.body.innerHTML.replace(/(\d+\s+\w+(?=\s+\d+))/g, '<span style="color:red">$1</span>'); void 0;
will work in every browser but Firefox.
Is there a trick to work around this issue?
(No, I don't want to parse the innerHTML string manually, and no, it doesn't work with jQuery either.)
It is failing because there is no body - even the file you linked is just a text file without a body (perhaps you are looking at it in firebug?).
The best thing to do would be a regex replace since you are working with text.
I think I found a working solution. First of all, let me give some more details on the question.
The problem is: Firefox creates something like
[some wrapper]
+---document
+---<html>[=documentElement]
+---<body>
+---<head/>
+---<pre>
+---[actual plain text contents]
but the wrapped document object does not support setting innerHTML properly. So, the basic idea is, create a new document object with full innerHTML support. Here's how it works:
var setInnerHTML = function(el, string) {
if (typeof window.supportsInnerHTML == 'undefined') {
var testParent = document.createElement('div');
testParent.innerHTML = '<br/>';
window.supportsInnerHTML = (testParent.firstChild.nodeType == 1);
}
if (window.supportsInnerHTML) {
el.innerHTML = string;
} else {
if (!window.cleanDocumentObject) {
/* this is where we get a 'clean' document object */
var f = document.createElement('iframe');
f.style.setProperty('display', 'none', 'important');
f.src = 'data:text/html,<!DOCTYPE html><html><title></title></html>';
document.body.appendChild(f); /* <- this is where FF creates f.contentDocument */
window.cleanDocumentObject = f.contentDocument;
document.body.removeChild(f);
}
/* let browser do the parsing */
var div = window.cleanDocumentObject.createElement('div');
div.innerHTML = string; /* this does work */
/* copy childNodes */
while(el.firstChild) {
el.removeChild(el.firstChild); /* cleanup */
}
for (var i = 0; i < div.childNodes.length; i++) {
el.appendChild(div.childNodes[i].cloneNode(true));
}
delete div;
}
}
edit:
This version is better and faster; using XSLTProcessor instead of iFrame.
var setInnerHTML = function(el, string) {
// element.innerHTML does not work on plain text files in FF; this restriction is similar to
// http://groups.google.com/group/mozilla.dev.extensions/t/55662db3ea44a198
var self = arguments.callee;
if (typeof self.supportsInnerHTML == 'undefined') {
var testParent = document.createElement('div');
testParent.innerHTML = '<p/>';
self.supportsInnerHTML = (testParent.firstChild.nodeType == 1);
}
if (self.supportsInnerHTML) {
el.innerHTML = string;
return el;
} else if (typeof XSLTProcessor == 'undefined') {
return undefined;
} else {
if (typeof self.cleanDocument == 'undefined')
self.cleanDocument = createHTMLDocument();
if (el.parentNode) {
var cleanEl = self.cleanDocument.importNode(el, false);
cleanEl.innerHTML = string;
el.parentNode.replaceChild(document.adoptNode(cleanEl), el);
} else {
var cleanEl = self.cleanDocument.adoptNode(el);
cleanEl.innerHTML = string;
el = document.adoptNode(cleanEl);
}
return el;
}
function createHTMLDocument() {
// Firefox does not support document.implementation.createHTMLDocument()
// cf. http://www.quirksmode.org/dom/w3c_html.html#t12
// the following is taken from http://gist.github.com/49453
var xmlDoc = document.implementation.createDocument('', 'fooblar', null);
var templ = '<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">'
+ '<xsl:output method="html"/><xsl:template match="/">'
+ '<html><title/><body/></html>'
+ '</xsl:template></xsl:stylesheet>';
var proc = new XSLTProcessor();
proc.importStylesheet(new DOMParser().parseFromString(templ,'text/xml'));
return proc.transformToDocument(xmlDoc);
}
};
use GreaseMonkey
It seems on a text document in Firefox 3, assigning the innerHTML of any node acts as if you are assigning to innerText (with “<html><body><pre>” prepended).
(Since DOM scripting on a non-XML/HTML document is completely undefined, it's certainly within Firefox's rights to do this; it appears to be a quick hack to display text files in an HTML page.)
So you can't use innerHTML on Firefox, but other DOM methods work:
var span= createElement('span');
span.style.color= 'red';
span.appendChild(document.createTextNode(match));