I need to wait for document readyness in my JavaScript, to insert a div at the bottom of the body.
I want to:
make this JavaScript file as small as possible (compile it down to < 1kb if possible)
inline the code that provides the document readyness in a closure (without exporting it)
Inlining the whole jQuery source in my file would be too big, so I'm looking for other methods. window.onload would work, but I specifically want document readyness, and not wait for the window.onload event.
Does anyone know a JS snippet that can do this? Or should I just copy part of jQuery's source?
EDIT:
I managed to crawl the jQuery source and put together with the following snippet:
var ready = (function () {
var ready_event_fired = false;
var ready_event_listener = function (fn) {
// Create an idempotent version of the 'fn' function
var idempotent_fn = function () {
if (ready_event_fired) {
return;
}
ready_event_fired = true;
return fn();
}
// The DOM ready check for Internet Explorer
var do_scroll_check = function () {
if (ready_event_fired) {
return;
}
// If IE is used, use the trick by Diego Perini
// http://javascript.nwbox.com/IEContentLoaded/
try {
document.documentElement.doScroll('left');
} catch(e) {
setTimeout(do_scroll_check, 1);
return;
}
// Execute any waiting functions
return idempotent_fn();
}
// If the browser ready event has already occured
if (document.readyState === "complete") {
return idempotent_fn()
}
// Mozilla, Opera and webkit nightlies currently support this event
if (document.addEventListener) {
// Use the handy event callback
document.addEventListener("DOMContentLoaded", idempotent_fn, false);
// A fallback to window.onload, that will always work
window.addEventListener("load", idempotent_fn, false);
// If IE event model is used
} else if (document.attachEvent) {
// ensure firing before onload; maybe late but safe also for iframes
document.attachEvent("onreadystatechange", idempotent_fn);
// A fallback to window.onload, that will always work
window.attachEvent("onload", idempotent_fn);
// If IE and not a frame: continually check to see if the document is ready
var toplevel = false;
try {
toplevel = window.frameElement == null;
} catch (e) {}
if (document.documentElement.doScroll && toplevel) {
return do_scroll_check();
}
}
};
return ready_event_listener;
})();
// TEST
var ready_1 = function () {
alert("ready 1");
};
var ready_2 = function () {
alert("ready 2");
};
ready(function () {
ready_1();
ready_2();
});
Thank you very much for helping me find this in the jQuery source. I can now put all this in a closure and do my work without exporting any functions and polluting the global scope.
One option would be to just get the core.js jQuery file from github.
You could probably slim it down quite a bit for code you don't need. Then run it through YUI compressor, and it should be pretty small.
http://github.com/jquery/jquery/blob/1.4.2/src/core.js (jQuery core)
http://yui.2clics.net/ (YUI compressor online)
I tried it, and this code worked properly:
$(function() {
var newDiv = document.createElement('div');
document.getElementsByTagName('body')[0].appendChild(newDiv);
});
Update: This was as small as I got it. It is entirely from jQuery and is around 1,278 bytes (compressed). Should get smaller when you gzip.
Only difference is that you need to call it like:
$.fn.ready(function() {
// your code
});
YUI Compressed:
(function(){var e=function(i,j){},c=window.jQuery,h=window.$,d,g=false,f=[],b;e.fn={ready:function(i){e.bindReady();if(e.isReady){i.call(document,e)}else{if(f){f.push(i)}}return this}};e.isReady=false;e.ready=function(){if(!e.isReady){if(!document.body){return setTimeout(e.ready,13)}e.isReady=true;if(f){var k,j=0;while((k=f[j++])){k.call(document,e)}f=null}if(e.fn.triggerHandler){e(document).triggerHandler("ready")}}};e.bindReady=function(){if(g){return}g=true;if(document.readyState==="complete"){return e.ready()}if(document.addEventListener){document.addEventListener("DOMContentLoaded",b,false);window.addEventListener("load",e.ready,false)}else{if(document.attachEvent){document.attachEvent("onreadystatechange",b);window.attachEvent("onload",e.ready);var i=false;try{i=window.frameElement==null}catch(j){}if(document.documentElement.doScroll&&i){a()}}}};d=e(document);if(document.addEventListener){b=function(){document.removeEventListener("DOMContentLoaded",b,false);e.ready()}}else{if(document.attachEvent){b=function(){if(document.readyState==="complete"){document.detachEvent("onreadystatechange",b);e.ready()}}}}function a(){if(e.isReady){return}try{document.documentElement.doScroll("left")}catch(i){setTimeout(a,1);return}e.ready()}window.jQuery=window.$=e})();
Full source (again, this is jQuery code):
(function() {
var jQuery = function( selector, context ) {
},
_jQuery = window.jQuery,
_$ = window.$,
rootjQuery,
readyBound = false,
readyList = [],
DOMContentLoaded;
jQuery.fn = {
ready: function( fn ) {
jQuery.bindReady();
if ( jQuery.isReady ) {
fn.call( document, jQuery );
} else if ( readyList ) {
readyList.push( fn );
}
return this;
}
};
jQuery.isReady = false;
jQuery.ready = function() {
if ( !jQuery.isReady ) {
if ( !document.body ) {
return setTimeout( jQuery.ready, 13 );
}
jQuery.isReady = true;
if ( readyList ) {
var fn, i = 0;
while ( (fn = readyList[ i++ ]) ) {
fn.call( document, jQuery );
}
readyList = null;
}
if ( jQuery.fn.triggerHandler ) {
jQuery( document ).triggerHandler( "ready" );
}
}
};
jQuery.bindReady = function() {
if ( readyBound ) {
return;
}
readyBound = true;
if ( document.readyState === "complete" ) {
return jQuery.ready();
}
if ( document.addEventListener ) {
document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
window.addEventListener( "load", jQuery.ready, false );
} else if ( document.attachEvent ) {
document.attachEvent("onreadystatechange", DOMContentLoaded);
window.attachEvent( "onload", jQuery.ready );
var toplevel = false;
try {
toplevel = window.frameElement == null;
} catch(e) {}
if ( document.documentElement.doScroll && toplevel ) {
doScrollCheck();
}
}
};
rootjQuery = jQuery(document);
if ( document.addEventListener ) {
DOMContentLoaded = function() {
document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false );
jQuery.ready();
};
} else if ( document.attachEvent ) {
DOMContentLoaded = function() {
if ( document.readyState === "complete" ) {
document.detachEvent( "onreadystatechange", DOMContentLoaded );
jQuery.ready();
}
};
}
function doScrollCheck() {
if ( jQuery.isReady ) {
return;
}
try {
document.documentElement.doScroll("left");
} catch(e) {
setTimeout( doScrollCheck, 1 );
return;
}
jQuery.ready();
}
window.jQuery = window.$ = jQuery;
})();
I'm sure there are more bytes that could be removed.
Don't forget:
/*!
* jQuery JavaScript Library v1.4.2
* http://jquery.com/
*
* Copyright 2010, John Resig
* Dual licensed under the MIT or GPL Version 2 licenses.
* http://jquery.org/license
*/
There are several implementations for "DOMReady" functions but most that I can find seem a bit dated, so I don't know how they will behave with IE8 and such.
I would recommend using jQuery's ready() as I think it promises the most cross-browser compatibility. I'm not an expert in jQuery's source code, but this seems to be the right spot (lines 812-845 or search for function bindReady).
You can start with script: http://snipplr.com/view/6029/domreadyjs/, not optimized (but work) for latest Safari though (e.g. use timer instead of supported DOMContentLoaded).
Related
I have a function that triggers an action on screen resize:
if ( window.addEventListener ) {
// For all major browsers, except IE 8 and earlier
window.addEventListener('resize', debounce(function () {
if ( $videosContainer.attr('data-video-hosting') === 'youtube' ) {
resizeVideo(netVidX, netVidY, cryptoVidX, cryptoVidY, 'youtube');
}
}, 250));
} else if (window.attachEvent ) {
// For IE 8 and earlier versions
window.attachEvent('resize', debounce(function () {
if ( $videosContainer.attr('data-video-hosting') === 'youtube' ) {
resizeVideo(netVidX, netVidY, cryptoVidX, cryptoVidY, 'youtube');
}
}, 250));
}
The debounce mechanism is defined as below:
// Smart debounce. Source - https://gist.github.com/vincentorback/9649034
// it works the exact same way as you'd expect, except it only fires once the keypress event is idle for XX ms
function debounce(fn, wait) {
var timeout;
return function () {
clearTimeout(timeout);
timeout = setTimeout(function () {
fn.apply(this, arguments)
}, (wait || 1));
}
}
As this function is called repeatedly, I would like to remove the listener/unbind it BEFORE I add it (again). It's in a slider of videos and I need to re-set the resize listener because some local scoping and the fact videos don't have the same dimensions eahc time...long story...)
In jquery I could do something along the lines of:
$video.off('resize').on('resize', function(e) {
In javascript I can use removeEventListener
if (window.removeEventListener) {
window.removeEventListener('resize', functionToUnbind, false);
} else if (window.detachEvent) {
window.detachEvent('resize', functionToUnbind, false);
}
But my issue is that due to where debounce is located, I don't manage to make removeEventListener work.
I tried putting all the "debounce block" inside a new function called functionToUnbind in order to be able to use the standard removelistener method as shown just above, but it does not work.
launchStuffForLatestSliderVideo();
function launchStuffForLatestSliderVideo() {
//remove first any lingering/remaining/existing resize listener on the videos
if (window.removeEventListener) {
window.removeEventListener('resize', functionToUnbind, false);
} else if (window.detachEvent) {
window.detachEvent('resize', functionToUnbind, false);
}
function functionToUnbind() {
debounce(function () {
if ( $videosContainer.attr('data-video-hosting') === 'youtube' ) {
resizeVideo(netVidX, netVidY, cryptoVidX, cryptoVidY, 'youtube');
}
}, 250)
}
//reset a new event listener for the latest video
if ( window.addEventListener ) {
// For all major browsers, except IE 8 and earlier
window.addEventListener('resize', functionToUnbind);
} else if (window.attachEvent ) {
// For IE 8 and earlier versions
window.attachEvent('resize', functionToUnbind);
}
}
How should I remove the listener every time before re-setting it? i think the issue comes from the debounce but I'm not sure
The problem is that the way you're trying to use it, each resize event will have a different reference to timeout variable.
Since you're always setting the event handler on a global window object which has exactly one instance, you might change timeout to be in a global scope so that it too has a single instance.
To do that, you should change your debounce function to look like this:
var globalResizeTimeout;
function debounce(fn, wait) {
clearTimeout(globalResizeTimeout);
globalResizeTimeout = setTimeout(function () {
fn.apply(this, arguments)
}, (wait || 1));
}
Suddenly today, out of nowhere, I started getting this one on every page on our website
Added non-passive event listener to a scroll-blocking 'touchstart' event.
Consider marking event handler as 'passive' to make the page more responsive
And its not just once or twice.. its like thousands of them....
They are running amok.
The only way to stop the flood of violations is to comment out this line
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.4/jquery.min.js" type="text/javascript"></script>
I read the other posts on what this violation means, but I really cannot see that I did anything different between two hours ago and now (I did a full rollback just to see if it helped)
It's almost like someone put a bug into jquery.min.js but I seriously doubt that, because then everyone would get it.
Any ideas? I tried debugging everything I could and I still have no idea what causes this?!?
UPDATE
I replaced all <button><md-tooltip>text</md-tooltip></button> with <button data-toggle="tooltip" title="text"></button> This removed 99% of all the violations.
This solve the problem to me:
jQuery.event.special.touchstart = {
setup: function( _, ns, handle ){
if ( ns.includes("noPreventDefault") ) {
this.addEventListener("touchstart", handle, { passive: false });
} else {
this.addEventListener("touchstart", handle, { passive: true });
}
}
};
I am using various events and this seems to solve my use case
(function () {
if (typeof EventTarget !== "undefined") {
let func = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function (type, fn, capture) {
this.func = func;
if(typeof capture !== "boolean"){
capture = capture || {};
capture.passive = false;
}
this.func(type, fn, capture);
};
};
}());
Ok digging this up a little more, this is not a new behavior, it has been reported a while ago and jQuery still hasn't fixed it.
The problem lies in the fact that for an handler to be passive it has to be certain of never calling preventDefault() but jQuery doesn't know in advance...
The only tip I could give you is change your console logging level and remove "Verbose". Follow up on this issue for ideas on solving this.
The answer from Sergio is correct, add it at the bottom jquery script. If there is issue touchstart and touchmove, just add same code and replace touchstart with touchmove, like this:
jQuery.event.special.touchstart = {
setup: function( _, ns, handle ){
if ( ns.includes("noPreventDefault") ) {
this.addEventListener("touchstart", handle, { passive: false });
} else {
this.addEventListener("touchstart", handle, { passive: true });
}
}
};
jQuery.event.special.touchmove = {
setup: function( _, ns, handle ){
if ( ns.includes("noPreventDefault") ) {
this.addEventListener("touchmove", handle, { passive: false });
} else {
this.addEventListener("touchmove", handle, { passive: true });
}
}
};
An improved solution from #neel-singh's answer
(function () {
if (typeof EventTarget !== 'undefined') {
let supportsPassive = false;
try {
// Test via a getter in the options object to see if the passive property is accessed
const opts = Object.defineProperty({}, 'passive', {
get: () => {
supportsPassive = true;
},
});
window.addEventListener('testPassive', null, opts);
window.removeEventListener('testPassive', null, opts);
} catch (e) {}
const func = EventTarget.prototype.addEventListener;
EventTarget.prototype.addEventListener = function (type, fn) {
this.func = func;
this.func(type, fn, supportsPassive ? { passive: false } : false);
};
}
})();
I think in addition to touch-based events you can add scroll-based fixes so to prevent google page score from flagging it for Desktop vs Mobile:
jQuery.event.special.wheel = {
setup: function( _, ns, handle ){
this.addEventListener("wheel", handle, { passive: true });
}
};
jQuery.event.special.mousewheel = {
setup: function( _, ns, handle ){
this.addEventListener("mousewheel", handle, { passive: true });
}
};
Just make one helper function and call it for as many events you need to call.
const preventPassiveWarning = event => {
jQuery.event.special[event] = {
setup: function (_, ns, handle) {
if (ns.includes("noPreventDefault")) {
this.addEventListener(event, handle, { passive: false });
} else {
this.addEventListener(event, handle, { passive: true });
}
}
}
}
//Call it here
preventPassiveWarning('touchstart')
preventPassiveWarning('touchmove')
I'm trying to recall some variable events after page load.
I'm using smoothstate.js so I load my page dynamically with ajax.
The code works only on page refresh if i simply put the code inside onAfter, so I think that there is another way to do that.
I want to recall this:
var snapper = new Snap({
element: document.getElementById('content'),
hyperextensible: false
});
var addEvent = function addEvent(element, eventName, func) {
if (element.addEventListener) {
return element.addEventListener(eventName, func, false);
} else if (element.attachEvent) {
return element.attachEvent("on" + eventName, func);
}
};
addEvent(document.getElementById('open-left'), 'click', function(){
if( snapper.state().state=="left" ){
snapper.close('left');
} else {
snapper.open('left');
}
});
var snapper2 = new Snap({
element: document.getElementById('content'),
hyperextensible: false
});
$('#open-right').click(function(){
if( snapper2.state().state=="right" ){
snapper2.close('right');
} else {
snapper2.open('right');
}
});
})(document, window.navigator, "standalone");
Inside this:
onAfter: function($container, $newContent) {
// Recall plugin here
}
How can I do that?
How to fix submenus disappearing in Chrome 43?
Using Extjs 4.
This was working on previous versions of Chrome.
This overrides needs to be added in order to fix this.
https://www.sencha.com/forum/showthread.php?301116-Submenus-disappear-in-Chrome-43-beta
(Thanks to festr user on Sencha forum - thought this needed to be on SO too)
// fix hide submenu (in chrome 43)
Ext.override(Ext.menu.Menu, {
onMouseLeave: function(e) {
var me = this;
// BEGIN FIX
var visibleSubmenu = false;
me.items.each(function(item) {
if(item.menu && item.menu.isVisible()) {
visibleSubmenu = true;
}
})
if(visibleSubmenu) {
//console.log('apply fix hide submenu');
return;
}
// END FIX
me.deactivateActiveItem();
if (me.disabled) {
return;
}
me.fireEvent('mouseleave', me, e);
}
});
In regards to that same link, https://www.sencha.com/forum/showthread.php?301116-Submenus-disappear-in-Chrome-43-beta, here's a more general, non-specific fix thanks to post #27 by siq:
Ext.apply(Ext.EventManager,{
normalizeEvent: function(eventName, fn) {
//start fix
var EventManager = Ext.EventManager,
supports = Ext.supports;
if(Ext.chromeVersion >=43 && eventName == 'mouseover'){
var origFn = fn;
fn = function(){
var me = this,
args = arguments;
setTimeout(
function(){
origFn.apply(me || Ext.global, args);
},
0);
};
}
//end fix
if (EventManager.mouseEnterLeaveRe.test(eventName) && !supports.MouseEnterLeave) {
if (fn) {
fn = Ext.Function.createInterceptor(fn, EventManager.contains);
}
eventName = eventName == 'mouseenter' ? 'mouseover' : 'mouseout';
} else if (eventName == 'mousewheel' && !supports.MouseWheel && !Ext.isOpera) {
eventName = 'DOMMouseScroll';
}
return {
eventName: eventName,
fn: fn
};
}
});
This has been tested on my end and I can verify that it works (v. 4.1.2).
I'm aware there's plenty of JQuery slideshows out there but I'm coding my own and have come across an obstacle I've not found an answer to specifically.
Background: An automated JQuery Slideshow plugin with pause/resume on hover/out
Problem: How to pause the slideshow on mousenter, and then finish the remaining time period of the setInterval on mouseleave instead of firing the setInterval again.
Solution: I think the only way to do this would be to use a self-invoking method with a delay attached and a stop() to handle the mouse behaviour which would allow the paused animation to resume for its proper remainder rather than firing the method again?
What are your thoughts?
Here's the code:
(function( $ ){
var methods = {
init: function() {
var cycle = window.setInterval(methods.automate, 1000);
$('.slider_control').on('click', 'span', function(){
methods.automate( $(this).attr('slideNumber') );
});
$('.slider_master_container')
.on({
'mouseenter': function() {
clearInterval(cycle);
},
'mouseleave': function() {
cycle = window.setInterval(methods.automate, 1000);
}
});
},
automate: function( el ) {
var $active = $('.slide.active'),
$next = el ? $('.slide[slideNumber=' + el) : $active.nextOrFirst();
$next.css('z-index', 2);
$active.fadeOut(1500, function() {
$active.css('z-index', 1).show().removeClass('active');
$next.css('z-index', 3).addClass('active');
});
}
};
$.fn.smartSlider = function( method ) {
if ( methods[method] ) {
return methods[ method ].apply( this, Array.prototype.slice.call( arguments, 1 ));
} else if ( typeof method === 'object' || ! method ) {
return methods.init.apply( this, arguments );
} else {
$.error( 'Method ' + method + ' does not exist on jQuery.smartSlider' );
}
};
})( jQuery );