Is there any tool that enables you to \"hot swap\" JavaScript contents while executing a webpage?
I am looking for something similar to what HotSpot does for Java,
Since I had a similar problem to solve I wrote a small js lib to hotswap javascript, css and image files. It´s of course open source on github: hotswap.js
Hope it helps.
Update: I have attached the full lib source here. To use it simply copy the content into a file (e.g.: hotswap.js) and insert the script tag into your website like this:
API:
// refresh .js files
hotswap.refreshAllJs(arrExcludedFiles);
hotswap.refreshJs(arrIncludedFiles);
// refresh .css files
hotswap.refreshAllCss(arrExcludedFiles);
hotswap.refreshCss(arrIncludedFiles);
// refresh images
hotswap.refreshAllImg(arrExcludedFiles);
hotswap.refreshImg(arrIncludedFiles);
// show a gui (this is optional and not required for hotswap to work) (Click on the "H").
hotswap.createGui();
// Examples:
// refresh all .js files
hotswap.refreshAllJs();
// refresh main.css only
hotswap.refreshCss( ["main.js"] );
// refresh all images (img tags) except "dont-refreh-me.png".
hotswap.refreshAllImg( ["dont-refreh-me.png"] );
Full source (v. 0.2.0):
I had to remove all comments to make it under the 30000 char answer limit. The inline html + css is ugly I know, but I wanted to keep this within on single .js file.
(function() {
var root = this;
var previousHotswap = root.hotswap;
var hotswap = function()
{
if (!(this instanceof hotswap))
{
return new hotswap();
}
else
{
return this;
}
};
root.hotswap = hotswap();
hotswap.prototype.VERSION = '0.2.0';
hotswap.prototype.RND_PARAM_NAME = 'hs982345jkasg89zqnsl';
hotswap.prototype.FILE_REMOVAL_DELAY = 400;
hotswap.prototype.CSS_HTML_PREFIX = 'hs982345jkasg89zqnsl';
hotswap.prototype._prefix = false;
hotswap.prototype._prefixCache = [];
hotswap.prototype._guiCache = {};
hotswap.prototype._guiGuiRefreshInterval = null;
hotswap.prototype._guiHtml = '' +
''+
' '+
' '+
' '+
' H'+
' '+
' '+
' '+
' - CSS
'+
' '+
'
'+
' '+
' - JS
'+
' '+
'
'+
' '+
' - IMG
'+
' '+
'
'+
' '+
' '+
' ';
var
xGetElementById = function(sId){ return document.getElementById(sId) },
xGetElementsByTagName = function(sTags){ return document.getElementsByTagName(sTags) },
xAppendChild = function(parent, child){ return parent.appendChild(child) },
xCloneNode = function(node){ return document.cloneNode(node) },
xCreateElement = function(sTag){ return document.createElement(sTag) },
xCloneNode = function(ele, deep){ return ele.cloneNode(deep) },
xRemove = function(ele)
{
if( typeof ele.parentNode != "undefined" && ele.parentNode )
{
ele.parentNode.removeChild( ele );
}
},
xAddEventListener = function(ele, sEvent, fn, bCaptureOrBubble)
{
if( xIsEmpty(bCaptureOrBubble) )
{
bCaptureOrBubble = false;
}
if (ele.addEventListener)
{
ele.addEventListener(sEvent, fn, bCaptureOrBubble);
return true;
}
else if (ele.attachEvent)
{
return ele.attachEvent('on' + sEvent, fn);
}
else
{
ele['on' + sEvent] = fn;
}
},
xStopPropagation = function(evt)
{
if (evt && evt.stopPropogation)
{
evt.stopPropogation();
}
else if (window.event && window.event.cancelBubble)
{
window.event.cancelBubble = true;
}
},
xPreventDefault = function(evt)
{
if (evt && evt.preventDefault)
{
evt.preventDefault();
}
else if (window.event && window.event.returnValue)
{
window.eventReturnValue = false;
}
},
xContains = function(sHaystack, sNeedle)
{
return sHaystack.indexOf(sNeedle) >= 0
},
xStartsWith = function(sHaystack, sNeedle)
{
return sHaystack.indexOf(sNeedle) === 0
},
xReplace = function(sHaystack, sNeedle, sReplacement)
{
if( xIsEmpty(sReplacement) )
{
sReplacement = "";
}
return sHaystack.split(sNeedle).join(sReplacement);
},
xGetAttribute = function(ele, sAttr)
{
var result = (ele.getAttribute && ele.getAttribute(sAttr)) || null;
if( !result ) {
result = ele[sAttr];
}
if( !result ) {
var attrs = ele.attributes;
var length = attrs.length;
for(var i = 0; i < length; i++)
if(attrs[i].nodeName === sAttr)
result = attrs[i].nodeValue;
}
return result;
},
xSetAttribute = function(ele, sAttr, value)
{
if(ele.setAttribute)
{
ele.setAttribute(sAttr, value)
}
else
{
ele[sAttr] = value;
}
},
xGetParent = function(ele)
{
return ele.parentNode || ele.parentElement;
},
xInsertAfter = function( refEle, newEle )
{
return xGetParent(refEle).insertBefore(newEle, refEle.nextSibling);
},
xBind = function(func, context)
{
if (Function.prototype.bind && func.bind === Function.prototype.bind)
{
return func.bind(context);
}
else
{
return function() {
if( arguments.length > 2 )
{
return func.apply(context, arguments.slice(2));
}
else
{
return func.apply(context);
}
};
}
},
xIsEmpty = function(value)
{
var ret = true;
if( value instanceof Object )
{
for(var i in value){ if(value.hasOwnProperty(i)){return false}}
return true;
}
ret = typeof value === "undefined" || value === undefined || value === null || value === "";
return ret;
},
xAddClass = function(ele, sClass)
{
var clazz = xGetAttribute( ele, "class" );
if( !xHasClass(ele, sClass) )
{
xSetAttribute( ele, "class", clazz + " " + sClass );
}
},
xRemoveClass = function(ele, sClass)
{
var clazz = xGetAttribute( ele, "class" );
if( xHasClass(ele, sClass) )
{
xSetAttribute( ele, "class", xReplace( clazz, sClass, "" ) );
}
},
xHasClass = function(ele, sClass)
{
var clazz = xGetAttribute( ele, "class" );
return !xIsEmpty(clazz) && xContains( clazz, sClass );
};
hotswap.prototype._recreate = function( type, xcludedFiles, xcludeComparator, nDeleteDelay, bForceRecreation )
{
if( typeof nDeleteDelay == "undefined")
{
nDeleteDelay = 0;
}
if( typeof bForceRecreation == "undefined")
{
bForceRecreation = false;
}
var tags = this._getFilesByType(type, xcludedFiles, xcludeComparator);
var newTags = [];
var removeTags = [];
var i, src, detected, node, srcAttributeName;
for(i=0; i 0 )
{
for(var i=0; i < removeTags.length; i++) {
xSetAttribute(removeTags[i], "data-hotswap-deleted", "1");
}
setTimeout( function() {
for(var i=0; i < removeTags.length; i++) {
xRemove(removeTags[i]);
}
}, nDeleteDelay);
}
else
{
for(var i=0; i < removeTags.length; i++) {
xRemove(removeTags[i]);
}
}
};
hotswap.prototype._reload = function( type, xcludedFiles, xcludeComparator )
{
var tags = this._getFilesByType(type, xcludedFiles, xcludeComparator);
var i, src, node, srcAttributeName;
for(i=0; i= 0)
{
this._prefixCache[foundAt] = prefixHistory;
}
else
{
this._prefixCache.push( prefixHistory );
}
}
else
{
prefix = "";
}
if( !applyPrefix )
{
prefix = "";
}
url = prefix + cleanUrl + randomizedQueryString;
return (getCleanUrl) ? (cleanUrl + queryString) : url;
}
hotswap.prototype.refreshAllJs = function( excludedFiles )
{
if( typeof excludedFiles == "undefined" || !excludedFiles)
{
excludedFiles = []
}
excludedFiles.push("hotswap.js");
this._recreate( "js", excludedFiles, false, 0, true );
};
hotswap.prototype.refreshJs = function( includedFiles )
{
this._recreate( "js", includedFiles, true, 0, true );
};
hotswap.prototype.refreshAllCss = function( excludedFiles )
{
this._recreate( "css", excludedFiles, false, this.FILE_REMOVAL_DELAY );
};
hotswap.prototype.refreshCss = function( includedFiles )
{
this._recreate( "css", includedFiles, true, this.FILE_REMOVAL_DELAY );
};
hotswap.prototype.refreshAllImg = function( excludedFiles )
{
this._reload( "img", excludedFiles, false );
};
hotswap.prototype.refreshImg = function( includedFiles )
{
this._reload( "img", includedFiles, true );
};
hotswap.prototype.setPrefix = function( prefix )
{
this._prefix = prefix;
var gui = xGetElementById(this.CSS_HTML_PREFIX + "_wrapper");
if( gui )
{
if( !xIsEmpty(this._prefix) && this._prefix )
{
xGetElementById(this.CSS_HTML_PREFIX+"-prefix").value = this._prefix;
}
else
{
xGetElementById(this.CSS_HTML_PREFIX+"-prefix").value = "";
}
}
}
hotswap.prototype.getPrefix = function()
{
return this._prefix;
}
hotswap.prototype.createGui = function( nDistanceFromTopInPercent )
{
if( xIsEmpty(nDistanceFromTopInPercent) )
{
nDistanceFromTopInPercent = 20;
}
var gui = xGetElementById(this.CSS_HTML_PREFIX + "_wrapper");
if( gui )
{
xRemove(xGetElementById(this.CSS_HTML_PREFIX + "_wrapper"));
}
gui = xCreateElement("div");
xSetAttribute( gui, "id", this.CSS_HTML_PREFIX + "_wrapper" );
var guiHtml = xReplace( this._guiHtml, "PREFIX", this.CSS_HTML_PREFIX );
guiHtml = xReplace( guiHtml, '20%;/*distance from top*/', nDistanceFromTopInPercent+'%;/*distance from top*/' );
gui.innerHTML = guiHtml;
xAppendChild( xGetElementsByTagName("body")[0], gui );
if( !xIsEmpty(this._guiCache) )
{
this._guiCache = {};
}
this._guiCreateFilesList();
if( !xIsEmpty(this._prefix) && this._prefix )
{
xGetElementById(this.CSS_HTML_PREFIX+"-prefix").value = this._prefix;
}
var self = this;
xAddEventListener( xGetElementById(this.CSS_HTML_PREFIX+"-toggle"), "click", function(evt)
{
var gui = xGetElementById(self.CSS_HTML_PREFIX);
if( xHasClass(gui, "mini") )
{
xRemoveClass( gui, "mini" );
}
else
{
xAddClass( gui, "mini" );
}
});
xAddEventListener( xGetElementById(this.CSS_HTML_PREFIX+"-prefix"), "blur", function(evt)
{
self._guiPrefixChanged(evt.target);
});
xAddEventListener( xGetElementById(this.CSS_HTML_PREFIX+"-submit-selected"), "click", function(evt)
{
self._guiRefreshSelected()
});
xAddEventListener( xGetElementById(this.CSS_HTML_PREFIX+"-submit-start"), "click", function(evt)
{
if( xGetAttribute(evt.target, "class") != this.CSS_HTML_PREFIX+"-seconds" )
{
var input, nSeconds = 1;
var children = evt.target.children;
for(var i=0; i 0 )
{
this.refreshCss( activeFiles['css'] );
}
if( activeFiles['js'].length > 0 )
{
this.refreshJs( activeFiles['js'] );
}
if( activeFiles['img'].length > 0 )
{
this.refreshImg( activeFiles['img'] );
}
},
hotswap.prototype._guiRefreshStart = function( nSeconds )
{
if( this._guiGuiRefreshInterval !== null )
{
this._guiRefreshStop();
}
var self = this;
this._guiGuiRefreshInterval = setInterval( xBind(this._guiRefreshSelected, this), nSeconds * 1000 );
xAddClass( xGetElementById(this.CSS_HTML_PREFIX+"-submit-start"), "inactive" );
xRemoveClass( xGetElementById(this.CSS_HTML_PREFIX+"-submit-stop"), "inactive" );
},
hotswap.prototype._guiRefreshStop = function()
{
if( this._guiGuiRefreshInterval !== null )
{
clearInterval(this._guiGuiRefreshInterval);
}
this._guiGuiRefreshInterval = null;
xRemoveClass( xGetElementById(this.CSS_HTML_PREFIX+"-submit-start"), "inactive" );
xAddClass( xGetElementById(this.CSS_HTML_PREFIX+"-submit-stop"), "inactive" );
}
hotswap.prototype.guiRefreshFilesList = function()
{
this._guiCreateFilesList();
}
}).call(this);