<commit_before>
var utils = require("./utils"), dom = require("./dom");

var TEXT_TYPE = -1;

var CLEAN = 1, DELETE = 2;

var rootComponent, domHooks = {};

var updateId = new Date().getTime();

function applyHook(hook, event, el, callback){
    "use strict";
    var hookName;
    if(hook && el.nodeType === 1 && (hookName = (hook = (utils.isString(hook) ? {name: hook} : hook)).name) in domHooks){
        var handler = domHooks[hookName];
        if(handler[event]){
            try{
                handler[event](hook, el, callback);
            }catch(e){
                callback();
            }
            return;
        }
    }
    callback();
}

function domNamespace(tag, parent) {
    "use strict";
    if (tag === 'svg') {
        return 'http://www.w3.org/2000/svg';
    }
    return parent ? parent.namespaceURI : null;
}

function domCreate(tagName, attrs, parent) {
    "use strict";
    var ns = domNamespace(tagName, parent), element;
    if(ns){
        element = document.createElementNS(ns, tagName);
    }else{
        element = document.createElement(tagName);
    }
    if(attrs){
        domAttributes(element, attrs);
    }
    return element;
}

function domStyle(el, style) {
    "use strict";
    var key, rules;
    if (typeof style === 'string') {
        el.style.cssText = style;
    } else {
        el.style.cssText = '';
        rules = el.style;
        for (key in style) {
            if (style.hasOwnProperty(key)) {
                rules[key] = style[key];
            }
        }
    }
}

function domDisplay(el, value){
    "use strict";
    var eventName = value ? "show" : "hide";
    applyHook(el.hook, eventName, el, function(){
        el.style.display = value ? "" : "none";
    });
}

function domAttributes(el, values) {
    "use strict";
    var key, value, type;
    var properties = {
        hook: 1,
        className: 1
    };
    for (key in values) {
        if (values.hasOwnProperty(key)) {
            value = values[key];
            if(key === 'classes'){
                el.className = dom.classes(value);
            }else if(key === 'value' && el.tagName === 'TEXTAREA'){
                el.value = value;
            }else if(key === "show"){
                domDisplay(el, value);
            }else if (value === null || value === undefined) {
                el.removeAttribute(key);
            } else {
                type = typeof value;
                if (key === "style") {
                    domStyle(el, value);
                } else if (key in properties || type === 'function' || type === 'object') {
                    el[key] = value;
                } else {
                    if(type === 'boolean'){
                        el[key] = value;
                    }
                    el.setAttribute(key, value);
                }
            }
        }
    }
}

function domAdopt(parent, el, before, replace){
    "use strict";
    if(typeof before==='number' && (before%1)===0){
        before = parent.childNodes[before];
    }
    if(before){
        if(replace){
            applyHook(parent.hook, "enter", el, function(){
                parent.replaceChild(el, before);
            });
        }else{
            applyHook(parent.hook, "enter", el, function(){
                parent.insertBefore(el, before);
            });
        }
    }else{
        applyHook(parent.hook, "enter", el, function(){
            parent.appendChild(el);
        });
    }
}

function domRemove(el){
    "use strict";
    var parent = el.parentNode;
    applyHook(parent.hook, "leave", el, function(){
        parent.removeChild(el);
    });
}

function domReorder(el, index){
    "use strict";
    var parent = el.parentNode;
    applyHook(parent.hook, "enter", el, function(){
        var before = parent.childNodes[index];
        if(before !== el){
            parent.insertBefore(el, before);
        }
    });
}

function DomNode(type, attrs, children, index){
    "use strict";
    this.type = type;
    this.attrs = attrs;
    this.children = children;
    this.el = null;
    this.index = arguments.length < 4 ? -1 : index;
    this.owner = null;
}

DomNode.prototype = {
    constructor: DomNode,
    create: function(stack, parent, owner){
        "use strict";
        var src = this.tenant, before, replace;

        if(src){
            before = src.el;
            replace = true;
        }

        var el, isText = this.type === TEXT_TYPE;

        if(isText){
            el = document.createTextNode(this.children);
        }else{
            el = domCreate(this.type, this.attrs, parent);

        }

        domAdopt(parent, el, before, replace);

        this.el = el;
        this.owner = owner;
        delete this.tenant;

        if(src && this.index !== src.index){
            this.reorder(src);
        }

        if(owner && owner.$tree === this){
            owner.el = this.el;
            owner.$tag.el = this.el;
        }

        owner.$updated = true;

        if(!isText){
            pushChildNodes(stack, this.el, this.owner, this.children, 'dst');
        }
    },
    replace: function (stack, src, owner) {
        "use strict";
        var el = src.component ? src.component.el : src.el, parent = el.parentNode;
        this.tenant = src;
        pushChildNodes(stack, parent, owner, [src], 'src', CLEAN);
        this.create(stack, parent, owner);
    },
    destroy: function(stack, nodelete) {
        "use strict";
        var isText = this.type === TEXT_TYPE, owner = this.owner, el = this.el;
        if(!isText){
            pushChildNodes(stack, this.el, this.owner, this.children, 'src', CLEAN);
        }
        if(!nodelete){
            domRemove(el);
        }
        this.owner.$updated = true;
        this.el = null;
        this.owner = null;
    },
    patch: function(stack, src){
        "use strict";
        var el = src.el, isText = this.type === TEXT_TYPE, owner = src.owner;

        if(isText){
            if(src.children !== this.children){
                el.nodeValue = this.children;
                owner.$updated = true;
            }
        }else{
            var diff = utils.diffAttr(src.attrs, this.attrs), changes, show;
            if(diff){
                changes = diff.changes;
                domAttributes(el, changes);
                owner.$updated = true;
            }
        }

        this.el = el;
        this.owner = owner;

        if(this.index !== src.index){
            this.reorder(src);
        }
        src.el = null;
        if(!isText){
            patchChildNodes(stack, this.el, src.owner, src.children, this.children);
        }
    },
    reorder: function(src){
        "use strict";
        var index=this.index;
        if(src.index < index){
            index++;
        }
        domReorder(this.el, index);
    }
};

function ComponentNode(type, attrs, children, index){
    "use strict";
    //component shall have 4 attributes: owner, children, tree, el
    this.type = type;
    this.attrs = attrs;
    this.children = [];
    this.owner = null;
    this.el = null;
    this.component = null;
    this.index = arguments.length < 4 ? -1 : index;
}

ComponentNode.prototype = {
    constructor: ComponentNode,
    own: function(child){
        "use strict";
        var comp = this.component, children = comp.$children = comp.$children || [];
        if(child.$owner){
            child.$owner.disown(child);
        }
        child.$owner = comp;
        children.push(child);
    },
    disown: function(component){
        "use strict";
        var i,
            children = this.component.$children,
            l = children.length;
        component.$owner = null;
        for(i=0; i<l; i++){
            if(children[i] === component){
                children.splice(i,1);
            }
        }
    },
    render: function(){
        "use strict";
        var tree = this.component.render();
        if(tree){
            tree.index = this.index;
            this.children = [tree];
        }else{
            this.children = [];
        }
        this.component.$tree = tree;
        return tree;
    },
    create: function(stack, parent, owner){
        "use strict";
        var component = this.component = new this.type(this.attrs);

        component.$tag = this;
        this.render();
        this.owner = owner;
        this.el = parent;

        owner.$tag.own(component);
        component.$lastUpdate = updateId;
        pushChildNodes(stack, parent, this.component, this.children, 'dst');
    },
    replace: function(stack, src, owner){
        "use strict";

        var  parent = src.el.parentNode, tree;

        pushChildNodes(stack, parent, owner, [src], 'src', CLEAN);

        this.create(stack, parent, owner);

        tree = this.children[0];

        tree.tenant = src;

    },
    destroy: function (stack, nodelete) {
        "use strict";
        var action = nodelete ? CLEAN : null;
        if(!this.owner.$tag){
            return;
        }
        pushChildNodes(stack, this.el, this.component, this.children, 'src', action);
        this.owner.$tag.disown(this.component);
        this.component.$tag = null;
        this.component = null;
        this.el = null;
        this.children = [];
        this.owner = null;
    },
    patch: function(stack, src){
        "use strict";
        this.component = src.component;
        this.component.$tag = this;
        this.el = src.el;
        this.children = src.children;
        this.owner = src.owner;
        this.component.set(this.attrs);
        src.component = null;
        src.owner = null;
    },
    update: function(){
        "use strict";
        var stack = [this.component], content, component, tree, i, l;
        while(stack.length){
            component = stack.pop();
            if(component.$lastUpdate !== updateId && component.hasChanged && component.hasChanged()){
                tree = component.$tree;
                content = component.$tag.render();
                patch(content, tree);
                if(component.$updated && component.onUpdate){
                    component.onUpdate();
                }
            }
            delete component.$updated;
            component.$dirty = false;
            stack.push.apply(stack, component.$children);
        }
    }
};

function patch(target, current){
    "use strict";

    var origin = {
        src: current,
        dst: target,
        owner: (current && current.owner) || rootComponent.component,
        parent:current ? current.el.parentNode : document.createDocumentFragment()
    }, stack = [origin], item, src, dst, parent, owner;

    var mounts = [], unmounts = [], updates = [], deletes = [], i, l, child;

    if(target === current){
        return origin.parent;
    }

    while (stack.length){
        item = stack.pop();
        src = item.src;
        dst = item.dst;
        parent = item.parent;
        owner = item.owner;
        if(!dst){
            if(src.component){
                deletes.push(src.component);
                if(src.component.onUnmount){
                    src.component.onUnmount();
                }
            }
            src.destroy(stack, item.action === CLEAN);
        }else if(!src){
            dst.create(stack, parent, owner);
            if(dst.component){
                mounts.push(dst.component);
            }
        }else if(src.type !== dst.type){
            dst.replace(stack, src, owner);
        }else{
            dst.patch(stack, src);
        }
    }

    for(i=mounts.length-1; i>=0; i--){
        child = mounts[i];
        if(child.onMount){
            child.onMount();
        }
    }

    for(i=deletes.length-1; i>=0; i--){
        child = deletes[i];
        if(child.onDelete){
            child.onDelete();
        }
    }

    return origin.parent;
}

function getChildNodesMap(src){
    "use strict";
    var i, l = src.length, result = {}, child, key;
    for(i=0; i<l; i++){
        child = src[i];
        if(child.hasOwnProperty("key")){
            result[child.key] = child;
        }
    }
    return result;
}

function pushChildNodes(stack, parentNode, owner, children, attr, action){
    "use strict";
    var i, l = children.length, entry;
    for(i=l-1; i>=0; i--){
        entry = {
            owner: owner,
            parent: parentNode,
            action: action
        };
        entry[attr] = children[i];
        stack.push(entry);
    }
}

function patchChildNodes(stack, parentNode, owner, src, dst){
    "use strict";
    var i, l, srcChild, dstChild, key, used = {}, childMap, entries = [];

    l = dst.length;
    for(i=0; i<l; i++){
        dstChild = dst[i];
        if(dstChild.hasOwnProperty("key")){
            if(!childMap){
                childMap = getChildNodesMap(src);
            }
            srcChild = childMap[dstChild.key];
        }else{
            srcChild = src[i];
        }
        if(srcChild){
            used[srcChild.index] = true;
        }
        entries.push({
            owner: owner,
            src: srcChild,
            dst: dstChild,
            parent: parentNode
        });
    }
    l = src.length;
    for(i=0; i<l; i++){
        srcChild = src[i];
        if(srcChild.index in used){
            continue;
        }
        entries.push({
            owner: owner,
            src: srcChild,
            dst: null,
            parent: parentNode
        });
    }
    entries.reverse();
    stack.push.apply(stack, entries);
}

rootComponent = new ComponentNode();
rootComponent.component = {$tag: rootComponent};

function update(){
    "use strict";
    updateId += 1;
    rootComponent.update();
}

function setHook(name, handler){
    "use strict";
    domHooks[name] = handler;
}

function getHook(name){
    "use strict";
    return domHooks[name];
}

function hook(name, handler){
    "use strict";
    if(arguments.length < 2){
        return getHook(name);
    }else{
        setHook(name, handler);
        return getHook(name);
    }
}

module.exports = {
    DomNode: DomNode,
    ComponentNode: ComponentNode,
    TEXT_TYPE: TEXT_TYPE,
    patch: patch,
    update: update,
    hook: hook,
}<commit_msg>changes to node deletion<commit_after>
var utils = require("./utils"), dom = require("./dom");

var TEXT_TYPE = -1;

var CLEAN = 1, DELETE = 2;

var rootComponent, domHooks = {};

var updateId = new Date().getTime();

function applyHook(hook, event, el, callback){
    "use strict";
    var hookName;
    if(hook && el.nodeType === 1 && (hookName = (hook = (utils.isString(hook) ? {name: hook} : hook)).name) in domHooks){
        var handler = domHooks[hookName];
        if(handler[event]){
            try{
                handler[event](hook, el, callback);
            }catch(e){
                callback();
            }
            return;
        }
    }
    callback();
}

function domNamespace(tag, parent) {
    "use strict";
    if (tag === 'svg') {
        return 'http://www.w3.org/2000/svg';
    }
    return parent ? parent.namespaceURI : null;
}

function domCreate(tagName, attrs, parent) {
    "use strict";
    var ns = domNamespace(tagName, parent), element;
    if(ns){
        element = document.createElementNS(ns, tagName);
    }else{
        element = document.createElement(tagName);
    }
    if(attrs){
        domAttributes(element, attrs);
    }
    return element;
}

function domStyle(el, style) {
    "use strict";
    var key, rules;
    if (typeof style === 'string') {
        el.style.cssText = style;
    } else {
        el.style.cssText = '';
        rules = el.style;
        for (key in style) {
            if (style.hasOwnProperty(key)) {
                rules[key] = style[key];
            }
        }
    }
}

function domDisplay(el, value){
    "use strict";
    var eventName = value ? "show" : "hide";
    applyHook(el.hook, eventName, el, function(){
        el.style.display = value ? "" : "none";
    });
}

function domAttributes(el, values) {
    "use strict";
    var key, value, type;
    var properties = {
        hook: 1,
        className: 1
    };
    for (key in values) {
        if (values.hasOwnProperty(key)) {
            value = values[key];
            if(key === 'classes'){
                el.className = dom.classes(value);
            }else if(key === 'value' && el.tagName === 'TEXTAREA'){
                el.value = value;
            }else if(key === "show"){
                domDisplay(el, value);
            }else if (value === null || value === undefined) {
                el.removeAttribute(key);
            } else {
                type = typeof value;
                if (key === "style") {
                    domStyle(el, value);
                } else if (key in properties || type === 'function' || type === 'object') {
                    el[key] = value;
                } else {
                    if(type === 'boolean'){
                        el[key] = value;
                    }
                    el.setAttribute(key, value);
                }
            }
        }
    }
}

function domAdopt(parent, el, before, replace){
    "use strict";
    if(typeof before==='number' && (before%1)===0){
        before = parent.childNodes[before];
    }
    if(before){
        if(replace){
            applyHook(parent.hook, "enter", el, function(){
                parent.replaceChild(el, before);
            });
        }else{
            applyHook(parent.hook, "enter", el, function(){
                parent.insertBefore(el, before);
            });
        }
    }else{
        applyHook(parent.hook, "enter", el, function(){
            parent.appendChild(el);
        });
    }
}

function domRemove(el){
    "use strict";
    var parent = el.parentNode;
    applyHook(parent.hook, "leave", el, function(){
        parent.removeChild(el);
    });
}

function domReorder(el, index){
    "use strict";
    var parent = el.parentNode;
    applyHook(parent.hook, "enter", el, function(){
        var before = parent.childNodes[index];
        if(before !== el){
            parent.insertBefore(el, before);
        }
    });
}

function DomNode(type, attrs, children, index){
    "use strict";
    this.type = type;
    this.attrs = attrs;
    this.children = children;
    this.el = null;
    this.index = arguments.length < 4 ? -1 : index;
    this.owner = null;
}

DomNode.prototype = {
    constructor: DomNode,
    create: function(stack, parent, owner){
        "use strict";
        var src = this.tenant, before, replace;

        if(src){
            before = src.el;
            replace = true;
        }

        var el, isText = this.type === TEXT_TYPE;

        if(isText){
            el = document.createTextNode(this.children);
        }else{
            el = domCreate(this.type, this.attrs, parent);

        }

        domAdopt(parent, el, before, replace);

        this.el = el;
        this.owner = owner;
        delete this.tenant;

        if(src && this.index !== src.index){
            this.reorder(src);
        }

        if(owner && owner.$tree === this){
            owner.$tag.setEl(this);
            //owner.el = this.el;
            //owner.$tag.el = this.el;
        }

        owner.$updated = true;

        if(!isText){
            pushChildNodes(stack, this.el, this.owner, this.children, 'dst');
        }
    },
    replace: function (stack, src, owner) {
        "use strict";
        var el = src.component ? src.component.el : src.el, parent = el.parentNode;
        this.tenant = src;
        pushChildNodes(stack, parent, owner, [src], 'src', CLEAN);
        this.create(stack, parent, owner);
    },
    destroy: function(stack, nodelete) {
        "use strict";
        var isText = this.type === TEXT_TYPE, owner = this.owner, el = this.el;
        if(!isText){
            pushChildNodes(stack, this.el, this.owner, this.children, 'src', CLEAN);
        }
        if(!nodelete && el.parentNode){
            domRemove(el);
        }
        this.owner.$updated = true;
        this.el = null;
        this.owner = null;
    },
    patch: function(stack, src){
        "use strict";
        var el = src.el, isText = this.type === TEXT_TYPE, owner = src.owner;

        if(isText){
            if(src.children !== this.children){
                el.nodeValue = this.children;
                owner.$updated = true;
            }
        }else{
            var diff = utils.diffAttr(src.attrs, this.attrs), changes, show;
            if(diff){
                changes = diff.changes;
                domAttributes(el, changes);
                owner.$updated = true;
            }
        }

        this.el = el;
        this.owner = owner;

        if(this.index !== src.index){
            this.reorder(src);
        }
        src.el = null;
        if(!isText){
            patchChildNodes(stack, this.el, src.owner, src.children, this.children);
        }
    },
    reorder: function(src){
        "use strict";
        var index=this.index;
        if(src.index < index){
            index++;
        }
        domReorder(this.el, index);
    }
};

function ComponentNode(type, attrs, children, index){
    "use strict";
    //component shall have 4 attributes: owner, children, tree, el
    this.type = type;
    this.attrs = attrs;
    this.children = [];
    this.owner = null;
    this.el = null;
    this.component = null;
    this.index = arguments.length < 4 ? -1 : index;
}

ComponentNode.prototype = {
    constructor: ComponentNode,
    own: function(child){
        "use strict";
        var comp = this.component, children = comp.$children = comp.$children || [];
        if(child.$owner){
            child.$owner.disown(child);
        }
        child.$owner = comp;
        children.push(child);
    },
    disown: function(component){
        "use strict";
        var i,
            children = this.component.$children,
            l = children.length;
        component.$owner = null;
        for(i=0; i<l; i++){
            if(children[i] === component){
                children.splice(i,1);
            }
        }
    },
    render: function(){
        "use strict";
        var tree = this.component.render();
        if(tree){
            tree.index = this.index;
            this.children = [tree];
        }else{
            this.children = [];
        }
        this.component.$tree = tree;
        return tree;
    },
    create: function(stack, parent, owner){
        "use strict";
        var component = this.component = new this.type(this.attrs);

        component.$tag = this;
        this.render();
        this.owner = owner;
        this.el = null;

        owner.$tag.own(component);
        component.$lastUpdate = updateId;
        pushChildNodes(stack, parent, this.component, this.children, 'dst');
    },
    replace: function(stack, src, owner){
        "use strict";

        var  parent = src.el.parentNode, tree;

        pushChildNodes(stack, parent, owner, [src], 'src');

        this.create(stack, parent, owner);

        tree = this.children[0];

        tree.tenant = src;

    },
    destroy: function (stack, nodelete) {
        "use strict";
        var action = nodelete ? CLEAN : null;
        if(!this.owner.$tag){
            //throw new Error("**********");
            return;
        }
        pushChildNodes(stack, this.el, this.component, this.children, 'src', action);
        this.owner.$tag.disown(this.component);
        this.component.$tag = null;
        this.component = null;
        this.el = null;
        this.children = [];
        this.owner = null;
    },
    patch: function(stack, src){
        "use strict";
        this.component = src.component;
        this.component.$tag = this;
        this.el = src.el;
        this.children = src.children;
        this.owner = src.owner;
        this.component.set(this.attrs);
        src.component = null;
        src.owner = null;
    },
    update: function(){
        "use strict";
        var stack = [this.component], content, component, tree, i, l;
        while(stack.length){
            component = stack.pop();
            if(component.$lastUpdate !== updateId && component.hasChanged && component.hasChanged()){
                tree = component.$tree;
                content = component.$tag.render();
                patch(content, tree);
                if(component.$updated && component.onUpdate){
                    component.onUpdate();
                }
            }
            delete component.$updated;
            component.$dirty = false;
            stack.push.apply(stack, component.$children);
        }
    },
    setEl: function(node){
        "use strict";
        var component = this.component;
        while (component && component.$tree === node){
                component.el = node.el;
                component.$tag.el = node.el;
                node = component.$tag;
                component = component.$owner;
        }
        return this.component.el;
    }
};

function patch(target, current){
    "use strict";

    var origin = {
        src: current,
        dst: target,
        owner: (current && current.owner) || rootComponent.component,
        parent:current ? current.el.parentNode : document.createDocumentFragment()
    }, stack = [origin], item, src, dst, parent, owner;

    var mounts = [], unmounts = [], updates = [], deletes = [], i, l, child;

    if(target === current){
        return origin.parent;
    }

    while (stack.length){
        item = stack.pop();
        src = item.src;
        dst = item.dst;
        parent = item.parent;
        owner = item.owner;
        if(!dst){
            if(src.component){
                deletes.push(src.component);
                if(src.component.onUnmount){
                    src.component.onUnmount();
                }
            }
            src.destroy(stack, item.action === CLEAN);
        }else if(!src){
            dst.create(stack, parent, owner);
            if(dst.component){
                mounts.push(dst.component);
            }
        }else if(src.type !== dst.type){
            dst.replace(stack, src, owner);
        }else{
            dst.patch(stack, src);
        }
    }

    for(i=mounts.length-1; i>=0; i--){
        child = mounts[i];
        if(child.onMount){
            child.onMount();
        }
    }

    for(i=deletes.length-1; i>=0; i--){
        child = deletes[i];
        if(child.onDelete){
            child.onDelete();
        }
    }

    return origin.parent;
}

function getChildNodesMap(src){
    "use strict";
    var i, l = src.length, result = {}, child, key;
    for(i=0; i<l; i++){
        child = src[i];
        if(child.hasOwnProperty("key")){
            result[child.key] = child;
        }
    }
    return result;
}

function pushChildNodes(stack, parentNode, owner, children, attr, action){
    "use strict";
    var i, l = children.length, entry;
    for(i=l-1; i>=0; i--){
        entry = {
            owner: owner,
            parent: parentNode,
            action: action
        };
        entry[attr] = children[i];
        stack.push(entry);
    }
}

function patchChildNodes(stack, parentNode, owner, src, dst){
    "use strict";
    var i, l, srcChild, dstChild, key, used = {}, childMap, entries = [];

    l = dst.length;
    for(i=0; i<l; i++){
        dstChild = dst[i];
        if(dstChild.hasOwnProperty("key")){
            if(!childMap){
                childMap = getChildNodesMap(src);
            }
            srcChild = childMap[dstChild.key];
        }else{
            srcChild = src[i];
        }
        if(srcChild){
            used[srcChild.index] = true;
        }
        entries.push({
            owner: owner,
            src: srcChild,
            dst: dstChild,
            parent: parentNode
        });
    }
    l = src.length;
    for(i=0; i<l; i++){
        srcChild = src[i];
        if(srcChild.index in used){
            continue;
        }
        entries.push({
            owner: owner,
            src: srcChild,
            dst: null,
            parent: parentNode
        });
    }
    entries.reverse();
    stack.push.apply(stack, entries);
}

rootComponent = new ComponentNode();
rootComponent.component = {$tag: rootComponent};

function update(){
    "use strict";
    updateId += 1;
    rootComponent.update();
}

function setHook(name, handler){
    "use strict";
    domHooks[name] = handler;
}

function getHook(name){
    "use strict";
    return domHooks[name];
}

function hook(name, handler){
    "use strict";
    if(arguments.length < 2){
        return getHook(name);
    }else{
        setHook(name, handler);
        return getHook(name);
    }
}

module.exports = {
    DomNode: DomNode,
    ComponentNode: ComponentNode,
    TEXT_TYPE: TEXT_TYPE,
    patch: patch,
    update: update,
    hook: hook,
}<|endoftext|>


<commit_before>// Load modules

var Lab = require('lab');
var Fs = require('fs');
var Request = require('request');
var Zlib = require('zlib');
var Hapi = require('../..');

// Declare internals

var internals = {};

// Test shortcuts

var expect = Lab.expect;
var before = Lab.before;
var after = Lab.after;
var describe = Lab.experiment;
var it = Lab.test;

describe('Proxy', function () {

    var server = null;

    before(function (done) {

        // Define backend handlers

        var mapUriWithError = function (request, callback) {

            return callback(new Error('myerror'));
        };

        var profile = function () {

            if (this.state.test) {
                return this.reply('error');
            }

            var profile = {
                'id': 'fa0dbda9b1b',
                'name': 'John Doe'
            };

            this.reply(profile).state('test', '123');
        };

        var activeItem = function () {

            this.reply({
                'id': '55cf687663',
                'name': 'Active Item'
            });
        };

        var item = function () {

            this.reply({
                'id': '55cf687663',
                'name': 'Item'
            }).created('http://example.com');
        };

        var echoPostBody = function () {

            this.reply(this.payload);
        };

        var unauthorized = function () {

            this.reply(Hapi.error.unauthorized('Not authorized'));
        };

        var postResponseWithError = function (request) {

            request.reply(Hapi.error.forbidden('Forbidden'));
        };

        var streamHandler = function () {

            this.reply('success');
        };

        var forward = function () {

            expect(this.raw.req.headers['x-forwarded-for']).to.contain('xforwardfor');
            expect(this.raw.req.headers['x-forwarded-port']).to.contain('9000');
            expect(this.raw.req.headers['x-forwarded-proto']).to.contain('xforwardproto');
            this.reply('Success');
        };

        var headers = function () {

            this.reply({ status: 'success' })
                      .header('Custom1', 'custom header value 1')
                      .header('X-Custom2', 'custom header value 2')
                      .header('access-control-allow-headers', 'Invalid, List, Of, Values');
        };

        var gzipHandler = function () {

            this.reply('123456789012345678901234567890123456789012345678901234567890');
        };

        var gzipStreamHandler = function () {

            this.reply(new Hapi.response.File(__dirname + '/../../package.json'));
        };

        var upstream = new Hapi.Server(0);
        upstream.route([
            { method: 'GET', path: '/profile', handler: profile },
            { method: 'GET', path: '/item', handler: activeItem },
            { method: 'GET', path: '/proxyerror', handler: activeItem },
            { method: 'POST', path: '/item', handler: item },
            { method: 'GET', path: '/unauthorized', handler: unauthorized },
            { method: 'POST', path: '/file', handler: streamHandler, config: { payload: 'stream' } },
            { method: 'POST', path: '/echo', handler: echoPostBody },
            { method: 'GET', path: '/headers', handler: headers },
            { method: 'GET', path: '/noHeaders', handler: headers },
            { method: 'GET', path: '/forward', handler: forward },
            { method: 'GET', path: '/gzip', handler: gzipHandler },
            { method: 'GET', path: '/gzipstream', handler: gzipStreamHandler }
        ]);

        var mapUri = function (request, callback) {

            return callback(null, upstream.info.uri + request.path + (request.url.search || ''));
        };

        upstream.start(function () {

            var backendPort = upstream.info.port;
            var routeCache = { expiresIn: 500, mode: 'server+client' };

            server = new Hapi.Server(0, { cors: true });
            server.route([
                { method: 'GET', path: '/profile', handler: { proxy: { host: 'localhost', port: backendPort, xforward: true, passThrough: true } } },
                { method: 'GET', path: '/item', handler: { proxy: { host: 'localhost', port: backendPort } }, config: { cache: routeCache } },
                { method: 'GET', path: '/unauthorized', handler: { proxy: { host: 'localhost', port: backendPort } }, config: { cache: routeCache } },
                { method: 'POST', path: '/item', handler: { proxy: { host: 'localhost', port: backendPort } } },
                { method: 'POST', path: '/notfound', handler: { proxy: { host: 'localhost', port: backendPort } } },
                { method: 'GET', path: '/proxyerror', handler: { proxy: { host: 'localhost', port: backendPort } }, config: { cache: routeCache } },
                { method: 'GET', path: '/postResponseError', handler: { proxy: { host: 'localhost', port: backendPort, postResponse: postResponseWithError } }, config: { cache: routeCache } },
                { method: 'GET', path: '/errorResponse', handler: { proxy: { host: 'localhost', port: backendPort } }, config: { cache: routeCache } },
                { method: 'POST', path: '/echo', handler: { proxy: { mapUri: mapUri } } },
                { method: 'POST', path: '/file', handler: { proxy: { host: 'localhost', port: backendPort } }, config: { payload: 'stream' } },
                { method: 'GET', path: '/maperror', handler: { proxy: { mapUri: mapUriWithError } } },
                { method: 'GET', path: '/forward', handler: { proxy: { host: 'localhost', port: backendPort, xforward: true, passThrough: true } } },
                { method: 'GET', path: '/headers', handler: { proxy: { host: 'localhost', port: backendPort, passThrough: true } } },
                { method: 'GET', path: '/noHeaders', handler: { proxy: { host: 'localhost', port: backendPort } } },
                { method: 'GET', path: '/gzip', handler: { proxy: { host: 'localhost', port: backendPort, passThrough: true } } },
                { method: 'GET', path: '/gzipstream', handler: { proxy: { host: 'localhost', port: backendPort, passThrough: true } } }
            ]);

            server.state('auto', { autoValue: 'xyz' });
            server.start(function () {

                done();
            });
        });
    });

    function makeRequest(options, callback) {

        options = options || {};
        options.path = options.path || '/';
        options.method = options.method || 'get';

        var req = {
            method: options.method,
            url: server.info.uri + options.path,
            form: options.form,
            headers: options.headers,
            jar: false
        };

        Request(req, function (err, res) {

            callback(res);
        });
    }

    it('forwards on the response when making a GET request', function (done) {

        makeRequest({ path: '/profile' }, function (rawRes) {

            expect(rawRes.statusCode).to.equal(200);
            expect(rawRes.body).to.contain('John Doe');
            expect(rawRes.headers['set-cookie']).to.deep.equal(['test=123','auto=xyz']);

            makeRequest({ path: '/profile' }, function (rawRes) {

                expect(rawRes.statusCode).to.equal(200);
                expect(rawRes.body).to.contain('John Doe');
                done();
            });
        });
    });

    it('forwards on x-forward headers', function (done) {

        makeRequest({ path: '/forward', headers: { 'x-forwarded-for': 'xforwardfor', 'x-forwarded-port': '9000', 'x-forwarded-proto': 'xforwardproto' } }, function (rawRes) {

            expect(rawRes.statusCode).to.equal(200);
            expect(rawRes.body).to.equal('Success');
            done();
        });
    });

    it('forwards upstream headers', function (done) {

        server.inject({ url: '/headers', method: 'GET' }, function (res) {

            expect(res.statusCode).to.equal(200);
            expect(res.result).to.equal('{\"status\":\"success\"}');
            expect(res.headers.custom1).to.equal('custom header value 1');
            expect(res.headers['x-custom2']).to.equal('custom header value 2');
            expect(res.headers['access-control-allow-headers']).to.equal('Authorization, Content-Type, If-None-Match');
            expect(res.headers['access-control-expose-headers']).to.equal('WWW-Authenticate, Server-Authorization');
            done();
        });
    });

    it('forwards gzipped content', function (done) {

        Zlib.gzip(new Buffer('123456789012345678901234567890123456789012345678901234567890'), function (err, zipped) {

            makeRequest({ path: '/gzip', method: 'GET', headers: { 'accept-encoding': 'gzip' } }, function (res) {

                expect(res.statusCode).to.equal(200);
                expect(res.body).to.equal(zipped.toString());
                done();
            });
        });
    });

    it('forwards gzipped stream', function (done) {

        makeRequest({ path: '/gzipstream', method: 'GET', headers: { 'accept-encoding': 'gzip' } }, function (res) {

            expect(res.statusCode).to.equal(200);

            Fs.readFile(__dirname + '/../../package.json', function (err, file) {

                Zlib.gzip(file, function (err, zipped) {

                    expect(zipped.toString()).to.equal(res.body);
                    done();
                });
            });
        });
    });

    it('does not forward upstream headers without passThrough', function (done) {

        makeRequest({ path: '/noHeaders' }, function (rawRes) {

            expect(rawRes.statusCode).to.equal(200);
            expect(rawRes.body).to.equal('{\"status\":\"success\"}');
            expect(rawRes.headers.custom1).to.not.exist;
            expect(rawRes.headers['x-custom2']).to.not.exist;
            done();
        });
    });

    it('forwards on the response when making a GET request to a route that also accepts a POST', function (done) {

        makeRequest({ path: '/item' }, function (rawRes) {

            expect(rawRes.statusCode).to.equal(200);
            expect(rawRes.body).to.contain('Active Item');
            done();
        });
    });

    it('forwards on the status code when making a POST request', function (done) {

        makeRequest({ path: '/item', method: 'post' }, function (rawRes) {

            expect(rawRes.statusCode).to.equal(201);
            expect(rawRes.body).to.contain('Item');
            done();
        });
    });

    it('sends the correct status code with a request is unauthorized', function (done) {

        makeRequest({ path: '/unauthorized', method: 'get' }, function (rawRes) {

            expect(rawRes.statusCode).to.equal(401);
            done();
        });
    });

    it('sends a 404 status code with a proxied route doesn\'t exist', function (done) {

        makeRequest({ path: '/notfound', method: 'get' }, function (rawRes) {

            expect(rawRes.statusCode).to.equal(404);
            done();
        });
    });

    it('forwards on the status code when a custom postResponse returns an error', function (done) {

        makeRequest({ path: '/postResponseError', method: 'get' }, function (rawRes) {

            expect(rawRes.statusCode).to.equal(403);
            done();
        });
    });

    it('forwards the error message with a custom postResponse and a route error', function (done) {

        makeRequest({ path: '/postResponseNotFound', method: 'get' }, function (rawRes) {

            expect(rawRes.body).to.contain('error');
            done();
        });
    });

    it('handles an error from request safely', function (done) {

        var requestStub = function (options, callback) {

            callback(new Error());
        };

        var route = server._router.route({ method: 'get', path: '/proxyerror', info: {}, raw: { req: { headers: {} } } });
        route.proxy.httpClient = requestStub;

        makeRequest({ path: '/proxyerror', method: 'get' }, function (rawRes) {

            expect(rawRes.statusCode).to.equal(500);
            done();
        });
    });

    it('forwards on a POST body', function (done) {

        makeRequest({ path: '/echo', method: 'post', form: { echo: true } }, function (rawRes) {

            expect(rawRes.statusCode).to.equal(200);
            expect(rawRes.body).to.contain('echo');
            done();
        });
    });

    it('replies with an error when it occurs in mapUri', function (done) {

        makeRequest({ path: '/maperror', method: 'get' }, function (rawRes) {

            expect(rawRes.body).to.contain('myerror');
            done();
        });
    });

    it('works with a stream when the proxy response is streamed', function (done) {

        Fs.createReadStream(__dirname + '/proxy.js').pipe(Request.post(server.info.uri + '/file', function (err, rawRes, body) {

            expect(rawRes.statusCode).to.equal(200);
            done();
        }));
    });

    it('can add a proxy route with a http protocol set', function (done) {

        server.route({ method: 'GET', path: '/httpport', handler: { proxy: { host: 'localhost', protocol: 'http' } } });
        done();
    });

    it('can add a proxy route with a https protocol set', function (done) {

        server.route({ method: 'GET', path: '/httpsport', handler: { proxy: { host: 'localhost', protocol: 'https' } } });
        done();
    });
});<commit_msg>Add broken test<commit_after>// Load modules

var Lab = require('lab');
var Fs = require('fs');
var Request = require('request');
var Zlib = require('zlib');
var Hapi = require('../..');

// Declare internals

var internals = {};

// Test shortcuts

var expect = Lab.expect;
var before = Lab.before;
var after = Lab.after;
var describe = Lab.experiment;
var it = Lab.test;

describe('Proxy', function () {

    var server = null;

    before(function (done) {

        // Define backend handlers

        var mapUriWithError = function (request, callback) {

            return callback(new Error('myerror'));
        };

        var profile = function () {

            if (this.state.test) {
                return this.reply('error');
            }

            var profile = {
                'id': 'fa0dbda9b1b',
                'name': 'John Doe'
            };

            this.reply(profile).state('test', '123');
        };

        var activeItem = function () {

            this.reply({
                'id': '55cf687663',
                'name': 'Active Item'
            });
        };

        var item = function () {

            this.reply({
                'id': '55cf687663',
                'name': 'Item'
            }).created('http://example.com');
        };

        var echoPostBody = function () {

            this.reply(this.payload);
        };

        var unauthorized = function () {

            this.reply(Hapi.error.unauthorized('Not authorized'));
        };

        var postResponseWithError = function (request) {

            request.reply(Hapi.error.forbidden('Forbidden'));
        };

        var streamHandler = function () {

            this.reply('success');
        };

        var forward = function () {

            expect(this.raw.req.headers['x-forwarded-for']).to.contain('xforwardfor');
            expect(this.raw.req.headers['x-forwarded-port']).to.contain('9000');
            expect(this.raw.req.headers['x-forwarded-proto']).to.contain('xforwardproto');
            this.reply('Success');
        };

        var headers = function () {

            this.reply({ status: 'success' })
                      .header('Custom1', 'custom header value 1')
                      .header('X-Custom2', 'custom header value 2')
                      .header('access-control-allow-headers', 'Invalid, List, Of, Values');
        };

        var gzipHandler = function () {

            this.reply('123456789012345678901234567890123456789012345678901234567890');
        };

        var gzipStreamHandler = function () {

            this.reply(new Hapi.response.File(__dirname + '/../../package.json'));
        };

        var upstream = new Hapi.Server(0);
        upstream.route([
            { method: 'GET', path: '/profile', handler: profile },
            { method: 'GET', path: '/item', handler: activeItem },
            { method: 'GET', path: '/proxyerror', handler: activeItem },
            { method: 'POST', path: '/item', handler: item },
            { method: 'GET', path: '/unauthorized', handler: unauthorized },
            { method: 'POST', path: '/file', handler: streamHandler, config: { payload: 'stream' } },
            { method: 'POST', path: '/echo', handler: echoPostBody },
            { method: 'GET', path: '/headers', handler: headers },
            { method: 'GET', path: '/noHeaders', handler: headers },
            { method: 'GET', path: '/forward', handler: forward },
            { method: 'GET', path: '/gzip', handler: gzipHandler },
            { method: 'GET', path: '/gzipstream', handler: gzipStreamHandler }
        ]);

        var mapUri = function (request, callback) {

            return callback(null, upstream.info.uri + request.path + (request.url.search || ''));
        };

        upstream.start(function () {

            var backendPort = upstream.info.port;
            var routeCache = { expiresIn: 500, mode: 'server+client' };

            server = new Hapi.Server(0, { cors: true });
            server.route([
                { method: 'GET', path: '/profile', handler: { proxy: { host: 'localhost', port: backendPort, xforward: true, passThrough: true } } },
                { method: 'GET', path: '/item', handler: { proxy: { host: 'localhost', port: backendPort } }, config: { cache: routeCache } },
                { method: 'GET', path: '/unauthorized', handler: { proxy: { host: 'localhost', port: backendPort } }, config: { cache: routeCache } },
                { method: 'POST', path: '/item', handler: { proxy: { host: 'localhost', port: backendPort } } },
                { method: 'POST', path: '/notfound', handler: { proxy: { host: 'localhost', port: backendPort } } },
                { method: 'GET', path: '/proxyerror', handler: { proxy: { host: 'localhost', port: backendPort } }, config: { cache: routeCache } },
                { method: 'GET', path: '/postResponseError', handler: { proxy: { host: 'localhost', port: backendPort, postResponse: postResponseWithError } }, config: { cache: routeCache } },
                { method: 'GET', path: '/errorResponse', handler: { proxy: { host: 'localhost', port: backendPort } }, config: { cache: routeCache } },
                { method: 'POST', path: '/echo', handler: { proxy: { mapUri: mapUri } } },
                { method: 'POST', path: '/file', handler: { proxy: { host: 'localhost', port: backendPort } }, config: { payload: 'stream' } },
                { method: 'GET', path: '/maperror', handler: { proxy: { mapUri: mapUriWithError } } },
                { method: 'GET', path: '/forward', handler: { proxy: { host: 'localhost', port: backendPort, xforward: true, passThrough: true } } },
                { method: 'GET', path: '/headers', handler: { proxy: { host: 'localhost', port: backendPort, passThrough: true } } },
                { method: 'GET', path: '/noHeaders', handler: { proxy: { host: 'localhost', port: backendPort } } },
                { method: 'GET', path: '/gzip', handler: { proxy: { host: 'localhost', port: backendPort, passThrough: true } } },
                { method: 'GET', path: '/gzipstream', handler: { proxy: { host: 'localhost', port: backendPort, passThrough: true } } },
                { method: 'GET', path: '/google', handler: { proxy: { mapUri: function (request, callback) { callback(null, 'http://google.com'); } } } }
        ]);

            server.state('auto', { autoValue: 'xyz' });
            server.start(function () {

                done();
            });
        });
    });

    function makeRequest(options, callback) {

        options = options || {};
        options.path = options.path || '/';
        options.method = options.method || 'get';

        var req = {
            method: options.method,
            url: server.info.uri + options.path,
            form: options.form,
            headers: options.headers,
            jar: false
        };

        Request(req, function (err, res) {

            callback(res);
        });
    }

    it('forwards on the response when making a GET request', function (done) {

        makeRequest({ path: '/profile' }, function (rawRes) {

            expect(rawRes.statusCode).to.equal(200);
            expect(rawRes.body).to.contain('John Doe');
            expect(rawRes.headers['set-cookie']).to.deep.equal(['test=123','auto=xyz']);

            makeRequest({ path: '/profile' }, function (rawRes) {

                expect(rawRes.statusCode).to.equal(200);
                expect(rawRes.body).to.contain('John Doe');
                done();
            });
        });
    });

    it('forwards on x-forward headers', function (done) {

        makeRequest({ path: '/forward', headers: { 'x-forwarded-for': 'xforwardfor', 'x-forwarded-port': '9000', 'x-forwarded-proto': 'xforwardproto' } }, function (rawRes) {

            expect(rawRes.statusCode).to.equal(200);
            expect(rawRes.body).to.equal('Success');
            done();
        });
    });

    it('forwards upstream headers', function (done) {

        server.inject({ url: '/headers', method: 'GET' }, function (res) {

            expect(res.statusCode).to.equal(200);
            expect(res.result).to.equal('{\"status\":\"success\"}');
            expect(res.headers.custom1).to.equal('custom header value 1');
            expect(res.headers['x-custom2']).to.equal('custom header value 2');
            expect(res.headers['access-control-allow-headers']).to.equal('Authorization, Content-Type, If-None-Match');
            expect(res.headers['access-control-expose-headers']).to.equal('WWW-Authenticate, Server-Authorization');
            done();
        });
    });

    it('forwards gzipped content', function (done) {

        Zlib.gzip(new Buffer('123456789012345678901234567890123456789012345678901234567890'), function (err, zipped) {

            makeRequest({ path: '/gzip', method: 'GET', headers: { 'accept-encoding': 'gzip' } }, function (res) {

                expect(res.statusCode).to.equal(200);
                expect(res.body).to.equal(zipped.toString());
                done();
            });
        });
    });

    it('forwards gzipped stream', function (done) {

        makeRequest({ path: '/gzipstream', method: 'GET', headers: { 'accept-encoding': 'gzip' } }, function (res) {

            expect(res.statusCode).to.equal(200);

            Fs.readFile(__dirname + '/../../package.json', function (err, file) {

                Zlib.gzip(file, function (err, zipped) {

                    expect(zipped.toString()).to.equal(res.body);
                    done();
                });
            });
        });
    });

    it('does not forward upstream headers without passThrough', function (done) {

        makeRequest({ path: '/noHeaders' }, function (rawRes) {

            expect(rawRes.statusCode).to.equal(200);
            expect(rawRes.body).to.equal('{\"status\":\"success\"}');
            expect(rawRes.headers.custom1).to.not.exist;
            expect(rawRes.headers['x-custom2']).to.not.exist;
            done();
        });
    });

    it('forwards on the response when making a GET request to a route that also accepts a POST', function (done) {

        makeRequest({ path: '/item' }, function (rawRes) {

            expect(rawRes.statusCode).to.equal(200);
            expect(rawRes.body).to.contain('Active Item');
            done();
        });
    });

    it('forwards on the status code when making a POST request', function (done) {

        makeRequest({ path: '/item', method: 'post' }, function (rawRes) {

            expect(rawRes.statusCode).to.equal(201);
            expect(rawRes.body).to.contain('Item');
            done();
        });
    });

    it('sends the correct status code with a request is unauthorized', function (done) {

        makeRequest({ path: '/unauthorized', method: 'get' }, function (rawRes) {

            expect(rawRes.statusCode).to.equal(401);
            done();
        });
    });

    it('sends a 404 status code with a proxied route doesn\'t exist', function (done) {

        makeRequest({ path: '/notfound', method: 'get' }, function (rawRes) {

            expect(rawRes.statusCode).to.equal(404);
            done();
        });
    });

    it('forwards on the status code when a custom postResponse returns an error', function (done) {

        makeRequest({ path: '/postResponseError', method: 'get' }, function (rawRes) {

            expect(rawRes.statusCode).to.equal(403);
            done();
        });
    });

    it('forwards the error message with a custom postResponse and a route error', function (done) {

        makeRequest({ path: '/postResponseNotFound', method: 'get' }, function (rawRes) {

            expect(rawRes.body).to.contain('error');
            done();
        });
    });

    it('handles an error from request safely', function (done) {

        var requestStub = function (options, callback) {

            callback(new Error());
        };

        var route = server._router.route({ method: 'get', path: '/proxyerror', info: {}, raw: { req: { headers: {} } } });
        route.proxy.httpClient = requestStub;

        makeRequest({ path: '/proxyerror', method: 'get' }, function (rawRes) {

            expect(rawRes.statusCode).to.equal(500);
            done();
        });
    });

    it('forwards on a POST body', function (done) {

        makeRequest({ path: '/echo', method: 'post', form: { echo: true } }, function (rawRes) {

            expect(rawRes.statusCode).to.equal(200);
            expect(rawRes.body).to.contain('echo');
            done();
        });
    });

    it('replies with an error when it occurs in mapUri', function (done) {

        makeRequest({ path: '/maperror', method: 'get' }, function (rawRes) {

            expect(rawRes.body).to.contain('myerror');
            done();
        });
    });

    it('works with a stream when the proxy response is streamed', function (done) {

        Fs.createReadStream(__dirname + '/proxy.js').pipe(Request.post(server.info.uri + '/file', function (err, rawRes, body) {

            expect(rawRes.statusCode).to.equal(200);
            done();
        }));
    });

    it('can add a proxy route with a http protocol set', function (done) {

        server.route({ method: 'GET', path: '/httpport', handler: { proxy: { host: 'localhost', protocol: 'http' } } });
        done();
    });

    it('can add a proxy route with a https protocol set', function (done) {

        server.route({ method: 'GET', path: '/httpsport', handler: { proxy: { host: 'localhost', protocol: 'https' } } });
        done();
    });

    it('proxies to a remote site', function (done) {

        makeRequest({ path: '/google' }, function (rawRes) {

            expect(rawRes.statusCode).to.equal(200);
            done();
        });
    });
});<|endoftext|>


<commit_before>'use strict';
/**
 * Copyright (c) 2017, Yaacov Zamir <kobi.zamir@gmail.com>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF  THIS SOFTWARE.
 */
 var util = require('util');
 var events = require('events');
 var EventEmitter = events.EventEmitter || events;
 var net = require('net');

 var HOST = '127.0.0.1';
 var MODBUS_PORT = 502;

/* Add bit operation functions to Buffer
 */
require('../utils/buffer_bit')();
var crc16 = require('./../utils/crc16');

/**
 * Parse a modbusRTU buffer and return an answer buffer
 */
function parseModbusBuffer(buf, vector) {
    var ansBuf = null;
    var unitNumber = buf[0];
    var functionCode = buf[1];
    var crc = buf[buf.length - 2] + buf[buf.length - 1] * 0x100;

    // if crc is bad, ignore message
    if (crc != crc16(buf.slice(0, -2))) {
        return;
    }

    // function code 1 and 2
    if (functionCode == 1 || functionCode == 2) {
        var address = buf.readUInt16BE(2);
        var length = buf.readUInt16BE(4);

        // if length is bad, ignore message
        if (buf.length != 8) {
            return;
        }

        // build answer
        ansBuf = new Buffer(3 + parseInt((length - 1) / 8 + 1) + 2);
        ansBuf.writeUInt8(parseInt((length - 1) / 8 + 1), 2);

        // read coils
        if (vector.getCoil) {
            for (var i = 0; i < length; i++) {
              ansBuf.writeBit(vector.getCoil(address + i), i, 3);
            }
        }
    }

    // function code 3
    if (functionCode == 3) {
        var address = buf.readUInt16BE(2);
        var length = buf.readUInt16BE(4);

        // if length is bad, ignore message
        if (buf.length != 8) {
            return;
        }

        // build answer
        ansBuf = new Buffer(3 + length * 2 + 2);
        ansBuf.writeUInt8(length * 2, 2);

        // read registers
        if (vector.getHoldingRegister) {
          for (var i = 0; i < length; i++) {
              ansBuf.writeUInt16BE(vector.getHoldingRegister(address + i), 3 + i * 2);
          }
        }
    }

    // function code 4
    if (functionCode == 4) {
        var address = buf.readUInt16BE(2);
        var length = buf.readUInt16BE(4);

        // if length is bad, ignore message
        if (buf.length != 8) {
            return;
        }

        // build answer
        ansBuf = new Buffer(3 + length * 2 + 2);
        ansBuf.writeUInt8(length * 2, 2);

        // read registers
        if (vector.getInputRegister) {
          for (var i = 0; i < length; i++) {
              ansBuf.writeUInt16BE(vector.getInputRegister(address + i), 3 + i * 2);
          }
        }
    }

    // function code 5
    if (functionCode == 5) {
        var address = buf.readUInt16BE(2);
        var state = buf.readUInt16BE(4);

        // if length is bad, ignore message
        if (buf.length != 8) {
            return;
        }

        // build answer
        ansBuf = new Buffer(8);
        ansBuf.writeUInt16BE(address, 2);
        ansBuf.writeUInt16BE(state, 4);

        // write coil
        if (vector.setCoil) {
            vector.setCoil(address, state === 0xff00);
        }
    }

    // function code 6
    if (functionCode == 6) {
        var address = buf.readUInt16BE(2);
        var value = buf.readUInt16BE(4);
        // if length is bad, ignore message
        if (buf.length != (6 + 2)) {
            return;
        }

        // build answer
        ansBuf = new Buffer(8);
        ansBuf.writeUInt16BE(address, 2);
        ansBuf.writeUInt16BE(value, 4);

        if (vector.setRegister) vector.setRegister(address, value);
    }

    // function code 15
    if (functionCode == 15) {
        var address = buf.readUInt16BE(2);
        var length = buf.readUInt16BE(4);

        // if length is bad, ignore message
        if (buf.length != 7 + Math.ceil(length / 8) + 2) {
            return;
        }

        // build answer
        ansBuf = new Buffer(8);
        ansBuf.writeUInt16BE(address, 2);
        ansBuf.writeUInt16BE(length, 4);

        // write coils
        if (vector.setCoil) {
            for (var i = 0; i < length; i++) {
                var state = buf.readBit(i, 7);
                vector.setCoil(address + i, state !== 0);
            }
        }
    }

    // function code 16
    if (functionCode == 16) {
        var address = buf.readUInt16BE(2);
        var length = buf.readUInt16BE(4);

        // if length is bad, ignore message
        if (buf.length != (7 + length * 2 + 2)) {
            return;
        }

        // build answer
        ansBuf = new Buffer(8);
        ansBuf.writeUInt16BE(address, 2);
        ansBuf.writeUInt16BE(length, 4);

        // write registers
        if (vector.setRegister) {
            for (var i = 0; i < length; i++) {
                var value = buf.readUInt16BE(7 + i * 2);
                vector.setRegister(address + i, value);
            }
        }
    }

    // add unit-id, function code and crc
    if (ansBuf) {
        // add unit number and function code
        ansBuf.writeUInt8(unitNumber, 0);
        ansBuf.writeUInt8(functionCode, 1);

        // add crc
        crc = crc16(ansBuf.slice(0, -2));
        ansBuf.writeUInt16LE(crc, ansBuf.length - 2);
    }

    return ansBuf;
}

/**
 * Class making ModbusTCP server
 *
 * @param {object} options the server options
 */
var ServerTCP = function (vector, options) {
    var modbus = this;
    modbus._unitID = options.unitID || 1;
    modbus.debug = options.debug || false;

    // create a tcp server
    modbus._server = net.createServer();
    modbus._server.listen(options.port || MODBUS_PORT, options.host || HOST);

    modbus._server.on('connection', function(sock) {
        // emit debug data
        if (modbus.debug) modbus.emit('debug', {action: 'connected', data: null});

        sock.on('data', function (data) {
            // remove mbap and add crc16
            var buf = new Buffer(data.length - 6 + 2);
            data.copy(buf, 0, 6);
            var crc = crc16(buf.slice(0, -2));
            buf.writeUInt16LE(crc, buf.length - 2);

            // emit debug data
            if (modbus.debug) modbus.emit('debug', {action: 'recive', data: buf});

            // if length is too short, ignore message
            if (buf.length < 8) {
                return;
            }

            // parse the modbusRTU buffer
            var ansBuf = parseModbusBuffer(buf, vector);

            // send data back
            if (ansBuf) {
                // get transaction id
                var transactionsId = data.readUInt16BE(0);

                // remove crc and add mbap
                var outTcp = new Buffer(ansBuf.length + 6 - 2);
                outTcp.writeUInt16BE(transactionsId, 0);
                outTcp.writeUInt16BE(0, 2);
                outTcp.writeUInt16BE(ansBuf.length - 2, 4);
                ansBuf.copy(outTcp, 6);

                // emit debug data
                if (modbus.debug) modbus.emit('debug', {action: 'send', data: ansBuf});

                // write to port
                sock.write(outTcp);
            }
        });
    });
    EventEmitter.call(this);
};
util.inherits(ServerTCP, EventEmitter);

module.exports = ServerTCP;
<commit_msg>allow default options<commit_after>'use strict';
/**
 * Copyright (c) 2017, Yaacov Zamir <kobi.zamir@gmail.com>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF  THIS SOFTWARE.
 */
 var util = require('util');
 var events = require('events');
 var EventEmitter = events.EventEmitter || events;
 var net = require('net');

 var HOST = '127.0.0.1';
 var MODBUS_PORT = 502;

/* Add bit operation functions to Buffer
 */
require('../utils/buffer_bit')();
var crc16 = require('./../utils/crc16');

/**
 * Parse a modbusRTU buffer and return an answer buffer
 */
function parseModbusBuffer(buf, vector) {
    var ansBuf = null;
    var unitNumber = buf[0];
    var functionCode = buf[1];
    var crc = buf[buf.length - 2] + buf[buf.length - 1] * 0x100;

    // if crc is bad, ignore message
    if (crc != crc16(buf.slice(0, -2))) {
        return;
    }

    // function code 1 and 2
    if (functionCode == 1 || functionCode == 2) {
        var address = buf.readUInt16BE(2);
        var length = buf.readUInt16BE(4);

        // if length is bad, ignore message
        if (buf.length != 8) {
            return;
        }

        // build answer
        ansBuf = new Buffer(3 + parseInt((length - 1) / 8 + 1) + 2);
        ansBuf.writeUInt8(parseInt((length - 1) / 8 + 1), 2);

        // read coils
        if (vector.getCoil) {
            for (var i = 0; i < length; i++) {
              ansBuf.writeBit(vector.getCoil(address + i), i, 3);
            }
        }
    }

    // function code 3
    if (functionCode == 3) {
        var address = buf.readUInt16BE(2);
        var length = buf.readUInt16BE(4);

        // if length is bad, ignore message
        if (buf.length != 8) {
            return;
        }

        // build answer
        ansBuf = new Buffer(3 + length * 2 + 2);
        ansBuf.writeUInt8(length * 2, 2);

        // read registers
        if (vector.getHoldingRegister) {
          for (var i = 0; i < length; i++) {
              ansBuf.writeUInt16BE(vector.getHoldingRegister(address + i), 3 + i * 2);
          }
        }
    }

    // function code 4
    if (functionCode == 4) {
        var address = buf.readUInt16BE(2);
        var length = buf.readUInt16BE(4);

        // if length is bad, ignore message
        if (buf.length != 8) {
            return;
        }

        // build answer
        ansBuf = new Buffer(3 + length * 2 + 2);
        ansBuf.writeUInt8(length * 2, 2);

        // read registers
        if (vector.getInputRegister) {
          for (var i = 0; i < length; i++) {
              ansBuf.writeUInt16BE(vector.getInputRegister(address + i), 3 + i * 2);
          }
        }
    }

    // function code 5
    if (functionCode == 5) {
        var address = buf.readUInt16BE(2);
        var state = buf.readUInt16BE(4);

        // if length is bad, ignore message
        if (buf.length != 8) {
            return;
        }

        // build answer
        ansBuf = new Buffer(8);
        ansBuf.writeUInt16BE(address, 2);
        ansBuf.writeUInt16BE(state, 4);

        // write coil
        if (vector.setCoil) {
            vector.setCoil(address, state === 0xff00);
        }
    }

    // function code 6
    if (functionCode == 6) {
        var address = buf.readUInt16BE(2);
        var value = buf.readUInt16BE(4);
        // if length is bad, ignore message
        if (buf.length != (6 + 2)) {
            return;
        }

        // build answer
        ansBuf = new Buffer(8);
        ansBuf.writeUInt16BE(address, 2);
        ansBuf.writeUInt16BE(value, 4);

        if (vector.setRegister) vector.setRegister(address, value);
    }

    // function code 15
    if (functionCode == 15) {
        var address = buf.readUInt16BE(2);
        var length = buf.readUInt16BE(4);

        // if length is bad, ignore message
        if (buf.length != 7 + Math.ceil(length / 8) + 2) {
            return;
        }

        // build answer
        ansBuf = new Buffer(8);
        ansBuf.writeUInt16BE(address, 2);
        ansBuf.writeUInt16BE(length, 4);

        // write coils
        if (vector.setCoil) {
            for (var i = 0; i < length; i++) {
                var state = buf.readBit(i, 7);
                vector.setCoil(address + i, state !== 0);
            }
        }
    }

    // function code 16
    if (functionCode == 16) {
        var address = buf.readUInt16BE(2);
        var length = buf.readUInt16BE(4);

        // if length is bad, ignore message
        if (buf.length != (7 + length * 2 + 2)) {
            return;
        }

        // build answer
        ansBuf = new Buffer(8);
        ansBuf.writeUInt16BE(address, 2);
        ansBuf.writeUInt16BE(length, 4);

        // write registers
        if (vector.setRegister) {
            for (var i = 0; i < length; i++) {
                var value = buf.readUInt16BE(7 + i * 2);
                vector.setRegister(address + i, value);
            }
        }
    }

    // add unit-id, function code and crc
    if (ansBuf) {
        // add unit number and function code
        ansBuf.writeUInt8(unitNumber, 0);
        ansBuf.writeUInt8(functionCode, 1);

        // add crc
        crc = crc16(ansBuf.slice(0, -2));
        ansBuf.writeUInt16LE(crc, ansBuf.length - 2);
    }

    return ansBuf;
}

/**
 * Class making ModbusTCP server
 *
 * @param {object} options the server options
 */
var ServerTCP = function (vector, options) {
    var modbus = this;
    options = options || {};
    modbus._unitID = options.unitID || 1;
    modbus.debug = options.debug || false;

    // create a tcp server
    modbus._server = net.createServer();
    modbus._server.listen(options.port || MODBUS_PORT, options.host || HOST);

    modbus._server.on('connection', function(sock) {
        // emit debug data
        if (modbus.debug) modbus.emit('debug', {action: 'connected', data: null});

        sock.on('data', function (data) {
            // remove mbap and add crc16
            var buf = new Buffer(data.length - 6 + 2);
            data.copy(buf, 0, 6);
            var crc = crc16(buf.slice(0, -2));
            buf.writeUInt16LE(crc, buf.length - 2);

            // emit debug data
            if (modbus.debug) modbus.emit('debug', {action: 'recive', data: buf});

            // if length is too short, ignore message
            if (buf.length < 8) {
                return;
            }

            // parse the modbusRTU buffer
            var ansBuf = parseModbusBuffer(buf, vector);

            // send data back
            if (ansBuf) {
                // get transaction id
                var transactionsId = data.readUInt16BE(0);

                // remove crc and add mbap
                var outTcp = new Buffer(ansBuf.length + 6 - 2);
                outTcp.writeUInt16BE(transactionsId, 0);
                outTcp.writeUInt16BE(0, 2);
                outTcp.writeUInt16BE(ansBuf.length - 2, 4);
                ansBuf.copy(outTcp, 6);

                // emit debug data
                if (modbus.debug) modbus.emit('debug', {action: 'send', data: ansBuf});

                // write to port
                sock.write(outTcp);
            }
        });
    });
    EventEmitter.call(this);
};
util.inherits(ServerTCP, EventEmitter);

module.exports = ServerTCP;
<|endoftext|>


<commit_before>/*
 * brozzler/behaviors.d/facebook.js - facebook behavior, scrolls to the bottom
 * of the page, clicks to expand images, a few other things
 *
 * Copyright (C) 2014-2016 Internet Archive
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

var umbraAboveBelowOrOnScreen = function(e) {
        var eTop = e.getBoundingClientRect().top;
        if (eTop < window.scrollY) {
                return -1; // above
        } else if (eTop > window.scrollY + window.innerHeight) {
                // if (e.clientWidth != 0) {
                //         console.warn("e.clientWidth=" + e.clientWidth + " though it appears to be below the screen? e.getBoundingClientRect().top=" + eTop + " window.scrollY=" + window.scrollY + " window.innerHeight=" + window.innerHeight + " e=" + e);
                // }
                return 1;  // below
        } else {
                // if (e.clientWidth != 0) {
                //         console.warn("e.clientWidth=" + e.clientWidth + " though it appears to be on screen? e.getBoundingClientRect().top=" + eTop + " window.scrollY=" + window.scrollY + " window.innerHeight=" + window.innerHeight + " e=" + e);
                // }
                return 0;  // on screen
        }
}

// comments - 'a.UFIPagerLink > span, a.UFIPagerLink, span.UFIReplySocialSentenceLinkText'
var UMBRA_THINGS_TO_CLICK_SELECTOR = 'a[href^="/browse/likes"], *[rel="theater"]';
//div[class="phm pluginLikeboxStream"] = facebook widget embedded in 3rd party pages
var UMBRA_THINGS_TO_SCROLL_SELECTOR = 'div[class="phm pluginLikeboxStream"]';
var NUMBER_FAILED_SCROLL_ATTEMPTS_ON_THING_TO_SCROLL_BEFORE_STOP_SCROLLING = 5;
var umbraAlreadyClicked = {};
var umbraAlreadyScrolledThing = {};
var umbraScrolledThingFailedScrollAttempts = {};
var umbraState = {'idleSince':null,'expectingSomething':null,'bottomReachedScrollY':0};

var umbraIntervalFunc = function() {

		var thingsToScroll = document.querySelectorAll(UMBRA_THINGS_TO_SCROLL_SELECTOR);
		var everythingScrolled = true;

		for (var i = 0; i < thingsToScroll.length; i++) {
			var target = thingsToScroll[i];

			if (!(target in umbraAlreadyScrolledThing)) {

				everythingScrolled = false;

				console.log("scrolling to " + target.scrollHeight + " on element with nodeName " + target.nodeName + " with id of " + target.id);
				var lastScrollTop = target.scrollTop;
				target.scrollTop = target.scrollHeight;

				umbraState.idleSince = null;

				if (target.scrollTop >= target.scrollHeight) {
					umbraAlreadyScrolledThing[target] = true;
				}
				else if (target.scrollTop == lastScrollTop) {
					if (umbraScrolledThingFailedScrollAttempts[target]) {
						umbraScrolledThingFailedScrollAttempts[target]++;
					}
					else {
						umbraScrolledThingFailedScrollAttempts[target] = 1;
					}

					if (umbraScrolledThingFailedScrollAttempts[target] >= NUMBER_FAILED_SCROLL_ATTEMPTS_ON_THING_TO_SCROLL_BEFORE_STOP_SCROLLING) {
						umbraAlreadyScrolledThing[target] = true;
					}
				}
				else {
					//reset failed count on a successful scroll
					umbraScrolledThingFailedScrollAttempts[target] = 0;
				}
			}
			else {
				console.log("done scrolling for element with nodeName " + target.nodeName + " with id of " + target.id)
			}

			umbraState.expectingSomething = null;
		}

		if (thingsToScroll && thingsToScroll.length > 0 && everythingScrolled) {
			if (umbraState.idleSince == null) {
				umbraState.idleSince = Date.now();
			}

			return;
		}

	var closeButtons = document.querySelectorAll('a[title="Close"], a.closeTheater, a[aria-label="Press Esc to close"]');
        for (var i = 0; i < closeButtons.length; i++) {
                // XXX closeTheater buttons stick around in the dom after closing, clientWidth>0 is one way to check if they're visible
                if (closeButtons[i].clientWidth > 0) {
                        if (umbraState.expectingSomething == 'closeButton') {
                                console.log("found expected close button, clicking on it " + closeButtons[i].outerHTML);
                                umbraState.expectingSomething = null;
                        } else {
                                console.warn("found UNexpected close button, umbraState.expectingSomething=" + umbraState.expectingSomething + " ... clicking on it " + closeButtons[i].outerHTML);
                        }
                        closeButtons[i].click();
                        return;
                }
        }
        if (umbraState.expectingSomething == 'closeButton') {
                console.log("waiting for close button, haven't seen it yet");
                return;
        }

        var thingsToClick = document.querySelectorAll(UMBRA_THINGS_TO_CLICK_SELECTOR);
        var clickedSomething = false;
        var somethingLeftBelow = false;
        var somethingLeftAbove = false;
        var missedAbove = 0;

        for (var i = 0; i < thingsToClick.length; i++) {
                var target = thingsToClick[i];
                if (!(target in umbraAlreadyClicked)) {
                        var where = umbraAboveBelowOrOnScreen(target);
                        if (where == 0) { // on screen
                                // var pos = target.getBoundingClientRect().top;
                                // window.scrollTo(0, target.getBoundingClientRect().top - 100);
                                console.log("clicking at " + target.getBoundingClientRect().top + " on " + target.outerHTML);
                                if (target.click != undefined) {
                                        umbraState.expectingSomething = 'closeButton';
                                        target.click();
                                }
                                target.style.border = '1px solid #0a0';
                                umbraAlreadyClicked[target] = true;
                                clickedSomething = true;
                                umbraState.idleSince = null;
                                break;
                        } else if (where > 0) {
                                somethingLeftBelow = true;
                        } else if (where < 0) {
                                somethingLeftAbove = true;
                        }
                }
        }

        if (window.scrollY > umbraState.bottomReachedScrollY) {
                umbraState.bottomReachedScrollY = window.scrollY;
        }

        if (!clickedSomething) {
                if (somethingLeftBelow) {
                        // console.log("scrolling down because everything on this screen has been clicked but there's more below document.body.clientHeight=" + document.body.clientHeight);
                        window.scrollBy(0, 300);
                        umbraState.idleSince = null;
                } else if (umbraState.bottomReachedScrollY + window.innerHeight < document.documentElement.scrollHeight) {
                        // console.log("scrolling down because we haven't reached the bottom yet document.body.clientHeight=" + document.body.clientHeight);
                        window.scrollBy(0, 300);
                        umbraState.idleSince = null;
                } else if (somethingLeftAbove) {
                        // console.log("scrolling UP because we've already been to the bottom, everything on or below this screen has been clicked, but we missed something above");
                        window.scrollBy(0, -600);
                        umbraState.idleSince = null;
                } else if (umbraState.idleSince == null) {
                        umbraState.idleSince = Date.now();
                }
        }
}

// If we haven't had anything to do (scrolled, clicked, etc) in this amount of
// time, then we consider ourselves finished with the page.
var UMBRA_USER_ACTION_IDLE_TIMEOUT_SEC = 10;

// Called from outside of this script.
var umbraBehaviorFinished = function() {

        if (umbraState.idleSince != null) {
                var idleTimeMs = Date.now() - umbraState.idleSince;
                if (idleTimeMs / 1000 > UMBRA_USER_ACTION_IDLE_TIMEOUT_SEC) {
                        return true;
                }
        }
        return false;
}

var umbraIntervalId = setInterval(umbraIntervalFunc, 200);
<commit_msg>remove old facebook behavior, replaced by facebook.js.template (missed this on commit cea192b)<commit_after><|endoftext|>


<commit_before>// list of questions. Each list in this list is a set of questions for a cohort.
var giantjson = [
	[
		{
			"url" : "http://kbcc.cuny.libguides.com/prototype1",
			"message" : "Find the library hours for today."
		},
		{
			"url" : "http://kbcc.cuny.libguides.com/prototype2",
			"message" : "Look up a magazine article about Angela Merkel."
		},
		{
			"url" : "http://kbcc.cuny.libguides.com/prototype3",
			"message" : "Find a newspaper article about Syrian refugees."
		},
		{
			"url" : "http://kbcc.cuny.libguides.com/prototype4",
			"message" : "Look up a book called Animal Farm."
		}
	],
	[
		{
			"url" : "http://kbcc.cuny.libguides.com/prototype2",
			"message" : "Find the library hours for today."
		},
		{
			"url" : "http://kbcc.cuny.libguides.com/prototype3",
			"message" : "Look up a magazine article about Angela Merkel."
		},
		{
			"url" : "http://kbcc.cuny.libguides.com/prototype4",
			"message" : "Find a newspaper article about Syrian refugees."
		},
		{
			"url" : "http://kbcc.cuny.libguides.com/prototype1",
			"message" : "Look up a book called Animal Farm."
		}
	],
	[
		{
			"url" : "http://kbcc.cuny.libguides.com/prototype3",
			"message" : "Find the library hours for today."
		},
		{
			"url" : "http://kbcc.cuny.libguides.com/prototype4",
			"message" : "Look up a magazine article about Angela Merkel."
		},
		{
			"url" : "http://kbcc.cuny.libguides.com/prototype1",
			"message" : "Find a newspaper article about Syrian refugees."
		},
		{
			"url" : "http://kbcc.cuny.libguides.com/prototype2",
			"message" : "Look up a book called Animal Farm."
		}
	],
	[
		{
			"url" : "http://kbcc.cuny.libguides.com/prototype4",
			"message" : "Find the librarys hours for today."
		},
		{
			"url" : "http://kbcc.cuny.libguides.com/prototype1",
			"message" : "Look up a magazine article about Angela Merkel."
		},
		{
			"url" : "http://kbcc.cuny.libguides.com/prototype2",
			"message" : "Find a newspaper article about Syrian refugees."
		},
		{
			"url" : "http://kbcc.cuny.libguides.com/prototype3",
			"message" : "Look up a book called Animal Farm."
		}
	]
];

// select a cohort to get a unique combination of questions and interfaces
cohort = 0;
var activities = giantjson[cohort];
counter = 0;

// make the table using js
var tableHTML1 = '

Activity #';
var tableHTML2 = ' Try it now!

"';

$(document).ready(function() {
	for (i = 0; i < activities.length; i++) {
		$("tbody").append(tableHTML1 + parseInt(i + 1) + tableHTML2 + i + tableHTML3 + i + tableHTML4);
	};
});

// set some variables in preparation for gotoactivity()
var startHTML = '<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
Activity #'
var midHTML = '

';
var endHTML = '
';
var timer = undefined;

// this handles responses to the "Try it now!" button. Changes button text; opens the activity in a new window
function gotoactivity(id) {
	counter++;
	displayid = parseInt(id) + 1;
	$('#' + id).text('completed!').removeClass('active').addClass('disabled');
	var instructions = window.open('', 'instructions', 'left=20,top=20,width=160,height=300,menubar=no,titlebar=no');
	var workspace = window.open(activities[id]['url'], 'workspace', 'left=200,top=20,width=1195,height=750,menubar=no,titlebar=no,scrollbars=yes');
	instructions.document.body.innerHTML = startHTML + displayid + midHTML + activities[id]['message'] + endHTML;

	// closes one window when the other is closed
	timer = setInterval(function() {
		$(this).focus(function() {
			workspace.close();
			instructions.close();
			completeactivities();
		});
		if (instructions.closed) {
			workspace.close();
		    clearInterval(timer);
		    completeactivities();
		}
		else if (workspace.closed) {
			instructions.close();
			clearInterval(timer);
			completeactivities();
		}
	}, 50);
};

// check that all activities are completed; if they are, display a congratulations screen
function completeactivities() {
	if (counter >= giantjson[cohort].length) {
		var completeHTML = '
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous" style="text-align:center">Congratulations!
You\'ve completed our usability test!
'
		var complete = window.open('', 'complete', 'left=500,top=200,menubar=no,titlebar=no');
		complete.document.body.innerHTML = completeHTML;
		counter = 0;
	}
	else {}
}<commit_msg>completion message formatted nicely<commit_after>// list of questions. Each list in this list is a set of questions for a cohort.
var giantjson = [
	[
		{
			"url" : "http://kbcc.cuny.libguides.com/prototype1",
			"message" : "Find the library hours for today."
		},
		{
			"url" : "http://kbcc.cuny.libguides.com/prototype2",
			"message" : "Look up a magazine article about Angela Merkel."
		},
		{
			"url" : "http://kbcc.cuny.libguides.com/prototype3",
			"message" : "Find a newspaper article about Syrian refugees."
		},
		{
			"url" : "http://kbcc.cuny.libguides.com/prototype4",
			"message" : "Look up a book called Animal Farm."
		}
	],
	[
		{
			"url" : "http://kbcc.cuny.libguides.com/prototype2",
			"message" : "Find the library hours for today."
		},
		{
			"url" : "http://kbcc.cuny.libguides.com/prototype3",
			"message" : "Look up a magazine article about Angela Merkel."
		},
		{
			"url" : "http://kbcc.cuny.libguides.com/prototype4",
			"message" : "Find a newspaper article about Syrian refugees."
		},
		{
			"url" : "http://kbcc.cuny.libguides.com/prototype1",
			"message" : "Look up a book called Animal Farm."
		}
	],
	[
		{
			"url" : "http://kbcc.cuny.libguides.com/prototype3",
			"message" : "Find the library hours for today."
		},
		{
			"url" : "http://kbcc.cuny.libguides.com/prototype4",
			"message" : "Look up a magazine article about Angela Merkel."
		},
		{
			"url" : "http://kbcc.cuny.libguides.com/prototype1",
			"message" : "Find a newspaper article about Syrian refugees."
		},
		{
			"url" : "http://kbcc.cuny.libguides.com/prototype2",
			"message" : "Look up a book called Animal Farm."
		}
	],
	[
		{
			"url" : "http://kbcc.cuny.libguides.com/prototype4",
			"message" : "Find the librarys hours for today."
		},
		{
			"url" : "http://kbcc.cuny.libguides.com/prototype1",
			"message" : "Look up a magazine article about Angela Merkel."
		},
		{
			"url" : "http://kbcc.cuny.libguides.com/prototype2",
			"message" : "Find a newspaper article about Syrian refugees."
		},
		{
			"url" : "http://kbcc.cuny.libguides.com/prototype3",
			"message" : "Look up a book called Animal Farm."
		}
	]
];

// select a cohort to get a unique combination of questions and interfaces
counter = 0;
cohort = 0;
var activities = giantjson[cohort];

// make the table using js
var tableHTML1 = '

Activity #';
var tableHTML2 = ' Try it now!

"';

$(document).ready(function() {
	for (i = 0; i < activities.length; i++) {
		$("tbody").append(tableHTML1 + parseInt(i + 1) + tableHTML2 + i + tableHTML3 + i + tableHTML4);
	};
});

// set some variables in preparation for gotoactivity()
var startHTML = '<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
Activity #'
var midHTML = '

';
var endHTML = '
';
var timer = undefined;

// this handles responses to the "Try it now!" button. Changes button text; opens the activity in a new window
function gotoactivity(id) {
	counter++;
	displayid = parseInt(id) + 1;
	$('#' + id).text('completed!').removeClass('active').addClass('disabled');
	var instructions = window.open('', 'instructions', 'left=20,top=20,width=160,height=300,menubar=no,titlebar=no');
	var workspace = window.open(activities[id]['url'], 'workspace', 'left=200,top=20,width=1195,height=750,menubar=no,titlebar=no,scrollbars=yes');
	instructions.document.body.innerHTML = startHTML + displayid + midHTML + activities[id]['message'] + endHTML;

	// closes one window when the other is closed
	timer = setInterval(function() {
		$(this).focus(function() {
			workspace.close();
			instructions.close();
			completeactivities();
		});
		if (instructions.closed) {
			workspace.close();
		    clearInterval(timer);
		    completeactivities();
		}
		else if (workspace.closed) {
			instructions.close();
			clearInterval(timer);
			completeactivities();
		}
	}, 50);
};

// check that all activities are completed; if they are, display a congratulations screen
function completeactivities() {
	if (counter >= giantjson[cohort].length) {
		var completeHTML = '
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">Congratulations!
You\'ve completed our usability test!
'
		var complete = window.open('', 'complete', 'left=500,top=200,width=250,height=100,menubar=no,titlebar=no');
		complete.document.body.innerHTML = completeHTML;
		counter = 0;
	}
	else {}
}<|endoftext|>


<commit_before>(function($) {

  $.fn.pickList = function(options) {

    var self = this;

    var opts = $.extend({}, $.fn.pickList.defaults, options);

    var wrapper = $('<div />')
        .addClass(opts.wrapperClass)
        .appendTo(opts.container);

    function List(classes) {

      var self = this;

      this.items = [];

      this.elmt = $('<select />')
          .attr('multiple', 'multiple')
          .addClass('mai-picklist-list ' + classes);

      this.addItem = function(item) {
        var itemElmt = $('<option />')
            .attr('value', item[opts.columnItemValue])
            .text(item[opts.columnItemText]);
        self.elmt.append(itemElmt);
        self.items.push(item);
        return self.items;
      };

      this.removeItem = function(item) {
        var index = self.items.indexOf(item);
        if (index != -1) {
          self.items.splice(index, 1);
        }
        return self.items;
      };

    };

    function Control(name, container) {
      var elmt = $('<button />')
        .addClass(opts.controlButtonClass)
        .appendTo(container)
        .text(name);

      return elmt;
    };

    function moveSelected(listA, listB) {
      var selectedOptions = listA.find('option:selected');
      selectedOptions.appendTo(listB);
      for (var iterator = 0; iterator < selectedOptions; iterator++) {
        listA.removeItem(selectedOptions[iterator]);
      }
      listB.find('option:selected').prop('selected', false);
    };

    function moveAll(listA, listB) {
      var options = listA.find('option');
      options.appendTo(listB);
      listA.items = [];
    }

    this.leftList  = new List(opts.leftListClass);
    this.rightList = new List(opts.rightListClass);

    this.leftColumn = $('<div />')
          .addClass(opts.leftColumnClass);

    this.middleColumn = $('<div />')
          .addClass(opts.controlColumnClass);

    this.rightColumn = $('<div />')
          .addClass(opts.rightColumnClass);

    this.addBtn = new Control('Add >', this.middleColumn);
    this.addBtn.on('click', function(e) {
      moveSelected(self.leftList.elmt, self.rightList.elmt);
    });

    this.addAllBtn = new Control('Add All >>', this.middleColumn);
    this.addAllBtn.on('click', function(e) {
      moveAll(self.leftList.elmt, self.rightList.elmt);
    });

    this.removeBtn = new Control('< Remove', this.middleColumn);
    this.removeBtn.on('click', function(e) {
      moveSelected(self.rightList.elmt, self.leftList.elmt);
    });

    this.removeAllBtn = new Control('<< Remove All', this.middleColumn);
    this.removeAllBtn.on('click', function(e) {
      moveAll(self.rightList.elmt, self.leftList.elmt);
    });

    wrapper.append(this.leftColumn)
           .append(this.middleColumn)
           .append(this.rightColumn);

    this.leftColumn.append(this.leftList.elmt);
    this.rightColumn.append(this.rightList.elmt);

    for (var iterator = 0; iterator < opts.leftListData.length; iterator++) {
      this.leftList.addItem(opts.leftListData[iterator]);
    }

    for (var iterator = 0; iterator < opts.rightListData.length; iterator++) {
      this.rightList.addItem(opts.rightListData[iterator]);
    }

    return this;
  };

  $.fn.pickList.defaults = {
    container: 'body',
    wrapperClass: 'mai-picklist-wrapper',
    leftColumnClass: 'col-left',
    controlColumnClass: 'col-middle',
    rightColumnClass: 'col-right',
    controlButtonClass: 'mai-picklist-btn',
    leftListClass: 'mai-picklist-leftlist',
    leftListData: [],
    rightListData: [],
    rightListClass: 'mai-picklist-rightlist',
    columnItemValue: 'id',
    columnItemText: 'text'
  };

}(jQuery));
<commit_msg>deselect items on move-all<commit_after>(function($) {

  $.fn.pickList = function(options) {

    var self = this;

    var opts = $.extend({}, $.fn.pickList.defaults, options);

    var wrapper = $('<div />')
        .addClass(opts.wrapperClass)
        .appendTo(opts.container);

    function List(classes) {

      var self = this;

      this.items = [];

      this.elmt = $('<select />')
          .attr('multiple', 'multiple')
          .addClass('mai-picklist-list ' + classes);

      this.addItem = function(item) {
        var itemElmt = $('<option />')
            .attr('value', item[opts.columnItemValue])
            .text(item[opts.columnItemText]);
        self.elmt.append(itemElmt);
        self.items.push(item);
        return self.items;
      };

      this.removeItem = function(item) {
        var index = self.items.indexOf(item);
        if (index != -1) {
          self.items.splice(index, 1);
        }
        return self.items;
      };

    };

    function Control(name, container) {
      var elmt = $('<button />')
        .addClass(opts.controlButtonClass)
        .appendTo(container)
        .text(name);

      return elmt;
    };

    function moveSelected(listA, listB) {
      var selectedOptions = listA.find('option:selected');
      selectedOptions.appendTo(listB);
      for (var iterator = 0; iterator < selectedOptions; iterator++) {
        listA.removeItem(selectedOptions[iterator]);
      }
      listB.find('option:selected').prop('selected', false);
    };

    function moveAll(listA, listB) {
      var options = listA.find('option');
      options.appendTo(listB);
      listA.items = [];
      listB.find('option:selected').prop('selected', false);
    }

    this.leftList  = new List(opts.leftListClass);
    this.rightList = new List(opts.rightListClass);

    this.leftColumn = $('<div />')
          .addClass(opts.leftColumnClass);

    this.middleColumn = $('<div />')
          .addClass(opts.controlColumnClass);

    this.rightColumn = $('<div />')
          .addClass(opts.rightColumnClass);

    this.addBtn = new Control('Add >', this.middleColumn);
    this.addBtn.on('click', function(e) {
      moveSelected(self.leftList.elmt, self.rightList.elmt);
    });

    this.addAllBtn = new Control('Add All >>', this.middleColumn);
    this.addAllBtn.on('click', function(e) {
      moveAll(self.leftList.elmt, self.rightList.elmt);
    });

    this.removeBtn = new Control('< Remove', this.middleColumn);
    this.removeBtn.on('click', function(e) {
      moveSelected(self.rightList.elmt, self.leftList.elmt);
    });

    this.removeAllBtn = new Control('<< Remove All', this.middleColumn);
    this.removeAllBtn.on('click', function(e) {
      moveAll(self.rightList.elmt, self.leftList.elmt);
    });

    wrapper.append(this.leftColumn)
           .append(this.middleColumn)
           .append(this.rightColumn);

    this.leftColumn.append(this.leftList.elmt);
    this.rightColumn.append(this.rightList.elmt);

    for (var iterator = 0; iterator < opts.leftListData.length; iterator++) {
      this.leftList.addItem(opts.leftListData[iterator]);
    }

    for (var iterator = 0; iterator < opts.rightListData.length; iterator++) {
      this.rightList.addItem(opts.rightListData[iterator]);
    }

    return this;
  };

  $.fn.pickList.defaults = {
    container: 'body',
    wrapperClass: 'mai-picklist-wrapper',
    leftColumnClass: 'col-left',
    controlColumnClass: 'col-middle',
    rightColumnClass: 'col-right',
    controlButtonClass: 'mai-picklist-btn',
    leftListClass: 'mai-picklist-leftlist',
    leftListData: [],
    rightListData: [],
    rightListClass: 'mai-picklist-rightlist',
    columnItemValue: 'id',
    columnItemText: 'text'
  };

}(jQuery));
<|endoftext|>