Question

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, a way to "hot deploy" new JS code without having to reload the whole page.

Is there anything like that out there?

Clarifying in case people don't understand "hot swap", as indicated by lock:

By "hot swap" I mean allowing me to change parts of the code contained on the page itself and its .js files.

Then this framework would detect the change - either automagically or by some indication from my end - and reload the code dynamically, avoiding the new server-side post (reload).

That kind of approach would simplify debugging and error fixing, since you don't need to reload the page and start the interaction all over again, from scratch.

Was it helpful?

Solution

Interesting idea :)

I wrote the following bookmarklet:

function reload(){var scripts=document.getElementsByTagName("script");var head=document.getElementsByTagName("head")[0];var newScripts=[];var removeScripts=[];for(var i=0;i<scripts.length;i++){var parent=scripts[i].parentNode;if(parent==head&&scripts[i].src){var newScript={};newScript.src=scripts[i].src;newScript.innerHTML=scripts[i].innerHTML;newScripts.push(newScript);removeScripts.push(scripts[i]);}}for(var i=0;i<removeScripts.length;i++){head.removeChild(removeScripts[i]);}for(var i=0;i<newScripts.length;i++){var script=document.createElement("script");script.src=newScripts[i].src;script.type="text/javascript";script.innerHTML=newScripts[i].innerHTML;head.appendChild(script);}}

add that to the location of a new bookmark, and it will reload all the javascripts referenced in <head>. Not sure how well this will work in practice, but it was worth a shot :) I guess you'd have to be very careful in the way you write your scripts, so as not to have things added to the page body multiple times, etc. Maybe support for a 'reload="true"' attribute could be useful, that way you could tag only your libraries as reloadable.

Full source:

function reload() {
    var scripts = document.getElementsByTagName("script");
    var head = document.getElementsByTagName("head")[0];
    var newScripts = [];
    var removeScripts = [];
    for(var i=0; i < scripts.length; i++) {
        var parent = scripts[i].parentNode;
        if(parent == head && scripts[i].src) {
            var newScript = {};
            newScript.src = scripts[i].src;
            newScript.innerHTML = scripts[i].innerHTML;
            newScripts.push(newScript);
            removeScripts.push(scripts[i]);
        }
    }

    for(var i=0; i < removeScripts.length; i++) {
        head.removeChild(removeScripts[i]);
    }

    for(var i=0; i < newScripts.length; i++) {
        var script = document.createElement("script");
        script.src = newScripts[i].src;
        script.type = "text/javascript";
        script.innerHTML = newScripts[i].innerHTML;
        head.appendChild(script);
    }
}

OTHER TIPS

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:

<script src="hotswap.js"></script>

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 = '' +
        '<style type="text/css">'+
        '    #PREFIX'+
        '    {'+
        '        display: block;'+
        '        position: fixed;'+
        '        top: 20%;/*distance from top*/'+
        '        right: 0;'+
        '        z-index: 99999;'+
        '        width: 20em;'+
        '        height: auto;'+
        '        color: black;'+
        '        background-color: #666666;'+
        '        font-family: Verdana, sans-serif;'+
        '        font-size: 0.8em;'+
        '        -webkit-box-shadow: 0 0px 0.3em 0.1em #999999;'+
        '        -moz-box-shadow: 0 0px 0.3em 0.1em #999999;'+
        '        box-shadow: 0 0px 0.3em 0.1em #999999;'+
        '    }'+
        '    #PREFIX.mini'+
        '    {'+
        '        width: 2.9em;'+
        '        height: 2.9em;'+
        '        overflow:hidden;'+
        '    }'+
        '    #PREFIX.mini .PREFIX-header input, #PREFIX.mini .PREFIX-list, #PREFIX.mini .PREFIX-footer'+
        '    {'+
        '        display:none;'+
        '    }'+
        '        #PREFIX.mini .PREFIX-header div'+
        '    {'+
        '        display: block;'+
        '        width: 100%;'+
        '        height: 100%;'+
        '    }'+
        '    #PREFIX input'+
        '    {'+
        '        font-size: 1.0em;'+
        '        border: 0.1em solid #999999;'+
        '        border-radius: 0.2em;'+
        '        padding: 0.2em 0.1em;'+
        '        }'+
        '    #PREFIX .PREFIX-header'+
        '    {'+
        '        height: 2.4em;'+
        '        overflow:hidden;'+
        '        padding: 0.4em;'+
        '        color: white;'+
        '        background-color: black;'+
        '        }'+
        '    #PREFIX .PREFIX-header input'+
        '    {'+
        '        width: 83.5%;'+
        '        height: 1.6em;'+
        '    }'+
        '    #PREFIX .PREFIX-header div'+
        '    {'+
        '        position: absolute;'+
        '        top:0;'+
        '        right:0;'+
        '        width: 14.5%;'+
        '        height: 1.6em;'+
        '        line-height: 1.4em;'+
        '        text-align: center;'+
        '        font-size: 2em;'+
        '        font-weight: bold;'+
        '        cursor: pointer;'+
        '    }'+
        '    #PREFIX .PREFIX-header div:hover'+
        '    {'+
        '        background-color: #444444;'+
        '    }'+
        '    #PREFIX .PREFIX-list'+
        '    {'+
        '        width: 100%;'+
        '        height: 22em;'+
        '        overflow: auto;'+
        '    }'+
        '    #PREFIX ul'+
        '    {'+
        '        list-style-type: none;'+
        '        list-style-position: inside;'+
        '        padding: 0;'+
        '        margin: 0.5em 0.5em 1.2em 0.5em;'+
        '    }'+
        '    #PREFIX ul li'+
        '    {'+
        '        margin: 0.3em;'+
        '        padding: 0.5em 0.5em;'+
        '        color: white;'+
        '        background-color: #717171;'+
        '        font-size: 0.9em;'+
        '        line-height: 1.5em;'+
        '        cursor: pointer;'+
        '    }'+
        '    #PREFIX ul li:hover'+
        '    {'+
        '        background-color: #797979;'+
        '    }'+
        '    #PREFIX ul li.template'+
        '    {'+
        '        display: none;'+
        '    }'+
        '    #PREFIX ul li.active'+
        '    {'+
        '        background-color: black;'+
        '    }'+
        '    #PREFIX ul li.PREFIX-headline'+
        '    {'+
        '        color: white;'+
        '        background-color: transparent;'+
        '        text-align: center;'+
        '        font-weight: bold;'+
        '        cursor: default;'+
        '    }'+
        '    #PREFIX .PREFIX-footer'+
        '    {'+
        '        padding: 0;'+
        '        margin:0;'+
        '        background-color: #444444;'+
        '    }'+
        '    #PREFIX .PREFIX-footer ul'+
        '    {'+
        '        margin: 0;'+
        '        padding: 0.5em;'+
        '    }'+
        '    #PREFIX .PREFIX-footer ul li'+
        '    {'+
        '        color: white;'+
        '        background-color: black;'+
        '        font-size: 1.0em;'+
        '        border-radius: 0.5em;'+
        '        text-align: center;'+
        '        height: 2.2em;'+
        '        line-height: 2.2em;'+
        '    }'+
        '    #PREFIX .PREFIX-footer ul li input.PREFIX-seconds'+
        '    {'+
        '        text-align: center;'+
        '        width: 2em;'+
        '    }'+
        '    #PREFIX .PREFIX-footer ul li:hover'+
        '    {'+
        '        background-color: #222222;'+
        '        }'+
        '    #PREFIX .PREFIX-footer ul li.inactive'+
        '    {'+
        '        background-color: #666666;'+
        '        cursor: default;'+
        '    }'+
        '    </style>'+
        '    <div id="PREFIX" class="mini">'+
        '        <div class="PREFIX-header">'+
        '            <input id="PREFIX-prefix" placeholder="prefix" type="text" name="" />'+
        '            <div id="PREFIX-toggle">H</div>'+
        '        </div>'+
        '        <div class="PREFIX-list">'+
        '            <ul id="PREFIX-css">'+
        '                <li class="PREFIX-headline">CSS</li>'+
        '                <li class="template"></li>'+
        '            </ul>'+
        '            <ul id="PREFIX-js">'+
        '                <li class="PREFIX-headline">JS</li>'+
        '                <li class="template"></li>'+
        '            </ul>'+
        '            <ul id="PREFIX-img">'+
        '                <li class="PREFIX-headline">IMG</li>'+
        '                <li class="template"></li>'+
        '            </ul>'+
        '        </div>'+
        '        <div class="PREFIX-footer">'+
        '            <ul>'+
        '                <li id="PREFIX-submit-selected">refresh selected</li>'+
        '                <li id="PREFIX-submit-start">refresh every <input  class="PREFIX-seconds" type="text" value="1" /> sec.</li>'+
        '                <li id="PREFIX-submit-stop" class="inactive">stop refreshing</li>'+
        '                <li id="PREFIX-submit-refresh-list">refresh list</li>'+
        '            </ul>'+
        '        </div>'+
        '    </div>';
    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<tags.length; i++)
        {
            node = tags[i].node;
            srcAttributeName = tags[i].srcAttributeName;
            var newNode = {
                node: null,
                oldNode: node,
                parent: xGetParent(node)
            };
            if( bForceRecreation )
            {
                newNode.node = xCreateElement("script");
            }
            else
            {
                newNode.node = xCloneNode(node, false);
            }
            for (var p in node) {
                if (node.hasOwnProperty(p)) {
                    newNode.node.p = node.p;
                }
            }
            src = xGetAttribute( node, srcAttributeName );
            xSetAttribute( newNode.node, srcAttributeName, this._updatedUrl(src) );
            newTags.push(newNode);
            removeTags.push(node);
        }
        for(var i=0; i < newTags.length; i++) {
            xInsertAfter(newTags[i].oldNode, newTags[i].node);
        }
        if( nDeleteDelay > 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<tags.length; i++)
        {
            node = tags[i].node;
            srcAttributeName = tags[i].srcAttributeName;
            // update the src property
            src = xGetAttribute( node, srcAttributeName );
            xSetAttribute( node, srcAttributeName, this._updatedUrl(src) );
        }
    };
    hotswap.prototype._getFilesByType = function( type, xcludedFiles, xcludeComparator )
    {
        var files;
        switch(type)
        {
            case "css":
                files = this._getFiles(
                    "css",
                    "link",
                    function(ele)
                    {
                        return (xGetAttribute(ele, "rel") == "stylesheet" || xGetAttribute(ele, "type") == "text/css");
                    },
                    "href",
                    xcludedFiles,
                    xcludeComparator
                )
                break;

            case "js":
                files = this._getFiles(
                    "js",
                    "script",
                    function(ele)
                    {
                        return (xGetAttribute(ele, "type") == "" || xGetAttribute(ele, "type") == "text/javascript");
                    },
                    "src",
                    xcludedFiles,
                    xcludeComparator
                )
                break;

            case "img":
                files = this._getFiles(
                    "img",
                    "img",
                    function(ele)
                    {
                        return (xGetAttribute(ele, "src") != "");
                    },
                    "src",
                    xcludedFiles,
                    xcludeComparator
                )
                break;
        }

        return files;
    }
    hotswap.prototype._getFiles = function( type, tagName, tagFilterFunc, srcAttributeName, xcludedFiles, xcludeComparator )
    {
        if( typeof xcludedFiles == "undefined" || !xcludedFiles)
        {
            xcludedFiles = [];
        }

        if( typeof xcludeComparator == "undefined" || !xcludeComparator)
        {
            xcludeComparator = false;
        }

        var fileNodes = [];
        var tags = xGetElementsByTagName(tagName);
        var src, detected, node;
        for(var i=0; i<tags.length; i++) {
            node = tags[i];
            src = xGetAttribute(node,[srcAttributeName]);
            if( xIsEmpty( xGetAttribute(node, "data-hotswap-deleted") ) )
            {
                if(src && tagFilterFunc(node))
                {
                    detected = false;
                    for(var j=0; j<xcludedFiles.length; j++) {
                        if( xContains(src,xcludedFiles[j]) )
                        {
                            detected = true;
                            break;
                        }
                    }
                    if( detected == xcludeComparator )
                    {
                        fileNodes.push({
                            type: type,
                            node : node,
                            tagName : tagName,
                            srcAttributeName : srcAttributeName
                        });
                    }
                }
            }
        }

        return fileNodes;
    };
    hotswap.prototype._updatedUrl = function( url, getCleanUrl )
    {
        var cleanUrl;
        if( typeof getCleanUrl == "undefined")
        {
            getCleanUrl = false;
        }
        url = cleanUrl = url.replace(new RegExp("(\\?|&)"+this.RND_PARAM_NAME+"=[0-9.]*","g"), "");
        var queryString = "", randomizedQueryString = "";
        if( xContains(url, "?") )
        {
            if(xContains(url, "&" + this.RND_PARAM_NAME))
            {
                queryString = url.split("&" + this.RND_PARAM_NAME).slice(1,-1).join("");
            }
            randomizedQueryString = queryString + "&" + this.RND_PARAM_NAME + "=" + Math.random() * 99999999;
        }
        else
        {
            if(xContains(url, "?" + this.RND_PARAM_NAME))
            {
                queryString = url.split("?" + this.RND_PARAM_NAME).slice(1,-1).join("");
            }
            randomizedQueryString = queryString + "?" + this.RND_PARAM_NAME + "=" + Math.random() * 99999999;
        }
        var foundAt = -1;
        if( !xIsEmpty( this._prefixCache ) )
        {
            for(var i=0; i<this._prefixCache.length; ++i)
            {
                if( !xIsEmpty(this._prefixCache[i]) && foundAt < 0 )
                {
                    for(var h=0; h<this._prefixCache[i].length; ++h)
                    {
                        if( this._prefixCache[i][h] == cleanUrl + queryString )
                        {
                            cleanUrl = this._prefixCache[i][0];
                            foundAt = i;
                            break;
                        }
                    }
                }
            }
        }

        var prefixHistory = [cleanUrl + queryString];
        var applyPrefix = true;
        if( prefixHistory[0].match( new RegExp('^[A-Za-z0-9-_]+://') ) )
        {
            applyPrefix = false;
        }
        var prefix = this._prefix;
        if( !xIsEmpty(this._prefix) && this._prefix )
        {
            prefixHistory.push( this._prefix + cleanUrl + queryString );
            if(foundAt >= 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<children.length; ++i)
                {
                    if( xGetAttribute(children[i], "class") == this.CSS_HTML_PREFIX+"-seconds" )
                    {
                        nSeconds = children[i].value;
                    }
                }

                self._guiRefreshSelected();
                self._guiRefreshStart( nSeconds );
            }
        });
        xAddEventListener( xGetElementById(this.CSS_HTML_PREFIX+"-submit-stop"), "click", function(evt)
        {
            self._guiRefreshStop();
        });
        xAddEventListener( xGetElementById(this.CSS_HTML_PREFIX+"-submit-refresh-list"), "click", xBind(self.guiRefreshFilesList,self) );
    }

    hotswap.prototype._guiCreateFilesList = function()
    {
        this._guiCache.files = [];
        this._guiCache.activeFiles = {
            "css" : [],
            "js" : [],
            "img" : []
        };

        var self = this;
        var createFilesList = function(list, files)
        {
            var i, j, r, clone, template, file, fileName, nodesToRemove = [];
            for(j=0; j<list.children.length; ++j)
            {
                if( xHasClass( list.children[j], "template" ) )
                {
                    template = list.children[j];
                }
                else
                {
                    if( !xHasClass( list.children[j], self.CSS_HTML_PREFIX + "-headline" ) )
                    {
                        nodesToRemove.push(list.children[j]);
                    }
                }
            }
            for(r=0; r<nodesToRemove.length; ++r)
            {
                xRemove( nodesToRemove[r] );
            }
            for(i=0; i<files.length; ++i)
            {
                file = files[i];
                clone = xCloneNode( template );
                xRemoveClass( clone, "template" );
                fileName = self._updatedUrl( xGetAttribute( file.node, file.srcAttributeName ), true );
                if( !xContains(self._guiCache.files,fileName) )
                {
                    self._guiCache.files.push(fileName);
                    clone.innerHTML = fileName;
                    xAppendChild( list, clone );
                    xAddEventListener( clone, "click", (function(type, fileName){
                        return function(evt){
                            xStopPropagation(evt);
                            xPreventDefault(evt);
                            self._guiClickedFile(evt.target, type, fileName);
                        };
                    })(file.type, fileName)
                    );
                }
            }
        }

        createFilesList( xGetElementById(this.CSS_HTML_PREFIX+"-css"), this._getFilesByType("css") );
        createFilesList( xGetElementById(this.CSS_HTML_PREFIX+"-js"), this._getFilesByType("js", ["hotswap.js"]) );
        createFilesList( xGetElementById(this.CSS_HTML_PREFIX+"-img"), this._getFilesByType("img") );
    }
    hotswap.prototype.deleteGui = function()
    {
        var gui = xGetElementById(this.CSS_HTML_PREFIX + "_wrapper");
        if( gui )
        {
            xRemove(xGetElementById(this.CSS_HTML_PREFIX + "_wrapper"));
        }
    }
    hotswap.prototype._guiPrefixChanged = function(ele)
    {
        if( ele )
        {
            this.setPrefix(ele.value);
        }
    },

    hotswap.prototype._guiClickedFile = function( ele, sType, sFileName )
    {
        var activeFiles = this._guiCache.activeFiles[sType];
        if( xContains( activeFiles, sFileName ) )
        {
            xRemoveClass(ele, "active");
            activeFiles.splice( activeFiles.indexOf(sFileName), 1 )
        }
        else
        {
            xAddClass(ele, "active");
            activeFiles.push( sFileName );
        }
    },

    hotswap.prototype._guiRefreshSelected = function()
    {
        var activeFiles = this._guiCache.activeFiles;
        if( activeFiles['css'].length > 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);

im sure not too many people here know what you mean by hot swap but as virtuosiMedia said mootools allows that, if you dont trust mootools that much then there's a same plugin for jquery and still if you dont want frameworks you could always add those scripts via dom

but i'm sure you are not allowed to alter the head level scripts you've already defined as any scripts dynamically added through DOM is only body level

I'm not familiar with HotSport, but if you're talking about dynamically loading JavaScript, yes, you can do that. MooTools allows you to do that, as do jQuery, Prototype, Dojo, and YUI, and I'm sure most of the other frameworks do as well. You can also do it with native JavaScript.

You can easily hot-reload JavaScript code with the RequireJS front-end module system, I wrote an article about it recently (September 2015)

https://medium.com/@the1mills/hot-reloading-with-react-requirejs-7b2aa6cb06e1

you basically delete the cache for an AMD module and RequireJS will go fetch the new one from the filesystem.

the trick is to use websockets (socket.io works fine) to tell the browser that the file has changed, and to delete the cache and re-require the file.

the rest of the info is in the article

If you want to do this with entire JavaScript files, see this question for something similar enough that you should be able to get the basics of the idea.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top