Team:TU Darmstadt/Template/Javascript

From 2014.igem.org

(Difference between revisions)
Line 364: Line 364:
 +
 +
<script>
 +
 +
//    postscribe.js 1.1.2
 +
//    (c) Copyright 2012 to the present, Krux
 +
//    postscribe is freely distributable under the MIT license.
 +
//    For all details and documentation:
 +
//    http://krux.github.com/postscribe
 +
 +
 +
(function() {
 +
 +
    var global = this;
 +
 +
    if(global.postscribe) {
 +
        return;
 +
    }
 +
 +
    // Debug write tasks.
 +
    var DEBUG = true;
 +
 +
    // Turn on to debug how each chunk affected the DOM.
 +
    var DEBUG_CHUNK = false;
 +
 +
    // # Helper Functions
 +
 +
    var slice = Array.prototype.slice;
 +
 +
    // A function that intentionally does nothing.
 +
    function doNothing() {}
 +
 +
 +
    // Is this a function?
 +
    function isFunction(x) {
 +
        return "function" === typeof x;
 +
    }
 +
 +
    // Loop over each item in an array-like value.
 +
    function each(arr, fn, _this) {
 +
        var i, len = (arr && arr.length) || 0;
 +
        for(i = 0; i < len; i++) {
 +
            fn.call(_this, arr[i], i);
 +
        }
 +
    }
 +
 +
    // Loop over each key/value pair in a hash.
 +
    function eachKey(obj, fn, _this) {
 +
        var key;
 +
        for(key in obj) {
 +
            if(obj.hasOwnProperty(key)) {
 +
                fn.call(_this, key, obj[key]);
 +
            }
 +
        }
 +
    }
 +
 +
    // Set properties on an object.
 +
    function set(obj, props) {
 +
        eachKey(props, function(key, value) {
 +
            obj[key] = value;
 +
        });
 +
        return obj;
 +
    }
 +
 +
    // Set default options where some option was not specified.
 +
    function defaults(options, _defaults) {
 +
        options = options || {};
 +
        eachKey(_defaults, function(key, val) {
 +
            if(options[key] == null) {
 +
                options[key] = val;
 +
            }
 +
        });
 +
        return options;
 +
    }
 +
 +
    // Convert value (e.g., a NodeList) to an array.
 +
    function toArray(obj) {
 +
        try {
 +
            return slice.call(obj);
 +
        } catch(e) {
 +
            var ret = [];
 +
            each(obj, function(val) {
 +
                ret.push(val);
 +
            });
 +
            return ret;
 +
        }
 +
    }
 +
 +
    // Test if token is a script tag.
 +
    function isScript(tok) {
 +
        return (/^script$/i).test(tok.tagName);
 +
    }
 +
 +
    // # Class WriteStream
 +
 +
    // Stream static html to an element, where "static html" denotes "html without scripts".
 +
 +
    // This class maintains a *history of writes devoid of any attributes* or "proxy history".
 +
    // Injecting the proxy history into a temporary div has no side-effects,
 +
    // other than to create proxy elements for previously written elements.
 +
 +
    // Given the `staticHtml` of a new write, a `tempDiv`'s innerHTML is set to `proxy_history + staticHtml`.
 +
    // The *structure* of `tempDiv`'s contents, (i.e., the placement of new nodes beside or inside of proxy elements),
 +
    // reflects the DOM structure that would have resulted if all writes had been squashed into a single write.
 +
 +
    // For each descendent `node` of `tempDiv` whose parentNode is a *proxy*, `node` is appended to the corresponding *real* element within the DOM.
 +
 +
    // Proxy elements are mapped to *actual* elements in the DOM by injecting a data-id attribute into each start tag in `staticHtml`.
 +
    var WriteStream = (function(){
 +
 +
        // Prefix for data attributes on DOM elements.
 +
        var BASEATTR = 'data-ps-';
 +
 +
        // get / set data attributes
 +
        function data(el, name, value) {
 +
            var attr = BASEATTR + name;
 +
 +
            if(arguments.length === 2) {
 +
                // Get
 +
                var val = el.getAttribute(attr);
 +
 +
                // IE 8 returns a number if it's a number
 +
                return val == null ? val : String(val);
 +
 +
            } else if( value != null && value !== '') {
 +
                // Set
 +
                el.setAttribute(attr, value);
 +
 +
            } else {
 +
                // Remove
 +
                el.removeAttribute(attr);
 +
            }
 +
        }
 +
 +
        function WriteStream(root, options) {
 +
            var doc = root.ownerDocument;
 +
 +
            set(this, {
 +
                root: root,
 +
 +
                options: options,
 +
 +
                win: doc.defaultView || doc.parentWindow,
 +
 +
                doc: doc,
 +
 +
                parser: global.htmlParser('', { autoFix: true }),
 +
 +
                // Actual elements by id.
 +
                actuals: [root],
 +
 +
                // Embodies the "structure" of what's been written so far, devoid of attributes.
 +
                proxyHistory: '',
 +
 +
                // Create a proxy of the root element.
 +
                proxyRoot: doc.createElement(root.nodeName),
 +
 +
                scriptStack: [],
 +
 +
                writeQueue: []
 +
            });
 +
 +
            data(this.proxyRoot, 'proxyof', 0);
 +
 +
        }
 +
 +
 +
        WriteStream.prototype.write = function() {
 +
            [].push.apply(this.writeQueue, arguments);
 +
            // Process writes
 +
            // When new script gets pushed or pending this will stop
 +
            // because new writeQueue gets pushed
 +
            var arg;
 +
            while(!this.deferredRemote &&
 +
                this.writeQueue.length) {
 +
                arg = this.writeQueue.shift();
 +
 +
                if(isFunction(arg)) {
 +
                    this.callFunction(arg);
 +
                } else {
 +
                    this.writeImpl(arg);
 +
                }
 +
            }
 +
        };
 +
 +
        WriteStream.prototype.callFunction = function(fn) {
 +
            var tok = { type: "function", value: fn.name || fn.toString() };
 +
            this.onScriptStart(tok);
 +
            fn.call(this.win, this.doc);
 +
            this.onScriptDone(tok);
 +
        };
 +
 +
        WriteStream.prototype.writeImpl = function(html) {
 +
            this.parser.append(html);
 +
 +
            var tok, tokens = [];
 +
 +
            // stop if we see a script token
 +
            while((tok = this.parser.readToken()) && !isScript(tok)) {
 +
                tokens.push(tok);
 +
            }
 +
 +
            this.writeStaticTokens(tokens);
 +
 +
            if(tok) {
 +
                this.handleScriptToken(tok);
 +
            }
 +
        };
 +
 +
 +
        // ## Contiguous non-script tokens (a chunk)
 +
        WriteStream.prototype.writeStaticTokens = function(tokens) {
 +
 +
            var chunk = this.buildChunk(tokens);
 +
 +
            if(!chunk.actual) {
 +
                // e.g., no tokens, or a noscript that got ignored
 +
                return;
 +
            }
 +
            chunk.html = this.proxyHistory + chunk.actual;
 +
            this.proxyHistory += chunk.proxy;
 +
 +
            this.proxyRoot.innerHTML = chunk.html;
 +
 +
            if(DEBUG_CHUNK) {
 +
                chunk.proxyInnerHTML = this.proxyRoot.innerHTML;
 +
            }
 +
 +
            this.walkChunk();
 +
 +
            if(DEBUG_CHUNK) {
 +
                chunk.actualInnerHTML = this.root.innerHTML; //root
 +
            }
 +
 +
            return chunk;
 +
        };
 +
 +
 +
        WriteStream.prototype.buildChunk = function (tokens) {
 +
            var nextId = this.actuals.length,
 +
 +
            // The raw html of this chunk.
 +
                raw = [],
 +
 +
            // The html to create the nodes in the tokens (with id's injected).
 +
                actual = [],
 +
 +
            // Html that can later be used to proxy the nodes in the tokens.
 +
                proxy = [];
 +
 +
            each(tokens, function(tok) {
 +
 +
                raw.push(tok.text);
 +
 +
                if(tok.attrs) { // tok.attrs <==> startTag or atomicTag or cursor
 +
                    // Ignore noscript tags. They are atomic, so we don't have to worry about children.
 +
                    if(!(/^noscript$/i).test(tok.tagName)) {
 +
                        var id = nextId++;
 +
 +
                        // Actual: inject id attribute: replace '>' at end of start tag with id attribute + '>'
 +
                        actual.push(
 +
                            tok.text.replace(/(\/?>)/, ' '+BASEATTR+'id='+id+' $1')
 +
                        );
 +
 +
                        // Don't proxy scripts: they have no bearing on DOM structure.
 +
                        if(tok.attrs.id !== "ps-script") {
 +
                            // Proxy: strip all attributes and inject proxyof attribute
 +
                            proxy.push(
 +
                                // ignore atomic tags (e.g., style): they have no "structural" effect
 +
                                tok.type === 'atomicTag' ? '' :
 +
                                    '<'+tok.tagName+' '+BASEATTR+'proxyof='+id+(tok.unary ? '/>' : '>')
 +
                            );
 +
                        }
 +
                    }
 +
 +
                } else {
 +
                    // Visit any other type of token
 +
                    // Actual: append.
 +
                    actual.push(tok.text);
 +
                    // Proxy: append endTags. Ignore everything else.
 +
                    proxy.push(tok.type === 'endTag' ? tok.text : '');
 +
                }
 +
            });
 +
 +
            return {
 +
                tokens: tokens,
 +
                raw: raw.join(''),
 +
                actual: actual.join(''),
 +
                proxy: proxy.join('')
 +
            };
 +
        };
 +
 +
        WriteStream.prototype.walkChunk = function() {
 +
            var node, stack = [this.proxyRoot];
 +
 +
            // use shift/unshift so that children are walked in document order
 +
 +
            while((node = stack.shift()) != null) {
 +
 +
                var isElement = node.nodeType === 1;
 +
                var isProxy = isElement && data(node, 'proxyof');
 +
 +
                // Ignore proxies
 +
                if(!isProxy) {
 +
 +
                    if(isElement) {
 +
                        // New actual element: register it and remove the the id attr.
 +
                        this.actuals[data(node, 'id')] = node;
 +
                        data(node, 'id', null);
 +
                    }
 +
 +
                    // Is node's parent a proxy?
 +
                    var parentIsProxyOf = node.parentNode && data(node.parentNode, 'proxyof');
 +
                    if(parentIsProxyOf) {
 +
                        // Move node under actual parent.
 +
                        this.actuals[parentIsProxyOf].appendChild(node);
 +
                    }
 +
                }
 +
                // prepend childNodes to stack
 +
                stack.unshift.apply(stack, toArray(node.childNodes));
 +
            }
 +
        };
 +
 +
        // ### Script tokens
 +
 +
        WriteStream.prototype.handleScriptToken = function(tok) {
 +
            var remainder = this.parser.clear();
 +
 +
            if(remainder) {
 +
                // Write remainder immediately behind this script.
 +
                this.writeQueue.unshift(remainder);
 +
            }
 +
 +
            tok.src = tok.attrs.src || tok.attrs.SRC;
 +
 +
            if(tok.src && this.scriptStack.length) {
 +
                // Defer this script until scriptStack is empty.
 +
                // Assumption 1: This script will not start executing until
 +
                // scriptStack is empty.
 +
                this.deferredRemote = tok;
 +
            } else {
 +
                this.onScriptStart(tok);
 +
            }
 +
 +
            // Put the script node in the DOM.
 +
            var _this = this;
 +
            this.writeScriptToken(tok, function() {
 +
                _this.onScriptDone(tok);
 +
            });
 +
 +
        };
 +
 +
        WriteStream.prototype.onScriptStart = function(tok) {
 +
            tok.outerWrites = this.writeQueue;
 +
            this.writeQueue = [];
 +
            this.scriptStack.unshift(tok);
 +
        };
 +
 +
        WriteStream.prototype.onScriptDone = function(tok) {
 +
            // Pop script and check nesting.
 +
            if(tok !== this.scriptStack[0]) {
 +
                this.options.error({ message: "Bad script nesting or script finished twice" });
 +
                return;
 +
            }
 +
            this.scriptStack.shift();
 +
 +
            // Append outer writes to queue and process them.
 +
            this.write.apply(this, tok.outerWrites);
 +
 +
            // Check for pending remote
 +
 +
            // Assumption 2: if remote_script1 writes remote_script2 then
 +
            // the we notice remote_script1 finishes before remote_script2 starts.
 +
            // I think this is equivalent to assumption 1
 +
            if(!this.scriptStack.length && this.deferredRemote) {
 +
                this.onScriptStart(this.deferredRemote);
 +
                this.deferredRemote = null;
 +
            }
 +
        };
 +
 +
        // Build a script and insert it into the DOM.
 +
        // Done is called once script has executed.
 +
        WriteStream.prototype.writeScriptToken = function(tok, done) {
 +
            var el = this.buildScript(tok);
 +
 +
            if(tok.src) {
 +
                // Fix for attribute "SRC" (capitalized). IE does not recognize it.
 +
                el.src = tok.src;
 +
                this.scriptLoadHandler(el, done);
 +
            }
 +
 +
            try {
 +
                this.insertScript(el);
 +
                if(!tok.src) {
 +
                    done();
 +
                }
 +
            } catch(e) {
 +
                this.options.error(e);
 +
                done();
 +
            }
 +
        };
 +
 +
        // Build a script element from an atomic script token.
 +
        WriteStream.prototype.buildScript = function(tok) {
 +
            var el = this.doc.createElement(tok.tagName);
 +
 +
            // Set attributes
 +
            eachKey(tok.attrs, function(name, value) {
 +
                el.setAttribute(name, value);
 +
            });
 +
 +
            // Set content
 +
            if(tok.content) {
 +
                el.text = tok.content;
 +
            }
 +
 +
            return el;
 +
        };
 +
 +
 +
        // Insert script into DOM where it would naturally be written.
 +
        WriteStream.prototype.insertScript = function(el) {
 +
            // Append a span to the stream. That span will act as a cursor
 +
            // (i.e. insertion point) for the script.
 +
            this.writeImpl('<span id="ps-script"/>');
 +
 +
            // Grab that span from the DOM.
 +
            var cursor = this.doc.getElementById("ps-script");
 +
 +
            // Replace cursor with script.
 +
            cursor.parentNode.replaceChild(el, cursor);
 +
        };
 +
 +
 +
        WriteStream.prototype.scriptLoadHandler = function(el, done) {
 +
            function cleanup() {
 +
                el = el.onload = el.onreadystatechange = el.onerror = null;
 +
                done();
 +
            }
 +
 +
            // Error handler
 +
            var error = this.options.error;
 +
 +
            // Set handlers
 +
            set(el, {
 +
                onload: function() { cleanup(); },
 +
 +
                onreadystatechange: function() {
 +
                    if(/^(loaded|complete)$/.test( el.readyState )) {
 +
                        cleanup();
 +
                    }
 +
                },
 +
 +
                onerror: function() {
 +
                    error({ message: 'remote script failed ' + el.src });
 +
                    cleanup();
 +
                }
 +
            });
 +
        };
 +
 +
        return WriteStream;
 +
 +
    }());
 +
 +
 +
 +
 +
 +
 +
    // Public-facing interface and queuing
 +
    var postscribe = (function() {
 +
        var nextId = 0;
 +
 +
        var queue = [];
 +
 +
        var active = null;
 +
 +
        function nextStream() {
 +
            var args = queue.shift();
 +
            if(args) {
 +
                args.stream = runStream.apply(null, args);
 +
            }
 +
        }
 +
 +
 +
        function runStream(el, html, options) {
 +
            active = new WriteStream(el, options);
 +
 +
            // Identify this stream.
 +
            active.id = nextId++;
 +
            active.name = options.name || active.id;
 +
            postscribe.streams[active.name] = active;
 +
 +
            // Override document.write.
 +
            var doc = el.ownerDocument;
 +
 +
            var stash = { write: doc.write, writeln: doc.writeln };
 +
 +
            function write(str) {
 +
                str = options.beforeWrite(str);
 +
                active.write(str);
 +
                options.afterWrite(str);
 +
            }
 +
 +
            set(doc, { write: write, writeln: function(str) { write(str + '\n'); } });
 +
 +
            // Override window.onerror
 +
            var oldOnError = active.win.onerror || doNothing;
 +
 +
            // This works together with the try/catch around WriteStream::insertScript
 +
            // In modern browsers, exceptions in tag scripts go directly to top level
 +
            active.win.onerror = function(msg, url, line) {
 +
                options.error({ msg: msg + ' - ' + url + ':' + line });
 +
                oldOnError.apply(active.win, arguments);
 +
            };
 +
 +
            // Write to the stream
 +
            active.write(html, function streamDone() {
 +
                // restore document.write
 +
                set(doc, stash);
 +
 +
                // restore window.onerror
 +
                active.win.onerror = oldOnError;
 +
 +
                options.done();
 +
                active = null;
 +
                nextStream();
 +
            });
 +
 +
            return active;
 +
        }
 +
 +
 +
        function postscribe(el, html, options) {
 +
            if(isFunction(options)) {
 +
                options = { done: options };
 +
            }
 +
            options = defaults(options, {
 +
                done: doNothing,
 +
                error: function(e) { throw e; },
 +
                beforeWrite: function(str) { return str; },
 +
                afterWrite: doNothing
 +
            });
 +
 +
            el =
 +
                // id selector
 +
                (/^#/).test(el) ? global.document.getElementById(el.substr(1)) :
 +
                    // jquery object. TODO: loop over all elements.
 +
                    el.jquery ? el[0] : el;
 +
 +
 +
            var args = [el, html, options];
 +
 +
            el.postscribe = {
 +
                cancel: function() {
 +
                    if(args.stream) {
 +
                        // TODO: implement this
 +
                        args.stream.abort();
 +
                    } else {
 +
                        args[1] = doNothing;
 +
                    }
 +
                }
 +
            };
 +
 +
            queue.push(args);
 +
            if(!active) {
 +
                nextStream();
 +
            }
 +
 +
            return el.postscribe;
 +
        }
 +
 +
        return set(postscribe, {
 +
            // Streams by name.
 +
            streams: {},
 +
            // Queue of streams.
 +
            queue: queue,
 +
            // Expose internal classes.
 +
            WriteStream: WriteStream
 +
        });
 +
 +
    }());
 +
 +
    // export postscribe
 +
    global.postscribe = postscribe;
 +
 +
}());
 +
 +
</script>
</html>
</html>

Revision as of 21:32, 14 October 2014