I'm using the NicEdit WYSIWYG plugin on my site.
It's come to my attention that when NicEdit is instantiated in Chrome, the following Javascript error is generated:
Uncaught TypeError: Object has no method 'createRange'
This doesn't stop the plugin from working, but I would like to prevent this if possible. Here is the offending method:
getRng : function() {
var s = this.getSel();
if(!s) { return null; }
return (s.rangeCount > 0) ? s.getRangeAt(0) : s.createRange();
}
NicEdit seems to be pretty much dead as a project, which is why I am asking this question here instead of over at the NicEdit forums. I am hoping that someone knows of a 'quickfix' to this problem. In all other respects NicEdit works well for me, so I am reluctant to change over to a different WYISWYG plugin just yet...
Thanks (in advance) for your help.
The problem is that the implementation of the selection object for Webkit does not define a createRange( ) method. That method seems to be specific to Internet Explorer. For Webkit and Gecko DOM implementations, the createRange( ) method is defined on the document object. With this knowledge, the fix for getRng( ) becomes:
getRng : function() {
var s = this.getSel();
var rng;
if(!s) { return null; }
if (s.rangeCount > 0) {
rng = s.getRangeAt(0);
} else if ( typeof s.createRange === 'undefined' ) {
rng = document.createRange();
} else {
rng = s.createRange();
}
return rng;
},
I encountered this as I was evaluating a number of rich text editors for an upcoming project and had to create a sample page with nicEdit.
The version at https://github.com/danishkhan/NicEdit contains this and other bugfixes.
This particular fix: https://github.com/danishkhan/NicEdit/commit/efa6a1e8867b745b841157e919a0055cb626d2c4
Same code, written in nicEdit current design:
getRng : function() {
var s = this.getSel();
if(!s) { return null; }
return (s.rangeCount > 0) ? s.getRangeAt(0) : (typeof s.createRange == 'undefined') ? document.createRange() : s.createRange();
},
Related
I'm using jQuery in a website has a polyfill for the built-in String.trim(). Site used to run in IE 8 a lot and needed the polyfill, but it doesn't anymore. Unfortunately I can't remove the polyfill from the page -- I don't have permissions to touch that and there is no possible way for me to remove it -- so this bit of code runs before anything I can control:
String.prototype.trim = function() {
return this.replace(/^\s\s*/, "").replace(/\s\s*$/, "")
}
Then jQuery comes along and does this, not realizing that the native String.trim has already by messed with:
// Use native String.trim function wherever possible
trim: core_trim && !core_trim.call("\uFEFF\xA0") ?
function( text ) {
return text == null ?
"" :
core_trim.call( text );
} :
// Otherwise use our own trimming functionality
function( text ) {
return text == null ?
"" :
( text + "" ).replace( rtrim, "" );
},
Up to now this hasn't really been much of a problem, but I'm using the Datatables plugin for jQuery and it has many places where it calls $.trim() on data that isn't a string, like number or arrays. The native code in IE 11 and Chrome (the browsers we target) knows to just return the value of $.trim(6), but the polyfill doesn't.
I tried redefining the the prototype with a function that should work:
String.prototype.trim = function(){
if(typeof this.valueOf(this) === 'string'){
return this.replace(/^\s\s*/, "").replace(/\s\s*$/, "");
} else {
return this.valueOf(this);
}
}
But that didn't work because jQuery has already extend using the polyfill and further changes to the prototype won't change what jQuery is using.
I tried following this thread to redefine $.trim(), but that didn't work.
Is there a way to return String.prototype.trim() to its native code?
Is there a way to redefine $.trim()?
Some other idea I haven't thought of?
You can override jQuery core methods
(function(){
var string = " TRIM ME ";
console.log(jQuery.trim(string));
// Define overriding method.
jQuery.trim = function(string){
return string;
}
console.log(jQuery.trim(string));
})();
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
Simply override jQuery's $.trim() using String.prototype.trim(), then override String.prototype.trim() with your function:
var trimText = " Some text ";
String.prototype.trim = function() {
return this.replace(/^\s\s*/, "").replace(/\s\s*$/, "")
}
$.trim = function(string) {
return String.prototype.trim(string);
}
trimText = trimText.trim();
console.log(trimText);
<script src="https://code.jquery.com/jquery-3.3.1.js"></script>
Based on Jack Bashford's answer I came up with this.
String.prototype.trim = function(){
if(typeof this.valueOf(this) === 'string'){
return this.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, '');
} else {
return this.valueOf(this);
}
};
$.trim = function(e){
return String.prototype.trim.call(e);
};
Part of the original problem was that I needed to fix it so that if $.trim(number) or $.trim(array) was called it wouldn't throw and error.
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'm building a very simple Validation plugin that uses part of the Constraint validation API. I'm using 2 features of the API, valueMissing and patternMismatch like so:
var el = $(this)[0];
if ( el.validity.valueMissing || el.validity.patternMismatch ) {
$errMsg.show();
} else {
$errMsg.hide();
}
I'd like to write my own polyfill so these features work in older browsers as opposed to using an entire HTML5 validation library or plugin.
So I guess what I'm trying to achieve is something like this:
if (!typeof document.createElement( 'input' ).checkValidity == 'function'){
validity.valueMissing = function(){
// check for value
}
}
However, I can't figure out how to go about this, nor how the element is used as the first part of the function:
el.validity.valueMissing
Thanks in advance for any help or suggestions!
There would be a bit of work involved for this poly, but what you need to do, is alter HTMLInputElement's prototype, so each new instance has those methods and propertyes (validity, checkValidity and so on).
Something along these lines:
HTMLInputElement.prototype.testMethod = function (e) { console.log(e) };
HTMLInputElement.prototype.testProp = 'foo';
var input = document.createElement('input');
input.testMethod(input.testProp);
So I finally figured this out with some help from Poelinca's answer...
if ( typeof document.createElement( 'input' ).checkValidity !== 'function' ) {
Object.defineProperty( Element.prototype, 'validity', {
get: function() {
return this;
}
});
Object.defineProperty( Element.prototype.validity, 'valueMissing', {
get: function() {
return this.value !== "";
}
});
Object.defineProperty( Element.prototype.validity, 'patternMismatch', {
get: function() {
var pattern = this.getAttribute('pattern'),
regEx = pattern !== null ? eval( "{/" + pattern + "/}" ) : "";
value = this.value;
if ( regEx !== "" ) {
return regEx.test( value );
} else {
return true;
}
return false;
}
});
}
I used Object.defineProperty so I could call the function without using the parenthesis, which is how the native Constraint validation API works:
if ( el.validity.valueMissing || el.validity.patternMismatch ) { //...
From my research I learned that extending the DOM like this isn't really recommended as you can run into a lot of issues. For details read this article. The comments are particularly helpful as this article is quite dated.
In my case what I'm trying to do is quite simple and I only need to support IE8+. Maybe a little overkill but I like it.
Please feel free to improve / critique my answer!
Thanks!
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);
}
}
}
};
}());
document.getElementById doesn't seem to work across all browsers (I mean some old ones) and I am sure there are developers who are not aware of this.
What solutions would you suggest to make it cross-browser?
Thanks
If document.getElementById doesn't work then either:
You're doing it wrong (invalid HTML, trying to access names instead of IDs, etc)
or
You're working with Netscape 4.x and Internet Explorer 4.x
There are three ways to deal with browsers of this era.
Tell people to upgrade. They are unmaintained, security hole ridden nightmares for user and author alike.
Build on stuff that works and make sure your JS checks for the existence of getElementById and friends before trying to use them ( if (!document.getElementById) { return false; /* Insufficient DOM to bother with JS here */ } )
Learn about document.all and document.layers
Are you sure its not this kind of problem? Have a look its interesting, I didn't know that before.
However, to complement what is already suggested by David Dorward, you write a function like below.
function getElement (id) {
if (document.getElementById) {
return document.getElementById(id);
}
else if (document.all) {
return window.document.all[id];
}
else if (document.layers) {
return window.document.layers[id];
}
}
getElemID(obj){
if(document.getElementByID){
return document.getElementByID(obj);
}
else if (document.all){
return document.all[obj];
}
else if (document.layers){
return document.layers[obj];
}
else {
alert("Could not find support");
return false;
}
}
function getDOM() {
if (document.getElementById) {
return document.getElementById;
}
var window_document = window.document || {};
var elements = window_document.all || window_document.layers;
if(elements) {
return function(x) { return elements[x]; }
}
// everything failed
throw new InternalError('No means to "getElementById"');
}
... then
var getElementById;
try {
getElementById = getDOM();
} catch(err) {
alert(err);
}
// implicit 0K
var oHTMLElement = getElementById('#main');