<commit_before>
/* ---- Variables ---- */
var actb_timeOut = -1; // Autocomplete Timeout in ms (-1: autocomplete never time out)
var actb_lim = 5;    // Number of elements autocomplete can show (-1: no limit)
var actb_firstText = false; // should the auto complete be limited to the beginning of keyword?
/* ---- Variables ---- */

/* --- Styles --- */
var actb_bgColor = '#FFFFFF';
var actb_textColor = '#000000';
var actb_hBgColor = '#990000';
var actb_htextColor = '#FFFFFF';
var actb_fFamily = 'Courier';
var actb_fSize = '14px';
var actb_hStyle = 'text-decoration:underline;font-weight="bold"';
/* --- Styles --- */

/* ---- Constants ---- */
var actb_keywords = new Array();
var actb_display = true;
var actb_pos = 0;
var actb_total = 0;
var actb_curr = null;
var actb_rangeu = 0;
var actb_ranged = 0;
var actb_bool = new Array();
var actb_pre = 0;
var actb_toid;
var actb_tomake = true;
/* ---- Constants ---- */

function actb_parse(n){
    var t = escape(actb_curr.value);
    var tobuild = '';
    var i;
    var re = "";

    if (actb_firstText){
        re = new RegExp("^" + t, "i");
    }else{
        re = new RegExp(t, "i");
    }
    var p = n.search(re);

    for (i=0;i<p;i++){
        tobuild += n.substr(i,1);
    }
    tobuild += ""
    for (i=p;i<t.length+p;i++){
        tobuild += n.substr(i,1);
    }
    tobuild += "";
    for (i=t.length+p;i<n.length;i++){
        tobuild += n.substr(i,1);
    }
    return tobuild;
}
function actb_generate(){
    if(navigator.appName == "Microsoft Internet Explorer")
        if (document.getElementById('tat_frame')) document.body.removeChild(document.getElementById('tat_frame'));
    if (document.getElementById('tat_table')) document.body.removeChild(document.getElementById('tat_table'));
    if(navigator.appName == "Microsoft Internet Explorer") {
        frame = document.createElement('iframe');
        frame.cellSpacing='1px';
        frame.cellPadding='2px';
        frame.style.zIndex = 3;
        frame.style.position='absolute';
        frame.style.top = eval(curTop() + actb_curr.offsetHeight) + "px";
        frame.style.left = curLeft() + "px";
        frame.style.backgroundColor=actb_bgColor;
        frame.id = 'tat_frame';
    }
    a = document.createElement('table');
    a.cellSpacing='1px';
    a.cellPadding='2px';
    a.style.border = '1px solid #990000';
    a.style.zIndex = 5;
    a.style.position='absolute';
    a.style.top = eval(curTop() + actb_curr.offsetHeight) + "px";
    a.style.left = curLeft() + "px";
    a.style.backgroundColor=actb_bgColor;
    a.id = 'tat_table';
    if(navigator.appName == "Microsoft Internet Explorer")
        document.body.appendChild(frame);
    document.body.appendChild(a);
    var i;
    var first = true;
    var j = 1;
    var height = 0;
    var counter = 0;
    for (i=0;i<actb_keywords.length;i++){
        if (actb_bool[i]){
            counter++;
            r = a.insertRow(-1);
            if (first && !actb_tomake){
                r.style.backgroundColor = actb_hBgColor;
                first = false;
                actb_pos = counter;
            }else if(actb_pre == i){
                r.style.backgroundColor = actb_hBgColor;
                first = false;
                actb_pos = counter;
            }else{
                r.style.backgroundColor = actb_bgColor;
                r.style.color = actb_bgColor;
            }
            r.id = 'tat_tr'+(j);
            c = r.insertCell(-1);
            c.style.color = actb_textColor;
            counter = 0;
            first = true;
            if (first && !actb_tomake){
                c.style.color = actb_htextColor;
                first = false;
                actb_pos = counter;
            }else if(actb_pre == i){
                c.style.color = actb_htextColor;
                first = false;
                actb_pos = counter;
            }else{
                c.style.color = actb_textColor;
            }
            c.style.fontFamily = actb_fFamily;
            a.style.zIndex = 5;
            c.style.fontSize = actb_fSize;
            c.innerHTML = actb_parse(actb_keywords[i]);
            c.id = 'tat_td'+(j);
            height = height + c.offsetHeight;
            width = c.offsetWidth;
            j++;
        }
        if (j - 1 == actb_lim && j < actb_total){
            r = a.insertRow(-1);
            r.style.backgroundColor = actb_bgColor;
            c = r.insertCell(-1);
            c.style.color = actb_textColor;
            a.style.zIndex = 5;
            c.style.fontFamily = 'arial narrow';
            c.style.fontSize = actb_fSize;
            c.align='center';
            c.innerHTML = '\\/';
            break;
        }
    }
    actb_rangeu = 1;
    actb_ranged = j-1;
    actb_display = true;
    if (actb_pos <= 0) actb_pos = 1;
    if(navigator.appName == "Microsoft Internet Explorer") {
        frame.width=width;
        frame.height=height + 5;
    }
}
function curTop(){
    actb_toreturn = 0;
    obj = actb_curr;
    while(obj){
        actb_toreturn += obj.offsetTop;
        obj = obj.offsetParent;
    }
    return actb_toreturn;
}
function curLeft(){
    actb_toreturn = 0;
    obj = actb_curr;
    while(obj){
        actb_toreturn += obj.offsetLeft;
        obj = obj.offsetParent;
    }
    return actb_toreturn;
}
function actb_remake() {
    document.body.removeChild(document.getElementById('tat_table'));
    if(navigator.appName == "Microsoft Internet Explorer") {
        document.body.removeChild(document.getElementById('tat_frame'));
        frame = document.createElement('iframe');
        frame.cellSpacing='2px';
        frame.cellPadding='3px';
        frame.style.position='absolute';
        frame.style.zIndex = 5;
        frame.style.top = eval(curTop() + actb_curr.offsetHeight) + "px";
        frame.style.left = curLeft() + "px";
        frame.style.backgroundColor=actb_bgColor;
        frame.id = 'tat_frame';
    }
    a = document.createElement('table');
    a.cellSpacing='2px';
    a.cellPadding='3px';
    a.style.position='absolute';
    a.style.zIndex = 5;
    a.style.top = eval(curTop() + actb_curr.offsetHeight) + "px";
    a.style.left = curLeft() + "px";
    a.style.backgroundColor=actb_bgColor;
    a.id = 'tat_table';
    if(navigator.appName == "Microsoft Internet Explorer")
        document.body.appendChild(frame);
    document.body.appendChild(a);
    var width = 0;
    var height = 0;
    var i;
    var first = true;
    var j = 1;
    if (actb_rangeu > 1){
        r = a.insertRow(-1);
        r.style.backgroundColor = actb_bgColor;
        c = r.insertCell(-1);
        c.style.color = actb_textColor;
        c.style.fontFamily = 'arial narrow';
        a.style.zIndex = 5;
        c.style.fontSize = actb_fSize;
        c.align='center';
        c.innerHTML = '/\\';
    }
    for (i=0;i<actb_keywords.length;i++){
        if (actb_bool[i]){
            if (j >= actb_rangeu && j <= actb_ranged){
                r = a.insertRow(-1);
                r.style.backgroundColor = actb_bgColor;
                r.id = 'tat_tr'+(j);
                c = r.insertCell(-1);
                c.style.color = actb_textColor;
                c.style.fontFamily = actb_fFamily;
                a.style.zIndex = 5;
                c.style.fontSize = actb_fSize;
                c.innerHTML = actb_parse(actb_keywords[i]);
                c.id = 'tat_td'+(j);
                j++;
            }else{
                j++;
            }
        }
        if (j > actb_ranged) break;
    }
    if (j-1 < actb_total){
        r = a.insertRow(-1);
        r.style.backgroundColor = actb_bgColor;
        c = r.insertCell(-1);
        c.style.color = actb_textColor;
        c.style.fontFamily = 'arial narrow';
        c.style.fontSize = actb_fSize;
        c.align='center';
        c.innerHTML = '\\/';
        height = height + c.height;
        width = c.width;
    }
    if(navigator.appName == "Microsoft Internet Explorer") {
        frame.width=width
        frame.height=height;
    }
}
function actb_goup(){
    if (!actb_display) return;
    if (actb_pos == 1) return;
    document.getElementById('tat_tr'+actb_pos).style.backgroundColor = actb_bgColor;
    document.getElementById('tat_td'+actb_pos).style.color = actb_textColor;
    actb_pos--;
    if (actb_pos < actb_rangeu) actb_moveup();
    document.getElementById('tat_tr'+actb_pos).style.backgroundColor = actb_hBgColor;
    document.getElementById('tat_td'+actb_pos).style.color = actb_htextColor;
    if (actb_toid) clearTimeout(actb_toid);
    if (actb_timeOut > 0) actb_toid = setTimeout("actb_removedisp()",actb_timeOut);
}
function actb_godown(){
    if (!actb_display) return;
    if (actb_pos == actb_total) return;
    document.getElementById('tat_tr'+actb_pos).style.backgroundColor = actb_bgColor;
    document.getElementById('tat_td'+actb_pos).style.color = actb_textColor;
    actb_pos++;
    if (actb_pos > actb_ranged) actb_movedown();
    document.getElementById('tat_tr'+actb_pos).style.backgroundColor = actb_hBgColor;
    document.getElementById('tat_td'+actb_pos).style.color= actb_htextColor;
    if (actb_toid) clearTimeout(actb_toid);
    if (actb_timeOut > 0) actb_toid = setTimeout("actb_removedisp()",actb_timeOut);
}
function actb_movedown(){
    actb_rangeu++;
    actb_ranged++;
    actb_remake();
}
function actb_moveup(){
    actb_rangeu--;
    actb_ranged--;
    actb_remake();
}
function actb_penter(){
    if (!actb_display) return;
    actb_display = 0;
    var word = '';
    var c = 0;
    for (var i=0;i<=actb_keywords.length;i++){
        if (actb_bool[i]) c++;
        if (c == actb_pos){
            word = actb_keywords[i];
            break;
        }
    }
    a = word;//actb_keywords[actb_pos-1];//document.getElementById('tat_td'+actb_pos).;
    actb_curr.value = a;
    actb_removedisp();
}
function actb_removedisp(){
    actb_display = 0;
    if(navigator.appName == "Microsoft Internet Explorer")
        if (document.getElementById('tat_frame')) document.body.removeChild(document.getElementById('tat_frame'));
    if (document.getElementById('tat_table')) document.body.removeChild(document.getElementById('tat_table'));
    if (actb_toid) clearTimeout(actb_toid);
}
function actb_checkkey(evt, sndr){
    a = evt.keyCode;
    if (a == 38){ // up key
        actb_goup();
    }else if(a == 40){ // down key
        actb_godown();
    }else if(a == 13){
        actb_penter();
    }
}
function actb_tocomplete(sndr,evt,arr){
    if (arr) actb_keywords = arr;
    if (evt.keyCode == 38 || evt.keyCode == 40 || evt.keyCode == 13) return;
    var i;
    if (actb_display){
        var word = 0;
        var c = 0;
        for (i=0;i<=actb_keywords.length;i++){
            if (actb_bool[i]) c++;
            if (c == actb_pos){
                word = i;
                break;
            }
        }
        actb_pre = word;//actb_pos;
    }else{ actb_pre = -1};

    if (!sndr) sndr = evt.srcElement;
    actb_curr = sndr;

    if (sndr.value == ''){
        actb_removedisp();
        return;
    }
    var t = sndr.value;
    var re = "";
    if (actb_firstText){
        re = new RegExp("^" + t, "i");
    }else{
        re = new RegExp(t, "i");
    }

    actb_total = 0;
    actb_tomake = false;
    for (i=0;i<actb_keywords.length;i++){
        actb_bool[i] = false;
        if (re.test(actb_keywords[i])){
            actb_total++;
            actb_bool[i] = true;
            if (actb_pre == i) actb_tomake = true;
        }
    }
    if (actb_toid) clearTimeout(actb_toid);
    if (actb_timeOut > 0) actb_toid = setTimeout("actb_removedisp()",actb_timeOut);
    actb_generate(actb_bool);
}
<commit_msg>Do not try to activate dropdown code if no auto complete items are defined the browser is ie<commit_after>
/* ---- Variables ---- */
var actb_timeOut = -1; // Autocomplete Timeout in ms (-1: autocomplete never time out)
var actb_lim = 5;    // Number of elements autocomplete can show (-1: no limit)
var actb_firstText = false; // should the auto complete be limited to the beginning of keyword?
/* ---- Variables ---- */

/* --- Styles --- */
var actb_bgColor = '#FFFFFF';
var actb_textColor = '#000000';
var actb_hBgColor = '#990000';
var actb_htextColor = '#FFFFFF';
var actb_fFamily = 'Courier';
var actb_fSize = '14px';
var actb_hStyle = 'text-decoration:underline;font-weight="bold"';
/* --- Styles --- */

/* ---- Constants ---- */
var actb_keywords = new Array();
var actb_display = true;
var actb_pos = 0;
var actb_total = 0;
var actb_curr = null;
var actb_rangeu = 0;
var actb_ranged = 0;
var actb_bool = new Array();
var actb_pre = 0;
var actb_toid;
var actb_tomake = true;
/* ---- Constants ---- */

function actb_parse(n){
    var t = escape(actb_curr.value);
    var tobuild = '';
    var i;
    var re = "";

    if (actb_firstText){
        re = new RegExp("^" + t, "i");
    }else{
        re = new RegExp(t, "i");
    }
    var p = n.search(re);

    for (i=0;i<p;i++){
        tobuild += n.substr(i,1);
    }
    tobuild += ""
    for (i=p;i<t.length+p;i++){
        tobuild += n.substr(i,1);
    }
    tobuild += "";
    for (i=t.length+p;i<n.length;i++){
        tobuild += n.substr(i,1);
    }
    return tobuild;
}
function actb_generate(){
    if(navigator.appName == "Microsoft Internet Explorer") {
        if(actb_keywords.length < 1) return; // if no items setup, do not invoke code.  prevent ie crashing.
        if (document.getElementById('tat_frame')) document.body.removeChild(document.getElementById('tat_frame'));
    }
    if (document.getElementById('tat_table')) document.body.removeChild(document.getElementById('tat_table'));
    if(navigator.appName == "Microsoft Internet Explorer") {
        frame = document.createElement('iframe');
        frame.cellSpacing='1px';
        frame.cellPadding='2px';
        frame.style.zIndex = 3;
        frame.style.position='absolute';
        frame.style.top = eval(curTop() + actb_curr.offsetHeight) + "px";
        frame.style.left = curLeft() + "px";
        frame.style.backgroundColor=actb_bgColor;
        frame.id = 'tat_frame';
    }
    a = document.createElement('table');
    a.cellSpacing='1px';
    a.cellPadding='2px';
    a.style.border = '1px solid #990000';
    a.style.zIndex = 5;
    a.style.position='absolute';
    a.style.top = eval(curTop() + actb_curr.offsetHeight) + "px";
    a.style.left = curLeft() + "px";
    a.style.backgroundColor=actb_bgColor;
    a.id = 'tat_table';
    if(navigator.appName == "Microsoft Internet Explorer")
        document.body.appendChild(frame);
    document.body.appendChild(a);
    var i;
    var first = true;
    var j = 1;
    var height = 0;
    var counter = 0;
    for (i=0;i<actb_keywords.length;i++){
        if (actb_bool[i]){
            counter++;
            r = a.insertRow(-1);
            if (first && !actb_tomake){
                r.style.backgroundColor = actb_hBgColor;
                first = false;
                actb_pos = counter;
            }else if(actb_pre == i){
                r.style.backgroundColor = actb_hBgColor;
                first = false;
                actb_pos = counter;
            }else{
                r.style.backgroundColor = actb_bgColor;
                r.style.color = actb_bgColor;
            }
            r.id = 'tat_tr'+(j);
            c = r.insertCell(-1);
            c.style.color = actb_textColor;
            counter = 0;
            first = true;
            if (first && !actb_tomake){
                c.style.color = actb_htextColor;
                first = false;
                actb_pos = counter;
            }else if(actb_pre == i){
                c.style.color = actb_htextColor;
                first = false;
                actb_pos = counter;
            }else{
                c.style.color = actb_textColor;
            }
            c.style.fontFamily = actb_fFamily;
            a.style.zIndex = 5;
            c.style.fontSize = actb_fSize;
            c.innerHTML = actb_parse(actb_keywords[i]);
            c.id = 'tat_td'+(j);
            height = height + c.offsetHeight;
            width = c.offsetWidth;
            j++;
        }
        if (j - 1 == actb_lim && j < actb_total){
            r = a.insertRow(-1);
            r.style.backgroundColor = actb_bgColor;
            c = r.insertCell(-1);
            c.style.color = actb_textColor;
            a.style.zIndex = 5;
            c.style.fontFamily = 'arial narrow';
            c.style.fontSize = actb_fSize;
            c.align='center';
            c.innerHTML = '\\/';
            break;
        }
    }
    actb_rangeu = 1;
    actb_ranged = j-1;
    actb_display = true;
    if (actb_pos <= 0) actb_pos = 1;
    if(navigator.appName == "Microsoft Internet Explorer") {
        frame.width=width;
        frame.height=height + 5;
    }
}
function curTop(){
    actb_toreturn = 0;
    obj = actb_curr;
    while(obj){
        actb_toreturn += obj.offsetTop;
        obj = obj.offsetParent;
    }
    return actb_toreturn;
}
function curLeft(){
    actb_toreturn = 0;
    obj = actb_curr;
    while(obj){
        actb_toreturn += obj.offsetLeft;
        obj = obj.offsetParent;
    }
    return actb_toreturn;
}
function actb_remake() {
    document.body.removeChild(document.getElementById('tat_table'));
    if(navigator.appName == "Microsoft Internet Explorer") {
        document.body.removeChild(document.getElementById('tat_frame'));
        frame = document.createElement('iframe');
        frame.cellSpacing='2px';
        frame.cellPadding='3px';
        frame.style.position='absolute';
        frame.style.zIndex = 5;
        frame.style.top = eval(curTop() + actb_curr.offsetHeight) + "px";
        frame.style.left = curLeft() + "px";
        frame.style.backgroundColor=actb_bgColor;
        frame.id = 'tat_frame';
    }
    a = document.createElement('table');
    a.cellSpacing='2px';
    a.cellPadding='3px';
    a.style.position='absolute';
    a.style.zIndex = 5;
    a.style.top = eval(curTop() + actb_curr.offsetHeight) + "px";
    a.style.left = curLeft() + "px";
    a.style.backgroundColor=actb_bgColor;
    a.id = 'tat_table';
    if(navigator.appName == "Microsoft Internet Explorer")
        document.body.appendChild(frame);
    document.body.appendChild(a);
    var width = 0;
    var height = 0;
    var i;
    var first = true;
    var j = 1;
    if (actb_rangeu > 1){
        r = a.insertRow(-1);
        r.style.backgroundColor = actb_bgColor;
        c = r.insertCell(-1);
        c.style.color = actb_textColor;
        c.style.fontFamily = 'arial narrow';
        a.style.zIndex = 5;
        c.style.fontSize = actb_fSize;
        c.align='center';
        c.innerHTML = '/\\';
    }
    for (i=0;i<actb_keywords.length;i++){
        if (actb_bool[i]){
            if (j >= actb_rangeu && j <= actb_ranged){
                r = a.insertRow(-1);
                r.style.backgroundColor = actb_bgColor;
                r.id = 'tat_tr'+(j);
                c = r.insertCell(-1);
                c.style.color = actb_textColor;
                c.style.fontFamily = actb_fFamily;
                a.style.zIndex = 5;
                c.style.fontSize = actb_fSize;
                c.innerHTML = actb_parse(actb_keywords[i]);
                c.id = 'tat_td'+(j);
                j++;
            }else{
                j++;
            }
        }
        if (j > actb_ranged) break;
    }
    if (j-1 < actb_total){
        r = a.insertRow(-1);
        r.style.backgroundColor = actb_bgColor;
        c = r.insertCell(-1);
        c.style.color = actb_textColor;
        c.style.fontFamily = 'arial narrow';
        c.style.fontSize = actb_fSize;
        c.align='center';
        c.innerHTML = '\\/';
        height = height + c.height;
        width = c.width;
    }
    if(navigator.appName == "Microsoft Internet Explorer") {
        frame.width=width
        frame.height=height;
    }
}
function actb_goup(){
    if (!actb_display) return;
    if (actb_pos == 1) return;
    document.getElementById('tat_tr'+actb_pos).style.backgroundColor = actb_bgColor;
    document.getElementById('tat_td'+actb_pos).style.color = actb_textColor;
    actb_pos--;
    if (actb_pos < actb_rangeu) actb_moveup();
    document.getElementById('tat_tr'+actb_pos).style.backgroundColor = actb_hBgColor;
    document.getElementById('tat_td'+actb_pos).style.color = actb_htextColor;
    if (actb_toid) clearTimeout(actb_toid);
    if (actb_timeOut > 0) actb_toid = setTimeout("actb_removedisp()",actb_timeOut);
}
function actb_godown(){
    if (!actb_display) return;
    if (actb_pos == actb_total) return;
    document.getElementById('tat_tr'+actb_pos).style.backgroundColor = actb_bgColor;
    document.getElementById('tat_td'+actb_pos).style.color = actb_textColor;
    actb_pos++;
    if (actb_pos > actb_ranged) actb_movedown();
    document.getElementById('tat_tr'+actb_pos).style.backgroundColor = actb_hBgColor;
    document.getElementById('tat_td'+actb_pos).style.color= actb_htextColor;
    if (actb_toid) clearTimeout(actb_toid);
    if (actb_timeOut > 0) actb_toid = setTimeout("actb_removedisp()",actb_timeOut);
}
function actb_movedown(){
    actb_rangeu++;
    actb_ranged++;
    actb_remake();
}
function actb_moveup(){
    actb_rangeu--;
    actb_ranged--;
    actb_remake();
}
function actb_penter(){
    if (!actb_display) return;
    actb_display = 0;
    var word = '';
    var c = 0;
    for (var i=0;i<=actb_keywords.length;i++){
        if (actb_bool[i]) c++;
        if (c == actb_pos){
            word = actb_keywords[i];
            break;
        }
    }
    a = word;//actb_keywords[actb_pos-1];//document.getElementById('tat_td'+actb_pos).;
    actb_curr.value = a;
    actb_removedisp();
}
function actb_removedisp(){
    actb_display = 0;
    if(navigator.appName == "Microsoft Internet Explorer")
        if (document.getElementById('tat_frame')) document.body.removeChild(document.getElementById('tat_frame'));
    if (document.getElementById('tat_table')) document.body.removeChild(document.getElementById('tat_table'));
    if (actb_toid) clearTimeout(actb_toid);
}
function actb_checkkey(evt, sndr){
    a = evt.keyCode;
    if (a == 38){ // up key
        actb_goup();
    }else if(a == 40){ // down key
        actb_godown();
    }else if(a == 13){
        actb_penter();
    }
}
function actb_tocomplete(sndr,evt,arr){
    if (arr) actb_keywords = arr;
    if (evt.keyCode == 38 || evt.keyCode == 40 || evt.keyCode == 13) return;
    var i;
    if (actb_display){
        var word = 0;
        var c = 0;
        for (i=0;i<=actb_keywords.length;i++){
            if (actb_bool[i]) c++;
            if (c == actb_pos){
                word = i;
                break;
            }
        }
        actb_pre = word;//actb_pos;
    }else{ actb_pre = -1};

    if (!sndr) sndr = evt.srcElement;
    actb_curr = sndr;

    if (sndr.value == ''){
        actb_removedisp();
        return;
    }
    var t = sndr.value;
    var re = "";
    if (actb_firstText){
        re = new RegExp("^" + t, "i");
    }else{
        re = new RegExp(t, "i");
    }

    actb_total = 0;
    actb_tomake = false;
    for (i=0;i<actb_keywords.length;i++){
        actb_bool[i] = false;
        if (re.test(actb_keywords[i])){
            actb_total++;
            actb_bool[i] = true;
            if (actb_pre == i) actb_tomake = true;
        }
    }
    if (actb_toid) clearTimeout(actb_toid);
    if (actb_timeOut > 0) actb_toid = setTimeout("actb_removedisp()",actb_timeOut);
    actb_generate(actb_bool);
}
<|endoftext|>


<commit_before>/**
 * Copyright © Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file 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.
 */
define([
    'jquery',
    'Amazon_Pay/js/action/checkout-session-config-load',
    'Amazon_Pay/js/model/storage',
    'mage/url',
    'Amazon_Pay/js/amazon-checkout',
    'Magento_Customer/js/customer-data',
    'Magento_Checkout/js/model/payment/additional-validators'
], function ($, checkoutSessionConfigLoad, amazonStorage, url, amazonCheckout, customerData, additionalValidators) {
    'use strict';

    $.widget('amazon.AmazonButton', {
        options: {
            payOnly: null,
            placement: 'Cart',
            hideIfUnavailable: '',
            buttonType: 'Normal'
        },

        drawing: false,

        _loadButtonConfig: function (callback) {
            checkoutSessionConfigLoad(function (checkoutSessionConfig) {
                if (!$.isEmptyObject(checkoutSessionConfig)) {
                    var payload = checkoutSessionConfig['checkout_payload'];
                    var signature = checkoutSessionConfig['checkout_signature'];

                    if (this.buttonType === 'PayNow') {
                        payload = checkoutSessionConfig['paynow_payload'];
                        signature = checkoutSessionConfig['paynow_signature'];
                    }

                    callback({
                        merchantId: checkoutSessionConfig['merchant_id'],
                        publicKeyId: checkoutSessionConfig['public_key_id'],
                        ledgerCurrency: checkoutSessionConfig['currency'],
                        sandbox: checkoutSessionConfig['sandbox'],
                        checkoutLanguage: checkoutSessionConfig['language'],
                        productType: this._isPayOnly(checkoutSessionConfig['pay_only']) ? 'PayOnly' : 'PayAndShip',
                        placement: this.options.placement,
                        buttonColor: checkoutSessionConfig['button_color'],
                        createCheckoutSessionConfig: {
                            payloadJSON: payload,
                            signature: signature,
                            publicKeyId: checkoutSessionConfig['public_key_id'],
                        }
                    });

                    if (this.options.placement !== "Checkout") {
                        $(this.options.hideIfUnavailable).show();
                    }
                } else {
                    $(this.options.hideIfUnavailable).hide();
                }
            }.bind(this));
        },

        /**
         * @param {boolean} isCheckoutSessionPayOnly
         * @returns {boolean}
         * @private
         */
        _isPayOnly: function (isCheckoutSessionPayOnly) {
            var result = isCheckoutSessionPayOnly;
            if (result && this.options.payOnly !== null) {
                result = this.options.payOnly;
            }
            return result;
        },

        /**
         * Create button
         */
        _create: function () {
            var self = this;
            if (this.options.placement === 'PayNow') {
                // PayNow is not a valid placement for Amazon Pay API, only used for changing payload
                this.options.placement = 'Checkout';
                this.buttonType = 'PayNow';
            }

            this._draw();

            if (this.options.placement === 'Product') {
                this._redraw();
            }
        },

        /**
         * Draw button
         **/
        _draw: function () {
            var self = this;

            if (!this.drawing) {
                this.drawing = true;
                var $buttonContainer = this.element;
                amazonCheckout.withAmazonCheckout(function (amazon, args) {
                    var $buttonRoot = $('

');
                    $buttonRoot.html('');
                    $buttonContainer.empty().append($buttonRoot);

                    this._loadButtonConfig(function (buttonConfig) {
                        // do not use session config for decoupled button
                        delete buttonConfig.createCheckoutSessionConfig;
                        var amazonPayButton = amazon.Pay.renderButton('#' + $buttonRoot.empty().removeUniqueId().uniqueId().attr('id'), buttonConfig);
                        amazonPayButton.onClick(function() {
                            if (self.buttonType === 'PayNow' && !additionalValidators.validate()) {
                                return false;
                            }

                            self._loadButtonConfig(function (buttonConfig) {
                                var initConfig = {createCheckoutSessionConfig: buttonConfig.createCheckoutSessionConfig};
                                amazonPayButton.initCheckout(initConfig);
                            });
                            customerData.invalidate('*');
                        });
                        $('.amazon-button-container .field-tooltip').fadeIn();

                        self.drawing = false;
                    });
                }, this);
            }
        },

        /**
         * Redraw button if needed
         **/
        _redraw: function () {
            var self = this;

            amazonCheckout.withAmazonCheckout(function (amazon, args) {
                var cartData = customerData.get('cart');
                cartData.subscribe(function (updatedCart) {
                    if (!$(self.options.hideIfUnavailable).first().is(':visible')) {
                        self._draw();
                    }
                });
            });

        },

        click: function () {
            this.element.children().first().trigger('click');
        }
    });

    var cart = customerData.get('cart'),
        customer = customerData.get('customer'),
        canCheckoutWithAmazon = false;

    // to use Amazon Pay: customer needs to be logged in, or guest checkout allowed, or Amazon Sign-in enabled
    if (customer().firstname || amazonStorage.isGuestCheckoutEnabled || amazonStorage.isLwaEnabled) {
        canCheckoutWithAmazon = true;
    }

    if (amazonStorage.isEnabled && canCheckoutWithAmazon) {
        return $.amazon.AmazonButton;
    } else {
        return function(config, element) {
            customer.subscribe(function() {
                if (customer().firstname || amazonStorage.isGuestCheckoutEnabled || amazonStorage.isLwaEnabled) {
                    $(element).AmazonButton();
                }
            });
        };
    }
});
<commit_msg>ASD-794: Some OSC address updates are not reflected in PayNow button payload<commit_after>/**
 * Copyright © Amazon.com, Inc. or its affiliates. All Rights Reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License").
 * You may not use this file except in compliance with the License.
 * A copy of the License is located at
 *
 *  http://aws.amazon.com/apache2.0
 *
 * or in the "license" file accompanying this file. This file 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.
 */
define([
    'ko',
    'jquery',
    'Amazon_Pay/js/action/checkout-session-config-load',
    'Amazon_Pay/js/model/storage',
    'mage/url',
    'Amazon_Pay/js/amazon-checkout',
    'Magento_Customer/js/customer-data',
    'Magento_Checkout/js/model/payment/additional-validators',
    'mage/storage',
    'Magento_Checkout/js/model/error-processor'
], function (
        ko,
        $,
        checkoutSessionConfigLoad,
        amazonStorage,
        url,
        amazonCheckout,
        customerData,
        additionalValidators,
        storage,
        errorProcessor
    ) {
    'use strict';

    $.widget('amazon.AmazonButton', {
        options: {
            payOnly: null,
            placement: 'Cart',
            hideIfUnavailable: '',
            buttonType: 'Normal',
            isIosc: ko.observable($('button.iosc-place-order-button').length > 0)
        },

        drawing: false,

        _loadButtonConfig: function (callback) {
            checkoutSessionConfigLoad(function (checkoutSessionConfig) {
                if (!$.isEmptyObject(checkoutSessionConfig)) {
                    var payload = checkoutSessionConfig['checkout_payload'];
                    var signature = checkoutSessionConfig['checkout_signature'];

                    if (this.buttonType === 'PayNow') {
                        payload = checkoutSessionConfig['paynow_payload'];
                        signature = checkoutSessionConfig['paynow_signature'];
                    }

                    callback({
                        merchantId: checkoutSessionConfig['merchant_id'],
                        publicKeyId: checkoutSessionConfig['public_key_id'],
                        ledgerCurrency: checkoutSessionConfig['currency'],
                        sandbox: checkoutSessionConfig['sandbox'],
                        checkoutLanguage: checkoutSessionConfig['language'],
                        productType: this._isPayOnly(checkoutSessionConfig['pay_only']) ? 'PayOnly' : 'PayAndShip',
                        placement: this.options.placement,
                        buttonColor: checkoutSessionConfig['button_color'],
                        createCheckoutSessionConfig: {
                            payloadJSON: payload,
                            signature: signature,
                            publicKeyId: checkoutSessionConfig['public_key_id'],
                        }
                    });

                    if (this.options.placement !== "Checkout") {
                        $(this.options.hideIfUnavailable).show();
                    }
                } else {
                    $(this.options.hideIfUnavailable).hide();
                }
            }.bind(this));
        },

        /**
         * @param {boolean} isCheckoutSessionPayOnly
         * @returns {boolean}
         * @private
         */
        _isPayOnly: function (isCheckoutSessionPayOnly) {
            var result = isCheckoutSessionPayOnly;
            if (result && this.options.payOnly !== null) {
                result = this.options.payOnly;
            }
            return result;
        },

        /**
         * Create button
         */
        _create: function () {
            var self = this;
            if (this.options.placement === 'PayNow') {
                // PayNow is not a valid placement for Amazon Pay API, only used for changing payload
                this.options.placement = 'Checkout';
                this.buttonType = 'PayNow';
            }

            this._draw();

            if (this.options.placement === 'Product') {
                this._redraw();
            }
        },

        /**
         * Draw button
         **/
        _draw: function () {
            var self = this;

            if (!this.drawing) {
                this.drawing = true;
                var $buttonContainer = this.element;
                amazonCheckout.withAmazonCheckout(function (amazon, args) {
                    var $buttonRoot = $('

');
                    $buttonRoot.html('');
                    $buttonContainer.empty().append($buttonRoot);

                    this._loadButtonConfig(function (buttonConfig) {
                        // do not use session config for decoupled button
                        delete buttonConfig.createCheckoutSessionConfig;
                        var amazonPayButton = amazon.Pay.renderButton('#' + $buttonRoot.empty().removeUniqueId().uniqueId().attr('id'), buttonConfig);
                        amazonPayButton.onClick(function() {
                            if (self.buttonType === 'PayNow' && !additionalValidators.validate()) {
                                return false;
                            }
                            //This is for compatibility with Iosc. We need to update the customer's Magento session before getting the final config and payload
                            if (self.buttonType === 'PayNow' && self.options.isIosc()) {
                                storage.post(
                                    'checkout/onepage/update',
                                    "{}",
                                    false
                                ).done(
                                    function (response) {
                                        if (!response.error) {
                                            self._initCheckout(amazonPayButton);
                                        } else {
                                            errorProcessor.process(response);
                                        }
                                    }
                                ).fail(
                                    function (response) {
                                        errorProcessor.process(response);
                                    }
                                );
                            }else{
                                self._initCheckout(amazonPayButton);
                            }
                        });

                        $('.amazon-button-container .field-tooltip').fadeIn();
                        self.drawing = false;
                    });
                }, this);
            }
        },

        _initCheckout: function (amazonPayButton) {
            this._loadButtonConfig(function (buttonConfig) {
                var initConfig = {createCheckoutSessionConfig: buttonConfig.createCheckoutSessionConfig};
                amazonPayButton.initCheckout(initConfig);
            });
            customerData.invalidate('*');
        },

        /**
         * Redraw button if needed
         **/
        _redraw: function () {
            var self = this;

            amazonCheckout.withAmazonCheckout(function (amazon, args) {
                var cartData = customerData.get('cart');
                cartData.subscribe(function (updatedCart) {
                    if (!$(self.options.hideIfUnavailable).first().is(':visible')) {
                        self._draw();
                    }
                });
            });

        },

        click: function () {
            this.element.children().first().trigger('click');
        }
    });

    var cart = customerData.get('cart'),
        customer = customerData.get('customer'),
        canCheckoutWithAmazon = false;

    // to use Amazon Pay: customer needs to be logged in, or guest checkout allowed, or Amazon Sign-in enabled
    if (customer().firstname || amazonStorage.isGuestCheckoutEnabled || amazonStorage.isLwaEnabled) {
        canCheckoutWithAmazon = true;
    }

    if (amazonStorage.isEnabled && canCheckoutWithAmazon) {
        return $.amazon.AmazonButton;
    } else {
        return function(config, element) {
            customer.subscribe(function() {
                if (customer().firstname || amazonStorage.isGuestCheckoutEnabled || amazonStorage.isLwaEnabled) {
                    $(element).AmazonButton();
                }
            });
        };
    }
});
<|endoftext|>


<commit_before>define(["dojo/_base/declare", "dojo/_base/array", "dojo/_base/lang"],
	function(declare, arr, lang){
	return declare(null, {
		// summary:
		//		Mixin for classes for widgets that manage a list of selected data items. Receiving class must extend
		//		dui/_WidgetBase.

		constructor: function(){
			this.selectedItems = [];
		},

		// selectionMode: String
		//		Valid values are:
		//
		//		1. "none": No selection can be done.
		//		2. "single": Only one item can be selected at a time.
		//		3. "multiple": Several item can be selected using the control key modifier.
		//		Default value is "single".
		selectionMode: "single",

		_setSelectionModeAttr: function(value){
			if(value != "none" && value != "single" && value != "multiple"){
				throw new Error("selectionModel invalid value");
			}
			if(value != this.selectionMode){
				this.selectionMode = value;
				if(value == "none"){
					this.set("selectedItems", null);
				}else if(value == "single"){
					this.set("selectedItem", this.selectedItem); // null or last selected item
				}
			}
		},

		// selectedItem: Object
		//		In single selection mode, the selected item or in multiple selection mode the last selected item.
		//		Warning: Do not use this property directly, make sure to call set() or get() methods.
		selectedItem: null,

		_setSelectedItemAttr: function(value){
			if(this.selectedItem != value){
				this._set("selectedItem", value);
				this.set("selectedItems", value ? [value] : null);
			}
		},

		// selectedItems: Object[]
		//		The list of selected items.
		//		Warning: Do not use this property directly, make sure to call set() or get() methods.
		selectedItems: null,

		_setSelectedItemsAttr: function(value){
			var oldSelectedItems = this.selectedItems;

			this.selectedItems = value;
			this.selectedItem = null;

			if(oldSelectedItems != null && oldSelectedItems.length>0){
				this.updateRenderers(oldSelectedItems, true);
			}
			if(this.selectedItems && this.selectedItems.length>0){
				this.selectedItem = this.selectedItems[0];
				this.updateRenderers(this.selectedItems, true);
			}
		},

		_getSelectedItemsAttr: function(){
			return this.selectedItems == null ? [] : this.selectedItems.concat();
		},

		isSelected: function(item){
			// summary:
			//		Returns whether an item is selected or not.
			// item: Object
			//		The item to test the selection for.
			if(this.selectedItems == null || this.selectedItems.length== 0){
				return false;
			}

			return arr.some(this.selectedItems, lang.hitch(this, function(sitem){
				return this.getIdentity(sitem) == this.getIdentity(item);
			}));
		},

		getIdentity: function(item){
			// summary:
			//		This function must be implemented to return the id of a item.
			// item: Object
			//		The item to query the identity for.
		},

		setSelected: function(item, value){
			// summary:
			//		Change the selection state of an item.
			// item: Object
			//		The item to change the selection state for.
			// value: Boolean
			//		True to select the item, false to deselect it.

			if(this.selectionMode == "none" || item == null){
				return;
			}

			// copy is returned
			var sel = this.get("selectedItems");

			if(this.selectionMode == "single"){
				if(value){
					this.set("selectedItem", item);
				}else if(this.isItemSelected(item)){
					this.set("selectedItems", null);
				}
			}else{ // multiple
				if(value){
					if(this.isItemSelected(item)){
						return; // already selected
					}
					if(sel == null){
						sel = [item];
					}else{
						sel.unshift(item);
					}
					this.set("selectedItems", sel);
				}else{
					var res = arr.filter(sel, function(sitem){
						return sitem.id != item.id;
					});
					if(res == null || res.length == sel.length){
						return; // already not selected
					}
					this.set("selectedItems", res);
				}
			}
		},

		selectFromEvent: function(e, item, renderer, dispatch){
			// summary:
			//		Applies selection triggered by an user interaction
			// e: Event
			//		The source event of the user interaction.
			// item: Object
			//		The render item that has been selected/deselected.
			// renderer: Object
			//		The visual renderer of the selected/deselected item.
			// dispatch: Boolean
			//		Whether an event must be dispatched or not.
			// returns: Boolean
			//		Returns true if the selection has changed and false otherwise.
			// tags:
			//		protected

			if(this.selectionMode == "none"){
				return false;
			}

			var changed;
			var oldSelectedItem = this.get("selectedItem");
			var selected = item ? this.isItemSelected(item): false;

			if(item == null){
				if(!e.ctrlKey && this.selectedItem != null){
					this.set("selectedItem", null);
					changed = true;
				}
			}else if(this.selectionMode == "multiple"){
				 if(e.ctrlKey){
					this.setItemSelected(item, !selected);
					changed = true;
				}else{
					this.set("selectedItem", item);
					changed = true;
				}
			}else{ // single
				if(e.ctrlKey){
					//if the object is selected deselects it.
					this.set("selectedItem", selected ? null : item);
					changed = true;
				}else{
					if(!selected){
						this.set("selectedItem", item);
						changed = true;
					}
				}
			}

			if(dispatch && changed){
				this.dispatchSelectionChange(oldSelectedItem, this.get("selectedItem"), renderer, e);
			}

			return changed;
		},

		dispatchSelectionChange: function(oldSelectedItem, newSelectedItem, renderer, triggerEvent){
			// summary:
			//		Dispatch a selection change event.
			// oldSelectedItem: Object
			//		The previously selectedItem.
			// newSelectedItem: Object
			//		The new selectedItem.
			// renderer: Object
			//		The visual renderer of the selected/deselected item.
			// triggerEvent: Event
			//		The event that lead to the selection of the item.

			// we assume we are a WidgetBase and we have an emit method
			this.emit("selection-change", {
				oldValue: oldSelectedItem,
				newValue: newSelectedItem,
				renderer: renderer,
				triggerEvent: triggerEvent
			});
		}
	});
});
<commit_msg>rename isItemSelected -> isSelected rename setItemSelected -> setSelected use getIdentity for all comparisons<commit_after>define(["dojo/_base/declare", "dojo/_base/array", "dojo/_base/lang"],
	function(declare, arr, lang){
	return declare(null, {
		// summary:
		//		Mixin for classes for widgets that manage a list of selected data items. Receiving class must extend
		//		dui/_WidgetBase.

		constructor: function(){
			this.selectedItems = [];
		},

		// selectionMode: String
		//		Valid values are:
		//
		//		1. "none": No selection can be done.
		//		2. "single": Only one item can be selected at a time.
		//		3. "multiple": Several item can be selected using the control key modifier.
		//		Default value is "single".
		selectionMode: "single",

		_setSelectionModeAttr: function(value){
			if(value != "none" && value != "single" && value != "multiple"){
				throw new Error("selectionModel invalid value");
			}
			if(value != this.selectionMode){
				this.selectionMode = value;
				if(value == "none"){
					this.set("selectedItems", null);
				}else if(value == "single"){
					this.set("selectedItem", this.selectedItem); // null or last selected item
				}
			}
		},

		// selectedItem: Object
		//		In single selection mode, the selected item or in multiple selection mode the last selected item.
		//		Warning: Do not use this property directly, make sure to call set() or get() methods.
		selectedItem: null,

		_setSelectedItemAttr: function(value){
			if(this.selectedItem != value){
				this._set("selectedItem", value);
				this.set("selectedItems", value ? [value] : null);
			}
		},

		// selectedItems: Object[]
		//		The list of selected items.
		//		Warning: Do not use this property directly, make sure to call set() or get() methods.
		selectedItems: null,

		_setSelectedItemsAttr: function(value){
			var oldSelectedItems = this.selectedItems;

			this.selectedItems = value;
			this.selectedItem = null;

			if(oldSelectedItems != null && oldSelectedItems.length>0){
				this.updateRenderers(oldSelectedItems, true);
			}
			if(this.selectedItems && this.selectedItems.length>0){
				this.selectedItem = this.selectedItems[0];
				this.updateRenderers(this.selectedItems, true);
			}
		},

		_getSelectedItemsAttr: function(){
			return this.selectedItems == null ? [] : this.selectedItems.concat();
		},

		isSelected: function(item){
			// summary:
			//		Returns whether an item is selected or not.
			// item: Object
			//		The item to test the selection for.
			if(this.selectedItems == null || this.selectedItems.length== 0){
				return false;
			}

			return arr.some(this.selectedItems, lang.hitch(this, function(sitem){
				return this.getIdentity(sitem) == this.getIdentity(item);
			}));
		},

		getIdentity: function(item){
			// summary:
			//		This function must be implemented to return the id of a item.
			// item: Object
			//		The item to query the identity for.
		},

		setSelected: function(item, value){
			// summary:
			//		Change the selection state of an item.
			// item: Object
			//		The item to change the selection state for.
			// value: Boolean
			//		True to select the item, false to deselect it.

			if(this.selectionMode == "none" || item == null){
				return;
			}

			// copy is returned
			var sel = this.get("selectedItems");

			if(this.selectionMode == "single"){
				if(value){
					this.set("selectedItem", item);
				}else if(this.isSelected(item)){
					this.set("selectedItems", null);
				}
			}else{ // multiple
				if(value){
					if(this.isSelected(item)){
						return; // already selected
					}
					if(sel == null){
						sel = [item];
					}else{
						sel.unshift(item);
					}
					this.set("selectedItems", sel);
				}else{
					var res = arr.filter(sel, lang.hitch(this, function(sitem){
						return this.getIdentity(sitem) != this.getIdentity(item);
					}));
					if(res == null || res.length == sel.length){
						return; // already not selected
					}
					this.set("selectedItems", res);
				}
			}
		},

		selectFromEvent: function(e, item, renderer, dispatch){
			// summary:
			//		Applies selection triggered by an user interaction
			// e: Event
			//		The source event of the user interaction.
			// item: Object
			//		The render item that has been selected/deselected.
			// renderer: Object
			//		The visual renderer of the selected/deselected item.
			// dispatch: Boolean
			//		Whether an event must be dispatched or not.
			// returns: Boolean
			//		Returns true if the selection has changed and false otherwise.
			// tags:
			//		protected

			if(this.selectionMode == "none"){
				return false;
			}

			var changed;
			var oldSelectedItem = this.get("selectedItem");
			var selected = item ? this.isSelected(item): false;

			if(item == null){
				if(!e.ctrlKey && this.selectedItem != null){
					this.set("selectedItem", null);
					changed = true;
				}
			}else if(this.selectionMode == "multiple"){
				 if(e.ctrlKey){
					this.setSelected(item, !selected);
					changed = true;
				}else{
					this.set("selectedItem", item);
					changed = true;
				}
			}else{ // single
				if(e.ctrlKey){
					//if the object is selected deselects it.
					this.set("selectedItem", selected ? null : item);
					changed = true;
				}else{
					if(!selected){
						this.set("selectedItem", item);
						changed = true;
					}
				}
			}

			if(dispatch && changed){
				this.dispatchSelectionChange(oldSelectedItem, this.get("selectedItem"), renderer, e);
			}

			return changed;
		},

		dispatchSelectionChange: function(oldSelectedItem, newSelectedItem, renderer, triggerEvent){
			// summary:
			//		Dispatch a selection change event.
			// oldSelectedItem: Object
			//		The previously selectedItem.
			// newSelectedItem: Object
			//		The new selectedItem.
			// renderer: Object
			//		The visual renderer of the selected/deselected item.
			// triggerEvent: Event
			//		The event that lead to the selection of the item.

			// we assume we are a WidgetBase and we have an emit method
			this.emit("selection-change", {
				oldValue: oldSelectedItem,
				newValue: newSelectedItem,
				renderer: renderer,
				triggerEvent: triggerEvent
			});
		}
	});
});
<|endoftext|>


<commit_before>'use strict';

const http = require('http');
const gpio = require('pi-gpio');

//  Our times
const secTimer = sec(1);
const minTimer = min(15);

const pin = 16;

console.log('sec', secTimer, 'mainMin', minTimer);

function cycleOff () {
  gpio.open(pin, 'output', (err) => {
    gpio.write(pin, 0, () => {
      console.info(`pin ${pin} is off...`);
      gpio.close(pin);
    });
  });
};

function cycleOn () {
  gpio.open(pin, 'output', (err) => {
    gpio.write(pin, 1, () => {
      console.info(`pin ${pin} is on...`);
      gpio.close(pin);
    });
  });
};

const areYouAwake = () => {
  http.get('http://10.0.0.70', (res) => {
    const code = res.statusCode;

    res.setEncoding('utf8');

    if (code !== 200) {
      cycleOff();
      setTimeout(cycleOn, secTimer);
      return;
    }
    console.info(`Station currently up! ${new Date()}`)
  });
}
<commit_msg>self timed<commit_after>'use strict';

const http = require('http');
const gpio = require('pi-gpio');

//  Our times
const secTimer = sec(1);
const minTimer = min(15);

const pin = 16;

console.log('sec', secTimer, 'mainMin', minTimer);

function cycleOff () {
  gpio.open(pin, 'output', (err) => {
    gpio.write(pin, 0, () => {
      console.info(`pin ${pin} is off...`);
      gpio.close(pin);
    });
  });
};

function cycleOn () {
  gpio.open(pin, 'output', (err) => {
    gpio.write(pin, 1, () => {
      console.info(`pin ${pin} is on...`);
      gpio.close(pin);
    });
  });
};

const areYouAwake = () => {
  http.get('http://10.0.0.70', (res) => {
    const code = res.statusCode;

    res.setEncoding('utf8');

    if (code !== 200) {
      cycleOff();
      setTimeout(cycleOn, secTimer);
      return;
    }
    console.info(`Station currently up! ${new Date()}`)
  });
}

function timerCall () {
  setTimout(() => {
    areYouAwake();
    timerCall();
  }, minTimer)
};

timerCall();
<|endoftext|>


<commit_before>'use strict';

const http = require('http');
const gpio = require('rpi-gpio');

const pin = 27;

const stationIP = 'http://10.0.0.710';

const sec = 1;
const secTimer = sec * 1000;

console.log('++++++++++++++++++ My GPIO stuff ++++++++++++', gpio)
function cycleOff () {
  setTimeout(() => {
    gpio.write(pin, 0, cycleOn);
  }, secTimer);
};

function cycleOn () {
  setTimeout(() => {
    gpio.write(pin, 1, done);
  }, secTimer);
};

function done() {
  gpio.destroy();
  console.log(`Pin ${pin} destroyed...`);
}

const areYouAwake = () => {
  http.get(stationIP, (res) => {
    const code = res.statusCode;
    res.setEncoding('utf8');
    if (code === 200) {
      console.info(`Station up at ${new Date()}`);
      return;
    }
  }).on('error', (err) => {
    gpio.setup(pin, gpio.DIR_OUT, cycleOff);
    console.log(`reset station on ${pin} at ${new Date()}`);
  });
}

console.info(`checked at ${new Date()}`);
areYouAwake();
<commit_msg>wrong pin number!<commit_after>'use strict';

const http = require('http');
const gpio = require('rpi-gpio');

const pin = 4;

const stationIP = 'http://10.0.0.710';

const sec = 1;
const secTimer = sec * 1000;

console.log('++++++++++++++++++ My GPIO stuff ++++++++++++', gpio)
function cycleOff () {
  setTimeout(() => {
    gpio.write(pin, 0, cycleOn);
  }, secTimer);
};

function cycleOn () {
  setTimeout(() => {
    gpio.write(pin, 1, done);
  }, secTimer);
};

function done() {
  gpio.destroy();
  console.log(`Pin ${pin} destroyed...`);
}

const areYouAwake = () => {
  http.get(stationIP, (res) => {
    const code = res.statusCode;
    res.setEncoding('utf8');
    if (code === 200) {
      console.info(`Station up at ${new Date()}`);
      return;
    }
  }).on('error', (err) => {
    gpio.setup(pin, gpio.DIR_OUT, cycleOff);
    console.log(`reset station on ${pin} at ${new Date()}`);
  });
}

console.info(`checked at ${new Date()}`);
areYouAwake();
<|endoftext|>


<commit_before>var config = require('./config.js')
var http = require('http');
var postmark= require('./postmark');

function processRequest (url_conf, callback){

	var client = http.createClient(url_conf.host.port || 80, url_conf.host.host);
	var request = client.request(url_conf.method, url_conf.url, {'Host': url_conf.host.host });

	client.addListener('error', function(connectionException){
		callback(connectionException.errno, null, null);
	});

	request.on('response', function(response) {
		response.setEncoding('utf-8');
		var body = '';

		response.on('data', function(chunk) {
			body += chunk;
		});

		response.on('end', function() {
			callback(null, body, response)
		});

		response.on('error', function(e) {
			callback(e.message, null, null)
		});
	});

	request.on('error', function(e) {
		callback(e.message, null, null)
	});

	request.write("");
	request.end();
}

function sendEmail (to, subject, body){
	postmark.sendEmail(
	{
		'From' : config.postmark.From,
		'To': to,
		'Subject': subject,
		'TextBody': body }, config.postmark.Api_key, function(err, data) {

		if (err) {
			console.log('Error sending email: ' + JSON.stringify(err))
		} else {
			console.log('Email sent successfully')
		}
	})
}

function query_url(url_conf){
	var host_conf = url_conf.host

	processRequest(url_conf, function(err, body, response){
		var error, next_attempt_secs
		if (!err){
			if (response.statusCode != url_conf.expected.statuscode){
				error = 'we expected status code ' + url_conf.expected.statuscode + ' at ' + url_conf.url + ' but got ' + response.statusCode
			}
			else if (url_conf.expected.contains && body.indexOf(url_conf.expected.contains)==-1){
				error = 'expected text ' + url_conf.expected.contains + ' but no found '
			}
		}
		else
			error = 'Connection error: ' + err

		console.log (host_conf.host + ':'+ host_conf.port + ', url: "' +  url_conf.url + '" : ' + (error || ' response ok!'))

		if (error){
			if (config.notifications.Enabled){
				sendEmail(
					host_conf.alert_to || config.notifications.To,
					host_conf.host + ':'+ host_conf.port  + url_conf.url + ' is down!',
					error);
			}else{
				console.log ('Notification disabled');
			}

			if (!url_conf.attempts)
				url_conf.attempts = 1
			else if (url_conf.attempts < (url_conf.retry_in || url_conf.host.retry_in).length-1){
				url_conf.attempts++;
			}

			next_attempt_secs = (url_conf.retry_in || url_conf.host.retry_in) [url_conf.attempts] * 60;
			console.log ('Site down! Retrying in ' + next_attempt_secs / 60 + ' minutes..' )
		}
		else { //site up. queue next ping
			next_attempt_secs = (url_conf.ping_interval || url_conf.host.ping_interval);
			if (url_conf.attempts){
				//site is up again!
				url_conf.attempts = 0
				sendEmail(
					host_conf.alert_to || config.notifications.To,
					host_conf.host + ':'+ host_conf.port  + url_conf.url + ' is back up!',
					'site is up again!');
			}
		}

		setTimeout (query_url, next_attempt_secs * 1000, url_conf);
	})
}

/*main*/

console.log ('Starting Watchmen...')

for (var i=0; i<config.hosts.length;i++){
	var host = config.hosts[i];
	if (host.enabled){
		console.log ('Monitoring ' + host.name + ':')
		for (var u=0;u<config.hosts[i].urls.length;u++){
			var url = host.urls[u]
			url.host = host
			var ping = (url.ping_interval || host.ping_interval)
			console.log (' -- ping "' + url.url + '" every ' + ping + ' seconds...')
			setTimeout (query_url, ping * 1000, url);
		}
	}
	else{
		console.log ('Skipping ' + host.name + '...')
	}
}<commit_msg>Added notify_after_failed_ping property<commit_after>var config = require('./config.js')
var http = require('http');
var postmark= require('./postmark');

function processRequest (url_conf, callback){

	var client = http.createClient(url_conf.host.port || 80, url_conf.host.host);
	var request = client.request(url_conf.method, url_conf.url, {'Host': url_conf.host.host });

	client.addListener('error', function(connectionException){
		callback(connectionException.errno, null, null);
	});

	request.on('response', function(response) {
		response.setEncoding('utf-8');
		var body = '';

		response.on('data', function(chunk) {
			body += chunk;
		});

		response.on('end', function() {
			callback(null, body, response)
		});

		response.on('error', function(e) {
			callback(e.message, null, null)
		});
	});

	request.on('error', function(e) {
		callback(e.message, null, null)
	});

	request.write("");
	request.end();
}

function sendEmail (to, subject, body){
	postmark.sendEmail(
	{
		'From' : config.postmark.From,
		'To': to,
		'Subject': subject,
		'TextBody': body }, config.postmark.Api_key, function(err, data) {

		if (err) {
			console.log('Error sending email: ' + JSON.stringify(err))
		} else {
			console.log('Email sent successfully')
		}
	})
}

function query_url(url_conf){
	var host_conf = url_conf.host

	processRequest(url_conf, function(err, body, response){
		var error, next_attempt_secs
		if (!err){
			if (response.statusCode != url_conf.expected.statuscode){
				error = 'we expected status code ' + url_conf.expected.statuscode + ' at ' + url_conf.url + ' but got ' + response.statusCode
			}
			else if (url_conf.expected.contains && body.indexOf(url_conf.expected.contains)==-1){
				error = 'expected text ' + url_conf.expected.contains + ' but no found '
			}
		}
		else
			error = 'Connection error: ' + err

		console.log (host_conf.host + ':'+ host_conf.port + ', url: "' +  url_conf.url + '" : ' + (error || ' response ok!'))

		if (error){
			if (url_conf.attempts==undefined)
				url_conf.attempts = 0
			else if (url_conf.attempts < (url_conf.retry_in || url_conf.host.retry_in).length-1){
				url_conf.attempts++;
			}

			if ((url_conf.attempts >= (url_conf.notify_after_failed_ping || host_conf.notify_after_failed_ping)) && config.notifications.Enabled){
				sendEmail(
					host_conf.alert_to || config.notifications.To,
					host_conf.host + ':'+ host_conf.port  + url_conf.url + ' is down!',
					error);
			} else{
				console.log ('Notification disabled or not triggered this time');
			}

			next_attempt_secs = (url_conf.retry_in || url_conf.host.retry_in) [url_conf.attempts] * 60;
			console.log ('Site down! Retrying in ' + next_attempt_secs / 60 + ' minutes..' )
		}
		else { //site up. queue next ping
			next_attempt_secs = (url_conf.ping_interval || url_conf.host.ping_interval);
			if (url_conf.attempts){
				//site is up again!
				url_conf.attempts = 0
				sendEmail(
					host_conf.alert_to || config.notifications.To,
					host_conf.host + ':'+ host_conf.port  + url_conf.url + ' is back up!',
					'site is up again!');
			}
		}

		setTimeout (query_url, next_attempt_secs * 1000, url_conf);
	})
}

/*main*/

console.log ('Starting Watchmen...')

for (var i=0; i<config.hosts.length;i++){
	var host = config.hosts[i];
	if (host.enabled){
		console.log ('Monitoring ' + host.name + ':')
		for (var u=0;u<config.hosts[i].urls.length;u++){
			var url = host.urls[u]
			url.host = host
			var ping = (url.ping_interval || host.ping_interval)
			console.log (' -- ping "' + url.url + '" every ' + ping + ' seconds...')
			setTimeout (query_url, ping * 1000, url);
		}
	}
	else{
		console.log ('Skipping ' + host.name + '...')
	}
}<|endoftext|>


<commit_before>var CM = require('codemirror');
var React = require('react');
var className = require('classnames');

var CodeMirror = React.createClass({

	propTypes: {
		onChange: React.PropTypes.func,
		onFocusChange: React.PropTypes.func,
		options: React.PropTypes.object,
		path: React.PropTypes.string,
		value: React.PropTypes.string,
		className: React.PropTypes.any,
	},

	getInitialState () {
		return {
			isFocused: false
		};
	},

	componentDidMount () {
		var textareaNode = this.refs.textarea;
		this.codeMirror = CM.fromTextArea(textareaNode, this.props.options);
		this.codeMirror.on('change', this.codemirrorValueChanged);
		this.codeMirror.on('focus', this.focusChanged.bind(this, true));
		this.codeMirror.on('blur', this.focusChanged.bind(this, false));
		this._currentCodemirrorValue = this.props.value;
		this.codeMirror.setValue(this.props.value);
	},

	componentWillUnmount () {
		// todo: is there a lighter-weight way to remove the cm instance?
		if (this.codeMirror) {
			this.codeMirror.toTextArea();
		}
	},

	componentWillReceiveProps (nextProps) {
		if (this.codeMirror && this._currentCodemirrorValue !== nextProps.value) {
			this.codeMirror.setValue(nextProps.value);
		}
		if (typeof nextProps.options === 'object') {
			for (var optionName in nextProps.options) {
				if (nextProps.options.hasOwnProperty(optionName)) {
					this.codeMirror.setOption(optionName, nextProps.options[optionName]);
				}
			}
		}
	},

	getCodeMirror () {
		return this.codeMirror;
	},

	focus () {
		if (this.codeMirror) {
			this.codeMirror.focus();
		}
	},

	focusChanged (focused) {
		this.setState({
			isFocused: focused
		});
		this.props.onFocusChange && this.props.onFocusChange(focused);
	},

	codemirrorValueChanged (doc, change) {
		var newValue = doc.getValue();
		this._currentCodemirrorValue = newValue;
		this.props.onChange && this.props.onChange(newValue);
	},

	render () {
		var editorClassName = className(
			'ReactCodeMirror',
			this.state.isFocused ? 'ReactCodeMirror--focused' : null,
			this.props.className
		);

		return (

				<textarea ref="textarea" name={this.props.path} defaultValue={''} autoComplete="off" />

		);
	}

});

module.exports = CodeMirror;
<commit_msg>Add defaultValue option.<commit_after>var CM = require('codemirror');
var React = require('react');
var className = require('classnames');

var CodeMirror = React.createClass({

	propTypes: {
		onChange: React.PropTypes.func,
		onFocusChange: React.PropTypes.func,
		options: React.PropTypes.object,
		path: React.PropTypes.string,
		value: React.PropTypes.string,
		className: React.PropTypes.any,
	},

	getInitialState () {
		return {
			isFocused: false
		};
	},

	componentDidMount () {
		var textareaNode = this.refs.textarea;
		this.codeMirror = CM.fromTextArea(textareaNode, this.props.options);
		this.codeMirror.on('change', this.codemirrorValueChanged);
		this.codeMirror.on('focus', this.focusChanged.bind(this, true));
		this.codeMirror.on('blur', this.focusChanged.bind(this, false));
		this._currentCodemirrorValue = this.props.defaultValue || this.props.value || '';
		this.codeMirror.setValue(this._currentCodemirrorValue);
	},

	componentWillUnmount () {
		// todo: is there a lighter-weight way to remove the cm instance?
		if (this.codeMirror) {
			this.codeMirror.toTextArea();
		}
	},

	componentWillReceiveProps (nextProps) {
		if (this.codeMirror && nextProps.value !== undefined && this._currentCodemirrorValue !== nextProps.value) {
			this.codeMirror.setValue(nextProps.value);
		}
		if (typeof nextProps.options === 'object') {
			for (var optionName in nextProps.options) {
				if (nextProps.options.hasOwnProperty(optionName)) {
					this.codeMirror.setOption(optionName, nextProps.options[optionName]);
				}
			}
		}
	},

	getCodeMirror () {
		return this.codeMirror;
	},

	focus () {
		if (this.codeMirror) {
			this.codeMirror.focus();
		}
	},

	focusChanged (focused) {
		this.setState({
			isFocused: focused
		});
		this.props.onFocusChange && this.props.onFocusChange(focused);
	},

	codemirrorValueChanged (doc, change) {
		var newValue = doc.getValue();
		this._currentCodemirrorValue = newValue;
		this.props.onChange && this.props.onChange(newValue);
	},

	render () {
		var editorClassName = className(
			'ReactCodeMirror',
			this.state.isFocused ? 'ReactCodeMirror--focused' : null,
			this.props.className
		);

		return (

				<textarea ref="textarea" name={this.props.path} defaultValue={''} autoComplete="off" />

		);
	}

});

module.exports = CodeMirror;
<|endoftext|>


<commit_before>/**
 * @fileOverview Contains definition of the core graph object.
 *
 * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com
 */

/*global Viva*/

/**
 * @namespace Represents a graph data structure.
 *
 * @example
 *  var g = Viva.Graph.graph();
 *  g.addNode(1);     // g has one node.
 *  g.addLink(2, 3);  // now g contains three nodes and one link.
 *
 */
Viva.Graph.graph = function() {

    // Graph structure is maintained as dictionary of nodes
    // and array of links. Each node has 'links' property which
    // hold all links related to that node. And general links
    // array is used to speed up all links enumeration. This is inefficient
    // in terms of memory, but simplifies coding. Furthermore, the graph structure
    // is isolated from outter world, and can be changed to adjacency matrix later.

    var nodes = {},
        links = [],
        nodesCount = 0,
        suspendEvents = 0,

        // Accumlates all changes made during graph updates.
        // Each change element contains:
        //  changeType - one of the strings: 'add', 'remove' or 'update';
        //  node - if change is related to node this property is set to changed graph's node;
        //  link - if change is related to link this property is set to changed graph's link;
        changes = [],

        fireGraphChanged = function(graph){
            // TODO: maybe we shall copy changes?
            graph.fire('changed', changes);
        },

        // Enter, Exit Mofidication allows bulk graph updates without firing events.
        enterModification = function(graph){
            suspendEvents += 1;
        },

        exitModification = function(graph){
            suspendEvents -= 1;
            if (suspendEvents === 0 && changes.length > 0){
                fireGraphChanged(graph);
                changes.length = 0;
            }
        },

        recordNodeChange = function(node, changeType){
            // TODO: Could add changeType verification.
            changes.push( {node : node, changeType : changeType} );
        },

        recordLinkChange = function(link, changeType){
            // TODO: Could add change type verification;
            changes.push( {link : link, changeType : changeType} );
        },

        isArray = function (value) {
            return value &&
                   typeof value === 'object' &&
                   typeof value.length === 'number' &&
                   typeof value.splice === 'function' &&
                   !(value.propertyIsEnumerable('length'));
        };

    /** @scope Viva.Graph.graph */
    var graphPart = {

        /**
         * Adds node to the graph. If node with given id already exists in the graph
         * its data is extended with whatever comes in 'data' argument.
         *
         * @param nodeId the node's identifier. A string is preferred.
         * @param [data] additional data for the node being added. If node already
         *   exists its data object is augmented with the new one.
         *
         * @return {node} The newly added node or node with given id if it already exists.
         */
        addNode : function(nodeId, data) {
            if( typeof nodeId === 'undefined') {
                throw {
                    message: 'Invalid node identifier'
                };
            }

            enterModification();

            var node = this.getNode(nodeId);
            if(!node) {
                node = {};
                node.links = [];
                node.id = nodeId;
                nodesCount++;

                recordNodeChange(node, 'add');
            } else {
                recordNodeChange(node, 'update');
            }

            if(data) {
                var augmentedData = node.data || {},
                    dataType = typeof data;

                if (dataType === 'string' || isArray(data) ||
                    dataType === 'number' || dataType === 'boolean') {
                    augmentedData = data;
                } else if (dataType === 'undefined') {
                    augmentedData = null;
                } else {
                    for(var name in data) {
                        // TODO: Do we want to copy everything, including prototype's properties?
                        if (data.hasOwnProperty(name)){
                            augmentedData[name] = data[name];
                        }
                    }
                }

                node.data = augmentedData;
            }

            nodes[nodeId] = node;

            exitModification(this);
            return node;
        },

        /**
         * Adds a link to the graph. The function always create a new
         * link between two nodes. If one of the nodes does not exists
         * a new node is created.
         *
         * @param fromId link start node id;
         * @param toId link end node id;
         * @param [data] additional data to be set on the new link;
         *
         * @return {link} The newly created link
         */
        addLink : function(fromId, toId, data) {
            enterModification();

            var fromNode = this.getNode(fromId) || this.addNode(fromId);
            var toNode = this.getNode(toId) || this.addNode(toId);

            var link = {
                fromId : fromId,
                toId : toId,
                data : data
            };

            links.push(link);

            // TODO: this is not cool. On large graphs potentially would consume more memory.
            fromNode.links.push(link);
            toNode.links.push(link);

            recordLinkChange(link, 'add');

            exitModification(this);

            return link;
        },

        /**
         * Removes link from the graph. If link does not exist does nothing.
         *
         * @param link - object returned by addLink() or getLinks() methods.
         *
         * @returns true if link was removed; false otherwise.
         */
        removeLink : function(link) {
            if (!link) { return false; }
            var idx = Viva.Graph.Utils.indexOfElementInArray(link, links);
            if (idx < 0) { return false; }

            enterModification();

            links.splice(idx, 1);

            var fromNode = this.getNode(link.fromId);
            var toNode = this.getNode(link.toId);

            if (fromNode) {
                idx = Viva.Graph.Utils.indexOfElementInArray(link, fromNode.links);
                if (idx >= 0) {
                    fromNode.links.splice(idx, 1);
                }
            }

            if (toNode) {
                idx = Viva.Graph.Utils.indexOfElementInArray(link, toNode.links);
                if (idx >= 0) {
                    toNode.links.splice(idx, 1);
                }
            }

            recordLinkChange(link, 'remove');

            exitModification(this);

            return true;
        },

        /**
         * Removes node with given id from the graph. If node does not exist in the graph
         * does nothing.
         *
         * @param nodeId node's identifier passed to addNode() function.
         *
         * @returns true if node was removed; false otherwise.
         */
        removeNode: function(nodeId) {
            var node = this.getNode(nodeId);
            if (!node) { return false; }

            enterModification();

            while(node.links.length){
                var link = node.links[0];
                this.removeLink(link);
            }

            nodes[nodeId] = null;
            delete nodes[nodeId];
            nodesCount--;

            recordNodeChange(node, 'remove');

            exitModification(this);
        },

        /**
         * Gets node with given identifier. If node does not exist undefined value is returned.
         *
         * @param nodeId requested node identifier;
         *
         * @return {node} in with requested identifier or undefined if no such node exists.
         */
        getNode : function(nodeId) {
            return nodes[nodeId];
        },

        /**
         * Gets number of nodes in this graph.
         *
         * @return number of nodes in the graph.
         */
        getNodesCount : function() {
            return nodesCount;
        },

        /**
         * Gets total number of links in the graph.
         */
        getLinksCount : function() {
            return links.length;
        },

        /**
         * Gets all links (inbound and outbound) from the node with given id.
         * If node with given id is not found null is returned.
         *
         * @param nodeId requested node identifier.
         *
         * @return Array of links from and to requested node if such node exists;
         *   otherwise null is returned.
         */
        getLinks : function(nodeId) {
            var node = this.getNode(nodeId);
            return node ? node.links : null;
        },

        /**
         * Invokes callback on each node of the graph.
         *
         * @param {Function(node)} callback Function to be invoked. The function
         *   is passed one argument: visited node.
         */
        forEachNode : function(callback) {
            if( typeof callback !== 'function') {
                return;
            }

            // TODO: could it be faster for nodes iteration if we had indexed access?
            // I.e. use array + 'for' iterator instead of dictionary + 'for .. in'?
            for(var node in nodes) {
                // For performance reasons you might want to sacrifice this sanity check:
                if(nodes.hasOwnProperty(node)) {
                    callback(nodes[node]);
                }
            }
        },

        /**
         * Invokes callback on every linked (adjacent) node to the given one.
         *
         * @param nodeId Identifier of the requested node.
         * @param {Function(node, link)} callback Function to be called on all linked nodes.
         *   The function is passed two parameters: adjacent node and link object itself.
         * @param oriented if true graph treated as oriented.
         */
        forEachLinkedNode : function(nodeId, callback, oriented) {
            var node = this.getNode(nodeId),
                i, link, linkedNodeId;
            if(node && node.links && typeof callback === 'function') {
                // Extraced orientation check out of the loop to increase performance
                if (oriented){
                    for(i = 0; i < node.links.length; ++i) {
                        link = node.links[i];
                        if (link.fromId === nodeId){
                            callback(nodes[link.toId], link);
                        }
                    }
                } else {
                    for(i = 0; i < node.links.length; ++i) {
                        link = node.links[i];
                        linkedNodeId = link.fromId === nodeId ? link.toId : link.fromId;

                        callback(nodes[linkedNodeId], link);
                    }
                }
            }
        },

        /**
         * Enumerates all links in the graph
         *
         * @param {Function(link)} callback Function to be called on all links in the graph.
         *   The function is passed one parameter: graph's link object.
         *
         * Link object contains at least the following fields:
         *  fromId - node id where link starts;
         *  toId - node id where link ends,
         *  data - additional data passed to graph.addLink() method.
         */
        forEachLink : function(callback) {
            if( typeof callback === 'function') {
                for(var i = 0; i < links.length; ++i) {
                    callback(links[i]);
                }
            }
        },

        /**
         * Suspend all notifications about graph changes until
         * endUpdate is called.
         */
        beginUpdate : function() {
            enterModification();
        },

        /**
         * Resumes all notifications about graph changes and fires
         * graph 'changed' event in case there are any pending changes.
         */
        endUpdate : function() {
            exitModification(this);
        },

        /**
         * Removes all nodes and links from the graph.
         */
        clear : function(){
            var that = this;
            that.beginUpdate();
            that.forEachNode(function(node){ that.removeNode(node.id); });
            that.endUpdate();
        },

        /**
         * Detects whether there is a link between two nodes.
         * Operation complexity is O(n) where n - number of links of a node.
         *
         * @returns link if there is one. null otherwise.
         */
        hasLink : function(fromNodeId, toNodeId) {
            // TODO: Use adjacency matrix to speed up this operation.
            var node = this.getNode(fromNodeId);
            if (!node) {
                return null;
            }

            for (var i = 0; i < node.links.length; ++i) {
                var link = node.links[i];
                if (link.fromId === fromNodeId && link.toId === toNodeId) {
                    return link;
                }
            }

            return null; // no link.
        }
    };

    // Let graph fire events before we return it to the caller.
    Viva.Graph.Utils.events(graphPart).extend();

    return graphPart;
};
<commit_msg>Now clients can opt out from further iteration other graph nodes.<commit_after>/**
 * @fileOverview Contains definition of the core graph object.
 *
 * @author Andrei Kashcha (aka anvaka) / http://anvaka.blogspot.com
 */

/*global Viva*/

/**
 * @namespace Represents a graph data structure.
 *
 * @example
 *  var g = Viva.Graph.graph();
 *  g.addNode(1);     // g has one node.
 *  g.addLink(2, 3);  // now g contains three nodes and one link.
 *
 */
Viva.Graph.graph = function() {

    // Graph structure is maintained as dictionary of nodes
    // and array of links. Each node has 'links' property which
    // hold all links related to that node. And general links
    // array is used to speed up all links enumeration. This is inefficient
    // in terms of memory, but simplifies coding. Furthermore, the graph structure
    // is isolated from outter world, and can be changed to adjacency matrix later.

    var nodes = {},
        links = [],
        nodesCount = 0,
        suspendEvents = 0,

        // Accumlates all changes made during graph updates.
        // Each change element contains:
        //  changeType - one of the strings: 'add', 'remove' or 'update';
        //  node - if change is related to node this property is set to changed graph's node;
        //  link - if change is related to link this property is set to changed graph's link;
        changes = [],

        fireGraphChanged = function(graph){
            // TODO: maybe we shall copy changes?
            graph.fire('changed', changes);
        },

        // Enter, Exit Mofidication allows bulk graph updates without firing events.
        enterModification = function(graph){
            suspendEvents += 1;
        },

        exitModification = function(graph){
            suspendEvents -= 1;
            if (suspendEvents === 0 && changes.length > 0){
                fireGraphChanged(graph);
                changes.length = 0;
            }
        },

        recordNodeChange = function(node, changeType){
            // TODO: Could add changeType verification.
            changes.push( {node : node, changeType : changeType} );
        },

        recordLinkChange = function(link, changeType){
            // TODO: Could add change type verification;
            changes.push( {link : link, changeType : changeType} );
        },

        isArray = function (value) {
            return value &&
                   typeof value === 'object' &&
                   typeof value.length === 'number' &&
                   typeof value.splice === 'function' &&
                   !(value.propertyIsEnumerable('length'));
        };

    /** @scope Viva.Graph.graph */
    var graphPart = {

        /**
         * Adds node to the graph. If node with given id already exists in the graph
         * its data is extended with whatever comes in 'data' argument.
         *
         * @param nodeId the node's identifier. A string is preferred.
         * @param [data] additional data for the node being added. If node already
         *   exists its data object is augmented with the new one.
         *
         * @return {node} The newly added node or node with given id if it already exists.
         */
        addNode : function(nodeId, data) {
            if( typeof nodeId === 'undefined') {
                throw {
                    message: 'Invalid node identifier'
                };
            }

            enterModification();

            var node = this.getNode(nodeId);
            if(!node) {
                node = {};
                node.links = [];
                node.id = nodeId;
                nodesCount++;

                recordNodeChange(node, 'add');
            } else {
                recordNodeChange(node, 'update');
            }

            if(data) {
                var augmentedData = node.data || {},
                    dataType = typeof data;

                if (dataType === 'string' || isArray(data) ||
                    dataType === 'number' || dataType === 'boolean') {
                    augmentedData = data;
                } else if (dataType === 'undefined') {
                    augmentedData = null;
                } else {
                    for(var name in data) {
                        // TODO: Do we want to copy everything, including prototype's properties?
                        if (data.hasOwnProperty(name)){
                            augmentedData[name] = data[name];
                        }
                    }
                }

                node.data = augmentedData;
            }

            nodes[nodeId] = node;

            exitModification(this);
            return node;
        },

        /**
         * Adds a link to the graph. The function always create a new
         * link between two nodes. If one of the nodes does not exists
         * a new node is created.
         *
         * @param fromId link start node id;
         * @param toId link end node id;
         * @param [data] additional data to be set on the new link;
         *
         * @return {link} The newly created link
         */
        addLink : function(fromId, toId, data) {
            enterModification();

            var fromNode = this.getNode(fromId) || this.addNode(fromId);
            var toNode = this.getNode(toId) || this.addNode(toId);

            var link = {
                fromId : fromId,
                toId : toId,
                data : data
            };

            links.push(link);

            // TODO: this is not cool. On large graphs potentially would consume more memory.
            fromNode.links.push(link);
            toNode.links.push(link);

            recordLinkChange(link, 'add');

            exitModification(this);

            return link;
        },

        /**
         * Removes link from the graph. If link does not exist does nothing.
         *
         * @param link - object returned by addLink() or getLinks() methods.
         *
         * @returns true if link was removed; false otherwise.
         */
        removeLink : function(link) {
            if (!link) { return false; }
            var idx = Viva.Graph.Utils.indexOfElementInArray(link, links);
            if (idx < 0) { return false; }

            enterModification();

            links.splice(idx, 1);

            var fromNode = this.getNode(link.fromId);
            var toNode = this.getNode(link.toId);

            if (fromNode) {
                idx = Viva.Graph.Utils.indexOfElementInArray(link, fromNode.links);
                if (idx >= 0) {
                    fromNode.links.splice(idx, 1);
                }
            }

            if (toNode) {
                idx = Viva.Graph.Utils.indexOfElementInArray(link, toNode.links);
                if (idx >= 0) {
                    toNode.links.splice(idx, 1);
                }
            }

            recordLinkChange(link, 'remove');

            exitModification(this);

            return true;
        },

        /**
         * Removes node with given id from the graph. If node does not exist in the graph
         * does nothing.
         *
         * @param nodeId node's identifier passed to addNode() function.
         *
         * @returns true if node was removed; false otherwise.
         */
        removeNode: function(nodeId) {
            var node = this.getNode(nodeId);
            if (!node) { return false; }

            enterModification();

            while(node.links.length){
                var link = node.links[0];
                this.removeLink(link);
            }

            nodes[nodeId] = null;
            delete nodes[nodeId];
            nodesCount--;

            recordNodeChange(node, 'remove');

            exitModification(this);
        },

        /**
         * Gets node with given identifier. If node does not exist undefined value is returned.
         *
         * @param nodeId requested node identifier;
         *
         * @return {node} in with requested identifier or undefined if no such node exists.
         */
        getNode : function(nodeId) {
            return nodes[nodeId];
        },

        /**
         * Gets number of nodes in this graph.
         *
         * @return number of nodes in the graph.
         */
        getNodesCount : function() {
            return nodesCount;
        },

        /**
         * Gets total number of links in the graph.
         */
        getLinksCount : function() {
            return links.length;
        },

        /**
         * Gets all links (inbound and outbound) from the node with given id.
         * If node with given id is not found null is returned.
         *
         * @param nodeId requested node identifier.
         *
         * @return Array of links from and to requested node if such node exists;
         *   otherwise null is returned.
         */
        getLinks : function(nodeId) {
            var node = this.getNode(nodeId);
            return node ? node.links : null;
        },

        /**
         * Invokes callback on each node of the graph.
         *
         * @param {Function(node)} callback Function to be invoked. The function
         *   is passed one argument: visited node.
         */
        forEachNode : function(callback) {
            if( typeof callback !== 'function') {
                return;
            }

            // TODO: could it be faster for nodes iteration if we had indexed access?
            // I.e. use array + 'for' iterator instead of dictionary + 'for .. in'?
            for(var node in nodes) {
                // For performance reasons you might want to sacrifice this sanity check:
                if(nodes.hasOwnProperty(node)) {
                    if (callback(nodes[node])) {
                        return; // client doesn't want to proceed. return.
                    }
                }
            }
        },

        /**
         * Invokes callback on every linked (adjacent) node to the given one.
         *
         * @param nodeId Identifier of the requested node.
         * @param {Function(node, link)} callback Function to be called on all linked nodes.
         *   The function is passed two parameters: adjacent node and link object itself.
         * @param oriented if true graph treated as oriented.
         */
        forEachLinkedNode : function(nodeId, callback, oriented) {
            var node = this.getNode(nodeId),
                i, link, linkedNodeId;
            if(node && node.links && typeof callback === 'function') {
                // Extraced orientation check out of the loop to increase performance
                if (oriented){
                    for(i = 0; i < node.links.length; ++i) {
                        link = node.links[i];
                        if (link.fromId === nodeId){
                            callback(nodes[link.toId], link);
                        }
                    }
                } else {
                    for(i = 0; i < node.links.length; ++i) {
                        link = node.links[i];
                        linkedNodeId = link.fromId === nodeId ? link.toId : link.fromId;

                        callback(nodes[linkedNodeId], link);
                    }
                }
            }
        },

        /**
         * Enumerates all links in the graph
         *
         * @param {Function(link)} callback Function to be called on all links in the graph.
         *   The function is passed one parameter: graph's link object.
         *
         * Link object contains at least the following fields:
         *  fromId - node id where link starts;
         *  toId - node id where link ends,
         *  data - additional data passed to graph.addLink() method.
         */
        forEachLink : function(callback) {
            if( typeof callback === 'function') {
                for(var i = 0; i < links.length; ++i) {
                    callback(links[i]);
                }
            }
        },

        /**
         * Suspend all notifications about graph changes until
         * endUpdate is called.
         */
        beginUpdate : function() {
            enterModification();
        },

        /**
         * Resumes all notifications about graph changes and fires
         * graph 'changed' event in case there are any pending changes.
         */
        endUpdate : function() {
            exitModification(this);
        },

        /**
         * Removes all nodes and links from the graph.
         */
        clear : function(){
            var that = this;
            that.beginUpdate();
            that.forEachNode(function(node){ that.removeNode(node.id); });
            that.endUpdate();
        },

        /**
         * Detects whether there is a link between two nodes.
         * Operation complexity is O(n) where n - number of links of a node.
         *
         * @returns link if there is one. null otherwise.
         */
        hasLink : function(fromNodeId, toNodeId) {
            // TODO: Use adjacency matrix to speed up this operation.
            var node = this.getNode(fromNodeId);
            if (!node) {
                return null;
            }

            for (var i = 0; i < node.links.length; ++i) {
                var link = node.links[i];
                if (link.fromId === fromNodeId && link.toId === toNodeId) {
                    return link;
                }
            }

            return null; // no link.
        }
    };

    // Let graph fire events before we return it to the caller.
    Viva.Graph.Utils.events(graphPart).extend();

    return graphPart;
};
<|endoftext|>