<commit_before>/* global describe, it */

var chai = require('chai');
var expect = require('chai').expect;
var sinon = require('sinon');
var factory = require('../../../../../app/http/workflow/authorize/transition/login');

describe('http/workflow/authorize/transition/login', function() {

  it('should export factory function', function() {
    expect(factory).to.be.a('function');
  });

  describe('handler', function() {

    describe.only('transitioning from password login', function() {
      var request, response, error;

      before(function(done) {
        var handler = factory();

        chai.express.handler(handler)
          .req(function(req) {
            request = req;

            req.state = {
              name: 'oauth2-authorize',
              client: 's6BhdRkqt3',
              redirectURI: 'https://client.example.com/cb',
              req: {
                clientID: 's6BhdRkqt3',
                redirectURI: 'https://client.example.com/cb',
                type: 'code',
                scope: [ 'openid', 'profile', 'email' ]
              },
              handle: 'af0ifjsldkj'
            };
            req.authInfo = { method: 'password' };
          })
          .next(function(res) {
            done();
          })
          .dispatch();
      });

      it('should update authentication contenxt', function() {
        expect(request.state.authN).to.deep.equal({
          verified: [ {
            method: 'password'
          } ]
        });
      });

    }); // default behavior

  }); // handler

});
<commit_msg>Remove only from test suite.<commit_after>/* global describe, it */

var chai = require('chai');
var expect = require('chai').expect;
var sinon = require('sinon');
var factory = require('../../../../../app/http/workflow/authorize/transition/login');

describe('http/workflow/authorize/transition/login', function() {

  it('should export factory function', function() {
    expect(factory).to.be.a('function');
  });

  describe('handler', function() {

    describe('transitioning from password login', function() {
      var request, response, error;

      before(function(done) {
        var handler = factory();

        chai.express.handler(handler)
          .req(function(req) {
            request = req;

            req.state = {
              name: 'oauth2-authorize',
              client: 's6BhdRkqt3',
              redirectURI: 'https://client.example.com/cb',
              req: {
                clientID: 's6BhdRkqt3',
                redirectURI: 'https://client.example.com/cb',
                type: 'code',
                scope: [ 'openid', 'profile', 'email' ]
              },
              handle: 'af0ifjsldkj'
            };
            req.authInfo = { method: 'password' };
          })
          .next(function(res) {
            done();
          })
          .dispatch();
      });

      it('should update authentication contenxt', function() {
        expect(request.state.authN).to.deep.equal({
          verified: [ {
            method: 'password'
          } ]
        });
      });
    }); // default behavior

  }); // handler

});
<|endoftext|>


<commit_before>define([
    'underscore',
    'backbone',
    'text!templates/navbar.html'
], function (_, Backbone, navbarTemplate) {

    var NAVBAR_TEMPLATE = _.template(navbarTemplate);
    var SEARCH_MAX_ITEM_PER_PAGE = 20;

    var IndexView = Backbone.View.extend({

        initialize: function () {
        },

        render: function () {
            this.$el.html(NAVBAR_TEMPLATE());
            return this;
        },

        events: {
            'submit form': 'submit',
        },

        submit: function (event) {
            event.preventDefault();
            var name = this.$('input[name="search-text"]').val();
            name = name.trim().split(' ').join('+');

            if (name === "") {
                return;
            }

            // Go to the research page
            var searchUrl = [
                '#documents?search=', name,
                '&order=asc&limit=', SEARCH_MAX_ITEM_PER_PAGE,
                '&offset=0'
            ].join('');
            finalApp.router.navigate(searchUrl, {trigger: true});
        }

    });

    return IndexView;
});
<commit_msg>search on keyup<commit_after>define([
    'underscore',
    'backbone',
    'text!templates/navbar.html'
], function (_, Backbone, navbarTemplate) {

    var NAVBAR_TEMPLATE = _.template(navbarTemplate);
    var SEARCH_MAX_ITEM_PER_PAGE = 20;

    var IndexView = Backbone.View.extend({

        initialize: function () {
        },

        render: function () {
            this.$el.html(NAVBAR_TEMPLATE());
            return this;
        },

        events: {
            'submit form': 'submit',
            'keyup input[name="search-text"]': function (e) {
                this.search();
            }
        },

        submit: function (event) {
            event.preventDefault();
        },

        search: function () {
            var name = this.$('input[name="search-text"]').val();
            name = name.trim().split(' ').join('+');

            if (name === "") {
                return;
            }

            // Go to the research page
            var searchUrl = [
                '#documents?search=', name,
                '&order=asc&limit=', SEARCH_MAX_ITEM_PER_PAGE,
                '&offset=0'
            ].join('');
            finalApp.router.navigate(searchUrl, {trigger: true});
        }

    });

    return IndexView;
});
<|endoftext|>


<commit_before>var opts = {
    lines: 13, // The number of lines to draw
    length: 20, // The length of each line
    width: 10, // The line thickness
    radius: 30, // The radius of the inner circle
    corners: 1, // Corner roundness (0..1)
    rotate: 0, // The rotation offset
    direction: 1, // 1: clockwise, -1: counterclockwise
    color: '#000', // #rgb or #rrggbb or array of colors
    speed: 1, // Rounds per second
    trail: 60, // Afterglow percentage
    shadow: false, // Whether to render a shadow
    hwaccel: false, // Whether to use hardware acceleration
    className: 'spinner', // The CSS class to assign to the spinner
    zIndex: 2e9, // The z-index (defaults to 2000000000)
    top: 'auto', // Top position relative to parent in px
    left: 'auto' // Left position relative to parent in px
};
var spinner = new Spinner(opts);
var token;
var pageNumber = 0;
var curReport;
var callCount = 0;

function loginSpin() {
    spinner.stop();
    $('#signinSection').find('input, button').prop("disabled", true);
    spinner.spin(document.getElementById('signinSection'));
}

function loginUnspin() {
    spinner.stop();
    $('#signinSection').find('input, button').prop("disabled", false);
}

function setToken(token) {
    this.token = token;
    var date = new Date();
    setCookie("token", token, date.getTime() + (2 * 24 * 60 * 60 * 1000));
}
function gotoMain()
{
    window.location.href = server_base + "main.jsp";
}
function doLogin()
{
    loginSpin();
    var user = $("#loginEmail").val();
    var password = $("#loginPassword").val();
    $.ajax({
        type: "POST",
        url: server_base + "rest/auth/login/" + user,
        data: password,
        cache: false,
        contentType: "TEXT/PLAIN",
        success: function (msg)
        {
            loginUnspin();
            if (msg.error != "")
            {
                alert(msg.error);
            } else {
                setToken(msg.token);
                gotoMain();
            }
        }
    }).fail(function (jqXHR, textStatus, errorThrown)
    {
        loginUnspin();
        alert(errorThrown);
    });
}
function loadReportListSimple() {
    token = getCookie("token");
    $.ajax({
        type: "GET",
        cache: false,
        url: server_base + "rest/auth/reports/" + token,
        success: function (msg)
        {
            if (!msg.error)
            {
                loadReportListData(msg);
            } else {
                ajaxFail(null, null, msg.error);
            }
        }
    }).fail(ajaxFail);
};
function loadReportListDetailed() {
    token = getCookie("token");
    $.ajax({
        type: "GET",
        cache: false,
        url: server_base + "rest/auth/reportsDetails/" + token + "/",
        success: function (msg)
        {
            if (!msg.error)
            {
                loadReportListData(msg);
            } else {
                ajaxFail(null, null, msg.error);
            }
        }
    }).fail(ajaxFail);
};
function loadReportListData(data)
{
    var reportDDList = $("#reportDropdownList");
    reportDDList.find("li:not(.hidden)").remove();
    if (Array.isArray(data))
    {
        data.forEach(function (report)
        {
            var template = reportDDList.find("li.hidden").clone();
            template.removeClass("hidden");
            reportDDList.append(template);
            template.html(""+report+"");
        });
    } else
    {
        Object.keys(data).forEach(function (reportId)
        {
            var template = reportDDList.find("li.hidden").clone();
            template.removeClass("hidden");
            reportDDList.append(template);
            if (data[reportId].extra && data[reportId].extra.reportName)
            {
                template.html(""+data[reportId].extra.reportName+"");
            } else {
                template.html(""+reportId+"");
            }
        });
    }
}
function resetDisplay() {
    $('#kaguraMain,#kaguraContactUs,#kaguraAbout,#reportMain').addClass("hidden");
}
function resetReportConfig()
{
    $('#reportTableHeader>tr:not(.hidden)').remove();
    $('#reportTableBody>tr:not(.hidden)').remove();
    $('#reportTableBody>tr>td:not(.hidden)').remove();
    $('#paramPanel>div:not(.hidden)').remove();
}
function resetReportBody()
{
    $('#reportTableBody>tr:not(.hidden)').remove();
}
function resetReport()
{
    $('#reportTableHeader>tr:not(.hidden)').remove();
    $('#reportTableBody>tr:not(.hidden)').remove();
    $('#reportTableBody>tr>td:not(.hidden)').remove();
}

function addColumns(columns)
{
    var reportTableHeader = $("#reportTableHeader");
    var reportTableBody = $("#reportTableBody");
    var templateTr = reportTableHeader.find("tr").clone();
    templateTr.removeClass("hidden");
    reportTableHeader.append(templateTr);
    columns.forEach(function (column)
    {
        var templateTh = templateTr.find("th.hidden").clone();
        if (column.label)
            templateTh.text(column.label);
        else
            templateTh.text(column.name);
        templateTh.removeClass("hidden");
        templateTr.append(templateTh);
        var templateRow = reportTableBody.find("tr.hidden");
        templateRow.removeClass("hidden");
        var templateTd = templateRow.find("td.hidden").clone();
        templateTd.removeClass("hidden");
        templateRow.append(templateTd);
        templateTd.attr("name", column.name);
        if (column.extraOptions)
        {
            if (column.extraOptions.styleType == "text")
            {
            } else if (column.extraOptions.styleType == "numbers")
            {
            }
        }
        templateRow.addClass("hidden");
    });
}
function buildReportParameters(msg, inputParamFieldTemplate) {
    var inputParamFieldTemplate = $('#inputParamFieldTemplate');
    msg.params.forEach(function (param) {
        var template = inputParamFieldTemplate.clone();
        template.removeAttr("id");
        template.removeClass("hidden");
        template.addClass("parameterField");
        var label = template.find("label");
        label.text(param.name);
        var paramIdField = template.find("span[name='paramId']");
        paramIdField.text(param.id);
        var paramId = param.id + "Param";
        label.prop("for", paramId);
        var input = template.find("input");
        if ("number" == param.type.toLowerCase()) {
            input.prop("id", paramId);
            input.prop("type", "number");
            input.prop("step", "any");
            input.val(param.value);
            input.prop("placeholder", param.placeholder);
            input.addClass("parameterFieldInput");
        } else if ("boolean" == param.type.toLowerCase()) {
            var options = "<option value='' " + (param.value == "" ? "selected" : "") + ">Select one</option>" +
                "<option value='true' " + (param.value == "true" ? "selected" : "") + ">Yes</option>" +
                "<option value='false' " + (param.value == "false" ? "selected" : "") + ">No</option>";
            input.replaceWith("<select id='" + paramId + "' class=\"parameterFieldInput\">" + options + "</select>");
        } else if ("combo" == param.type.toLowerCase() || "sql" == param.type.toLowerCase()) {
            var options = "<option value='' " + (param.value == "" ? "selected" : "") + ">Select one</option>";
            param.values.forEach(function (value) {
                options = options + "<option value='" + value + "' " + (param.value == value ? "selected" : "") + ">" + value + "</option>";
            });
            input.replaceWith("<select id='" + paramId + "' class=\"parameterFieldInput\">" + options + "</select>");
        } else if ("datetime" == param.type.toLowerCase()) {
            input.replaceWith('
' +
                '<input type="text" class="parameterFieldInput" placeholder="' + param.placeholder + '" />' +
                '' +
                '' +
                '' +
                '
' +
                '');
        } else if ("date" == param.type.toLowerCase()) {
            input.replaceWith('
' +
                '<input type="text" class="parameterFieldInput" placeholder="' + param.placeholder + '" />' +
                '' +
                '' +
                '' +
                '
' +
                '');
        } else if ("manycombo" == param.type.toLowerCase()) {
            var options = "<option disabled='true' value=''>Select one or more</option>";
            param.values.forEach(function (value) {
                options = options + "<option value='" + value + "' " + ((param.value != null && param.value.contains(value)) ? "selected" : "") + ">" + value + "</option>";
            });
            input.replaceWith("<select id='" + paramId + "' class=\"parameterFieldInput\" multiple>" + options + "</select>");
        } else //if ("string" == param.type.toLowerCase())
        {
            input.prop("id", paramId);
            input.val(param.value);
            input.prop("placeholder", param.placeholder);
            input.addClass("parameterFieldInput");
        }
        var help = template.find("p[name='help']");
        help.text(param.help);
        template.insertBefore('#runReportButton');
    });
}
function processReportExtras(msg, reportId) {
    if (msg.extra.reportName) {
        $("#reportTitle").text(msg.extra.reportName);
    } else {
        $("#reportTitle").text(reportId);
    }
    if (msg.extra.description) {
        $("#reportDescription").text(msg.extra.description);
    } else {
        $("#reportDescription").text("");
    }
    var reportImage = $("#reportImage");
    if (msg.extra.image) {
        reportImage.prop('src', msg.extra.image);
        reportImage.removeClass("hidden");
    } else {
        reportImage.addClass("hidden");
    }
}
function loadReport(reportId)
{
    var url = server_base + "rest/report/" + token + "/" + reportId + "/detailsAndRun";
    var method = "GET";
    var contentType = "application/json; charset=utf-8";
    storedHash = reportId;
    window.location.hash = "#" + reportId;
    if (reportId != curReport)
    {
        $(".alert").alert('close');
    }
    callKagura(reportId, method, url, contentType)
}

function displayMain()
{
    resetDisplay();
    $('#kaguraMain').removeClass("hidden");
}
function displayContactUs()
{
    resetDisplay();
    $('#kaguraContactUs').removeClass("hidden");
}
function displayAbout()
{
    resetDisplay();
    $('#kaguraAbout').removeClass("hidden");
}
function exportReport(fileType, all)
{
    var values = "";
    if (all)
    {
        values = values + "&allpages=true";
    } else {
        values = values + "&page=" + pageNumber;
    }
    values = values + "&parameters=" + buildRequestParameters();
    window.location.href = server_base + "rest/report/" + token + "/" + curReport + "/export."+fileType+"?" + values;
}

function buildRequestParameters() {
    var params = {};
    $('div.parameterField:not(.hidden)').each(function (index, divfield) {
        var param = $(divfield).find("span[name='paramId']").text();
        var value = $(divfield).find(".parameterFieldInput").val();
        params[param] = value;
    });
    var encParams = encodeURIComponent(JSON.stringify(params));
    return encParams;
}
function rerunReport()
{
    pageNumber = 0;
    runReport();
}
function reportErrors(msg) {
    msg.errors.forEach(function (error) {
        var newDiv = $('

');
        newDiv.html('
' +
            '<button type="button" class="close" data-dismiss="alert">×</button>' +
            'Warning! ' + error +
            '
');
        $('#reportErrors').append(newDiv);
    })
}
function populateReportRows(msg) {
    var reportTableBody = $("#reportTableBody");
    var templateRow = reportTableBody.find("tr.hidden");
    msg.rows.forEach(function (row) {
        var rowInstance = templateRow.clone();
        rowInstance.removeClass("hidden");
        reportTableBody.append(rowInstance);
        rowInstance.find("td.hidden").remove();
        Object.keys(row).forEach(function (key) {
            rowInstance.find("td[name='" + key + "']").text(row[key]);
        })
    });
}
function fixReportColumns(msg) {
    if (!msg.rows || msg.rows.length == 0) {
        resetReportBody();
    } else {
        resetReport();
        var columns = $.map(Object.keys(msg.rows[0]), function (x) {
            return { name: x, styleName: ""};
        });
        addColumns(columns);
    }
}
function runReport()
{
    var values = "page=" + pageNumber;
    var encParams = buildRequestParameters();
    var url = server_base + "rest/report/" + token + "/" + curReport + "/run?" + values + "&parameters=" + encParams;
    var method = "GET";
    var contentType = null;
    callKagura(curReport, method, url, contentType)
}

function callKagura(reportId, method, url, contentType)
{
    // If we have changed report, reset everything.
    if (reportId != curReport)
    {
        resetDisplay();
        resetReportConfig();
        $('#reportTitle').text(reportId);
        var reportMain = $('#reportMain');
        reportMain.removeClass("hidden");
    } else {
        resetReportBody();
    }
    spinner.stop();
    spinner.spin(document.getElementById('reportMain'));
    $('#reportPageNumber').text(pageNumber+1);
    resetReportBody();
    var myCall = ++callCount;
    $.ajax({
        type: method,
        url: url,
        cache: false,
        contentType: contentType,
        success: function (msg)
        {
            if (myCall != callCount) return; // Damn Double Clickers
            if (msg.error)
            {
                ajaxFail(null, null, msg.error);
                spinner.stop();
                return;
            }
            if (msg.errors)
            {
                reportErrors(msg);
            }
            if (msg.extra)
            {
                processReportExtras(msg, reportId);
            }
            if (msg.params)
            {
                buildReportParameters(msg);
            }
            if (msg.columns)
            {
                if (reportId != curReport)
                {
                    addColumns(msg.columns);
                }
            } else {
                fixReportColumns(msg);
            }
            if (msg.rows)
            {
                populateReportRows(msg);
            }
            if (reportId != curReport)
            {
                pageNumber = 0;
                curReport = reportId;
            }
            spinner.stop();
        }
    }).fail(ajaxFail);
}

function prevPage()
{
    pageNumber = Math.max(pageNumber - 1, 0);
    runReport();
}

function nextPage()
{
    pageNumber = pageNumber + 1;
    runReport();
}

function logout()
{
    setCookie("token", "",1);
    window.location = server_base + "index.jsp";
}

function ajaxFail(jqXHR, textStatus, errorThrown)
{
    if (errorThrown == "Not Found")
    {
        alert("Can not find reporting backend, please ensure report server is running.");
    } else if (errorThrown == "Authentication failure")
    {
        alert("Session expired.");
        logout();
    } else {
        alert(errorThrown);
    }
}<commit_msg>Report title is retained if possible.<commit_after>var opts = {
    lines: 13, // The number of lines to draw
    length: 20, // The length of each line
    width: 10, // The line thickness
    radius: 30, // The radius of the inner circle
    corners: 1, // Corner roundness (0..1)
    rotate: 0, // The rotation offset
    direction: 1, // 1: clockwise, -1: counterclockwise
    color: '#000', // #rgb or #rrggbb or array of colors
    speed: 1, // Rounds per second
    trail: 60, // Afterglow percentage
    shadow: false, // Whether to render a shadow
    hwaccel: false, // Whether to use hardware acceleration
    className: 'spinner', // The CSS class to assign to the spinner
    zIndex: 2e9, // The z-index (defaults to 2000000000)
    top: 'auto', // Top position relative to parent in px
    left: 'auto' // Left position relative to parent in px
};
var spinner = new Spinner(opts);
var token;
var pageNumber = 0;
var curReport;
var callCount = 0;
var reportTitle = null;

function loginSpin() {
    spinner.stop();
    $('#signinSection').find('input, button').prop("disabled", true);
    spinner.spin(document.getElementById('signinSection'));
}

function loginUnspin() {
    spinner.stop();
    $('#signinSection').find('input, button').prop("disabled", false);
}

function setToken(token) {
    this.token = token;
    var date = new Date();
    setCookie("token", token, date.getTime() + (2 * 24 * 60 * 60 * 1000));
}
function gotoMain()
{
    window.location.href = server_base + "main.jsp";
}
function doLogin()
{
    loginSpin();
    var user = $("#loginEmail").val();
    var password = $("#loginPassword").val();
    $.ajax({
        type: "POST",
        url: server_base + "rest/auth/login/" + user,
        data: password,
        cache: false,
        contentType: "TEXT/PLAIN",
        success: function (msg)
        {
            loginUnspin();
            if (msg.error != "")
            {
                alert(msg.error);
            } else {
                setToken(msg.token);
                gotoMain();
            }
        }
    }).fail(function (jqXHR, textStatus, errorThrown)
    {
        loginUnspin();
        alert(errorThrown);
    });
}
function loadReportListSimple() {
    token = getCookie("token");
    $.ajax({
        type: "GET",
        cache: false,
        url: server_base + "rest/auth/reports/" + token,
        success: function (msg)
        {
            if (!msg.error)
            {
                loadReportListData(msg);
            } else {
                ajaxFail(null, null, msg.error);
            }
        }
    }).fail(ajaxFail);
};
function loadReportListDetailed() {
    token = getCookie("token");
    $.ajax({
        type: "GET",
        cache: false,
        url: server_base + "rest/auth/reportsDetails/" + token + "/",
        success: function (msg)
        {
            if (!msg.error)
            {
                loadReportListData(msg);
            } else {
                ajaxFail(null, null, msg.error);
            }
        }
    }).fail(ajaxFail);
};
function loadReportListData(data)
{
    var reportDDList = $("#reportDropdownList");
    reportDDList.find("li:not(.hidden)").remove();
    if (Array.isArray(data))
    {
        data.forEach(function (report)
        {
            var template = reportDDList.find("li.hidden").clone();
            template.removeClass("hidden");
            reportDDList.append(template);
            template.html(""+report+"");
        });
    } else
    {
        Object.keys(data).forEach(function (reportId)
        {
            var template = reportDDList.find("li.hidden").clone();
            template.removeClass("hidden");
            reportDDList.append(template);
            if (data[reportId].extra && data[reportId].extra.reportName)
            {
                template.html(""+data[reportId].extra.reportName+"");
            } else {
                template.html(""+reportId+"");
            }
        });
    }
}
function resetDisplay() {
    $('#kaguraMain,#kaguraContactUs,#kaguraAbout,#reportMain').addClass("hidden");
}
function resetReportConfig()
{
    $('#reportTableHeader>tr:not(.hidden)').remove();
    $('#reportTableBody>tr:not(.hidden)').remove();
    $('#reportTableBody>tr>td:not(.hidden)').remove();
    $('#paramPanel>div:not(.hidden)').remove();
}
function resetReportBody()
{
    $('#reportTableBody>tr:not(.hidden)').remove();
}
function resetReport()
{
    $('#reportTableHeader>tr:not(.hidden)').remove();
    $('#reportTableBody>tr:not(.hidden)').remove();
    $('#reportTableBody>tr>td:not(.hidden)').remove();
}

function addColumns(columns)
{
    var reportTableHeader = $("#reportTableHeader");
    var reportTableBody = $("#reportTableBody");
    var templateTr = reportTableHeader.find("tr").clone();
    templateTr.removeClass("hidden");
    reportTableHeader.append(templateTr);
    columns.forEach(function (column)
    {
        var templateTh = templateTr.find("th.hidden").clone();
        if (column.label)
            templateTh.text(column.label);
        else
            templateTh.text(column.name);
        templateTh.removeClass("hidden");
        templateTr.append(templateTh);
        var templateRow = reportTableBody.find("tr.hidden");
        templateRow.removeClass("hidden");
        var templateTd = templateRow.find("td.hidden").clone();
        templateTd.removeClass("hidden");
        templateRow.append(templateTd);
        templateTd.attr("name", column.name);
        if (column.extraOptions)
        {
            if (column.extraOptions.styleType == "text")
            {
            } else if (column.extraOptions.styleType == "numbers")
            {
            }
        }
        templateRow.addClass("hidden");
    });
}
function buildReportParameters(msg, inputParamFieldTemplate) {
    var inputParamFieldTemplate = $('#inputParamFieldTemplate');
    msg.params.forEach(function (param) {
        var template = inputParamFieldTemplate.clone();
        template.removeAttr("id");
        template.removeClass("hidden");
        template.addClass("parameterField");
        var label = template.find("label");
        label.text(param.name);
        var paramIdField = template.find("span[name='paramId']");
        paramIdField.text(param.id);
        var paramId = param.id + "Param";
        label.prop("for", paramId);
        var input = template.find("input");
        if ("number" == param.type.toLowerCase()) {
            input.prop("id", paramId);
            input.prop("type", "number");
            input.prop("step", "any");
            input.val(param.value);
            input.prop("placeholder", param.placeholder);
            input.addClass("parameterFieldInput");
        } else if ("boolean" == param.type.toLowerCase()) {
            var options = "<option value='' " + (param.value == "" ? "selected" : "") + ">Select one</option>" +
                "<option value='true' " + (param.value == "true" ? "selected" : "") + ">Yes</option>" +
                "<option value='false' " + (param.value == "false" ? "selected" : "") + ">No</option>";
            input.replaceWith("<select id='" + paramId + "' class=\"parameterFieldInput\">" + options + "</select>");
        } else if ("combo" == param.type.toLowerCase() || "sql" == param.type.toLowerCase()) {
            var options = "<option value='' " + (param.value == "" ? "selected" : "") + ">Select one</option>";
            param.values.forEach(function (value) {
                options = options + "<option value='" + value + "' " + (param.value == value ? "selected" : "") + ">" + value + "</option>";
            });
            input.replaceWith("<select id='" + paramId + "' class=\"parameterFieldInput\">" + options + "</select>");
        } else if ("datetime" == param.type.toLowerCase()) {
            input.replaceWith('
' +
                '<input type="text" class="parameterFieldInput" placeholder="' + param.placeholder + '" />' +
                '' +
                '' +
                '' +
                '
' +
                '');
        } else if ("date" == param.type.toLowerCase()) {
            input.replaceWith('
' +
                '<input type="text" class="parameterFieldInput" placeholder="' + param.placeholder + '" />' +
                '' +
                '' +
                '' +
                '
' +
                '');
        } else if ("manycombo" == param.type.toLowerCase()) {
            var options = "<option disabled='true' value=''>Select one or more</option>";
            param.values.forEach(function (value) {
                options = options + "<option value='" + value + "' " + ((param.value != null && param.value.contains(value)) ? "selected" : "") + ">" + value + "</option>";
            });
            input.replaceWith("<select id='" + paramId + "' class=\"parameterFieldInput\" multiple>" + options + "</select>");
        } else //if ("string" == param.type.toLowerCase())
        {
            input.prop("id", paramId);
            input.val(param.value);
            input.prop("placeholder", param.placeholder);
            input.addClass("parameterFieldInput");
        }
        var help = template.find("p[name='help']");
        help.text(param.help);
        template.insertBefore('#runReportButton');
    });
}
function processReportExtras(msg, reportId) {
    if (msg.extra.reportName) {
        $("#reportTitle").text(msg.extra.reportName);
    } else {
        $("#reportTitle").text(reportId);
    }
    if (msg.extra.description) {
        $("#reportDescription").text(msg.extra.description);
    } else {
        $("#reportDescription").text("");
    }
    var reportImage = $("#reportImage");
    if (msg.extra.image) {
        reportImage.prop('src', msg.extra.image);
        reportImage.removeClass("hidden");
    } else {
        reportImage.addClass("hidden");
    }
}
function loadReport(reportId)
{
    var url = server_base + "rest/report/" + token + "/" + reportId + "/detailsAndRun";
    var method = "GET";
    var contentType = "application/json; charset=utf-8";
    storedHash = reportId;
    window.location.hash = "#" + reportId;
    if (reportId != curReport)
    {
        $(".alert").alert('close');
    }
    callKagura(reportId, method, url, contentType)
}

function displayMain()
{
    resetDisplay();
    $('#kaguraMain').removeClass("hidden");
}
function displayContactUs()
{
    resetDisplay();
    $('#kaguraContactUs').removeClass("hidden");
}
function displayAbout()
{
    resetDisplay();
    $('#kaguraAbout').removeClass("hidden");
}
function exportReport(fileType, all)
{
    var values = "";
    if (all)
    {
        values = values + "&allpages=true";
    } else {
        values = values + "&page=" + pageNumber;
    }
    values = values + "&parameters=" + buildRequestParameters();
    window.location.href = server_base + "rest/report/" + token + "/" + curReport + "/export."+fileType+"?" + values;
}

function buildRequestParameters() {
    var params = {};
    $('div.parameterField:not(.hidden)').each(function (index, divfield) {
        var param = $(divfield).find("span[name='paramId']").text();
        var value = $(divfield).find(".parameterFieldInput").val();
        params[param] = value;
    });
    var encParams = encodeURIComponent(JSON.stringify(params));
    return encParams;
}
function rerunReport()
{
    pageNumber = 0;
    runReport();
}
function reportErrors(msg) {
    msg.errors.forEach(function (error) {
        var newDiv = $('

');
        newDiv.html('
' +
            '<button type="button" class="close" data-dismiss="alert">×</button>' +
            'Warning! ' + error +
            '
');
        $('#reportErrors').append(newDiv);
    })
}
function populateReportRows(msg) {
    var reportTableBody = $("#reportTableBody");
    var templateRow = reportTableBody.find("tr.hidden");
    msg.rows.forEach(function (row) {
        var rowInstance = templateRow.clone();
        rowInstance.removeClass("hidden");
        reportTableBody.append(rowInstance);
        rowInstance.find("td.hidden").remove();
        Object.keys(row).forEach(function (key) {
            rowInstance.find("td[name='" + key + "']").text(row[key]);
        })
    });
}
function fixReportColumns(msg) {
    if (!msg.rows || msg.rows.length == 0) {
        resetReportBody();
    } else {
        resetReport();
        var columns = $.map(Object.keys(msg.rows[0]), function (x) {
            return { name: x, styleName: ""};
        });
        addColumns(columns);
    }
}
function runReport()
{
    var values = "page=" + pageNumber;
    var encParams = buildRequestParameters();
    var url = server_base + "rest/report/" + token + "/" + curReport + "/run?" + values + "&parameters=" + encParams;
    var method = "GET";
    var contentType = null;
    callKagura(curReport, method, url, contentType)
}

function callKagura(reportId, method, url, contentType)
{
    // If we have changed report, reset everything.
    if (reportId != curReport)
    {
        resetDisplay();
        resetReportConfig();
        if (reportTitle != null)
        {
            $('#reportTitle').text(reportTitle);
        } else {
            $('#reportTitle').text(reportId);
        }
        var reportMain = $('#reportMain');
        reportMain.removeClass("hidden");
    } else {
        resetReportBody();
    }
    spinner.stop();
    spinner.spin(document.getElementById('reportMain'));
    $('#reportPageNumber').text(pageNumber+1);
    resetReportBody();
    var myCall = ++callCount;
    $.ajax({
        type: method,
        url: url,
        cache: false,
        contentType: contentType,
        success: function (msg)
        {
            if (myCall != callCount) return; // Damn Double Clickers
            if (msg.error)
            {
                ajaxFail(null, null, msg.error);
                spinner.stop();
                return;
            }
            if (msg.errors)
            {
                reportErrors(msg);
            }
            if (msg.extra)
            {
                processReportExtras(msg, reportId);
            }
            if (msg.params)
            {
                buildReportParameters(msg);
            }
            if (msg.columns)
            {
                if (reportId != curReport)
                {
                    addColumns(msg.columns);
                }
            } else {
                fixReportColumns(msg);
            }
            if (msg.rows)
            {
                populateReportRows(msg);
            }
            if (reportId != curReport)
            {
                pageNumber = 0;
                curReport = reportId;
            }
            spinner.stop();
        }
    }).fail(ajaxFail);
}

function prevPage()
{
    pageNumber = Math.max(pageNumber - 1, 0);
    runReport();
}

function nextPage()
{
    pageNumber = pageNumber + 1;
    runReport();
}

function logout()
{
    setCookie("token", "",1);
    window.location = server_base + "index.jsp";
}

function ajaxFail(jqXHR, textStatus, errorThrown)
{
    if (errorThrown == "Not Found")
    {
        alert("Can not find reporting backend, please ensure report server is running.");
    } else if (errorThrown == "Authentication failure")
    {
        alert("Session expired.");
        logout();
    } else {
        alert(errorThrown);
    }
}<|endoftext|>


<commit_before>var CookieJar = require('tough-cookie').CookieJar,
    ToughCookie = require('tough-cookie').Cookie,
    PostmanCookie = require('postman-collection').Cookie,
    urlParse = require('url').parse,

    FUNCTION = 'function',

    getCookieContext = function (url) {
        if (url instanceof Object) {
            return url;
        }

        try {
            url = decodeURI(url);
        }
        catch (err) {
            // Silently swallow error
        }

        return urlParse(url);
    },

    // PostmanCookie to ToughCookie
    serialize = function (pmCookie) {
        if (!pmCookie) {
            return;
        }

        let serializedCookie = ToughCookie.parse(PostmanCookie.toString(pmCookie));

        // set hostOnly property if the cookie is HostOnly
        if (pmCookie.hostOnly) {
            serializedCookie.hostOnly = true;
        }

        return serializedCookie;
    },

    // ToughCookie to PostmanCookie
    deserialize = function (toughCookie) {
        return PostmanCookie.parse(ToughCookie.prototype.toString.call(toughCookie));
    },

    PostmanCookieJar;

PostmanCookieJar = function PostmanCookieJar (cookieStore) {
    this.store = cookieStore;
    this.jar = new CookieJar(cookieStore, {
        rejectPublicSuffixes: false,
        looseMode: true
    });
};

PostmanCookieJar.prototype.get = function (url, name, callback) {
    var done = function (err, cookies) {
        typeof callback === FUNCTION && callback(err, cookies);
    };

    this.jar.getCookies(url, function (err, cookies) {
        var i,
            ii;

        if (err) {
            return done(err);
        }

        if (!(cookies && cookies.length)) {
            return done(null);
        }

        // @todo add CookieJar~getCookie to avoid this
        for (i = 0, ii = cookies.length; i < ii; i++) {
            if (cookies[i].key === name) {
                return done(null, deserialize(cookies[i]));
            }
        }

        done(null);
    });
};

PostmanCookieJar.prototype.getAll = function (url, options, callback) {
    if (typeof options === FUNCTION && !callback) {
        callback = options;
        options = {};
    }

    this.jar.getCookies(url, options, function (err, cookies) {
        typeof callback === FUNCTION && callback(err, cookies.map(deserialize));
    });
};

PostmanCookieJar.prototype.set = function (url, cookie, callback) {
    if (PostmanCookie.isCookie(cookie)) {
        cookie = serialize(cookie);
    }

    this.jar.setCookie(cookie, url, function (err, cookie) {
        typeof callback === FUNCTION && callback(err, deserialize(cookie));
    });
};

PostmanCookieJar.prototype.unset = function (url, name, callback) {
    var context = getCookieContext(url);

    this.store.removeCookie(context.hostname, context.pathname, name, function (err) {
        typeof callback === FUNCTION && callback(err);
    });
};

PostmanCookieJar.prototype.clear = function (url, callback) {
    if (typeof url === FUNCTION) {
        callback = url;

        this.store.removeAllCookies(function (err) {
            callback(err);
        });

        return;
    }

    var context = getCookieContext(url);

    // @todo we can add options here to solve user-case like:
    // delete all `Secure` cookie; delete all cookies with name `foo` etc.
    this.store.removeCookies(context.hostname, context.pathname, function (err) {
        typeof callback === FUNCTION && callback(err);
    });
};

module.exports = PostmanCookieJar;
<commit_msg>Gracefully handle cookie jar exceptions<commit_after>var CookieJar = require('tough-cookie').CookieJar,
    ToughCookie = require('tough-cookie').Cookie,
    PostmanCookie = require('postman-collection').Cookie,
    Url = require('postman-collection').Url,

    FUNCTION = 'function',
    OBJECT = 'object',
    STRING = 'string',

    // PostmanCookie to ToughCookie
    serialize = function (cookie) {
        if (!cookie || typeof cookie !== OBJECT) {
            cookie = {};
        }

        return ToughCookie.fromJSON({
            key: cookie.name || cookie.key,
            value: cookie.value,
            expires: cookie.expires,
            maxAge: cookie.maxAge,
            domain: cookie.domain,
            path: cookie.path,
            secure: cookie.secure,
            httpOnly: cookie.httpOnly,
            hostOnly: cookie.hostOnly,
            extensions: cookie.extensions
        });
    },

    // ToughCookie to PostmanCookie
    deserialize = function (cookie) {
        if (!cookie || typeof cookie !== OBJECT) {
            cookie = {};
        }

        return new PostmanCookie({
            name: cookie.key,
            value: cookie.value,
            expires: cookie.expires === 'Infinity' ? null : cookie.expires,
            maxAge: cookie.maxAge,
            domain: cookie.domain,
            path: cookie.path,
            secure: cookie.secure,
            httpOnly: cookie.httpOnly,
            hostOnly: cookie.hostOnly,
            extensions: cookie.extensions
        });
    },

    PostmanCookieJar;

PostmanCookieJar = function PostmanCookieJar (cookieStore) {
    this.store = cookieStore;
    this.jar = new CookieJar(cookieStore, {
        rejectPublicSuffixes: false,
        looseMode: true
    });
};

PostmanCookieJar.prototype.get = function (url, name, callback) {
    if (typeof callback !== FUNCTION) {
        throw new TypeError('CookieJar.get() requires a callback function');
    }

    if (typeof name !== STRING) {
        throw new TypeError('CookieJar.get() requires cookie name to be a string');
    }

    if (url && typeof url.toString === FUNCTION) {
        url = url.toString();
    }

    // @todo add CookieJar~getCookie to avoid this
    this.jar.getCookies(url, function (err, cookies) {
        var i,
            ii;

        if (err) {
            return callback(err);
        }

        if (!(cookies && cookies.length)) {
            return callback(null);
        }

        for (i = 0, ii = cookies.length; i < ii; i++) {
            if (cookies[i].key === name) {
                return callback(null, deserialize(cookies[i]));
            }
        }

        callback(null);
    });
};

PostmanCookieJar.prototype.getAll = function (url, options, callback) {
    if (typeof options === FUNCTION && !callback) {
        callback = options;
        options = {};
    }

    if (typeof callback !== FUNCTION) {
        throw new TypeError('CookieJar.getAll() requires a callback function');
    }

    if (typeof options !== OBJECT) {
        throw new TypeError('CookieJar.getAll() requires options to be an object');
    }

    if (url && typeof url.toString === FUNCTION) {
        url = url.toString();
    }

    options = {
        // return HttpOnly cookies by default
        http: options.hasOwnProperty('http') ? Boolean(options.http) : true,
        // if undefined, auto-detect from url
        secure: options.hasOwnProperty('secure') ? Boolean(options.secure) : undefined
    };

    this.jar.getCookies(url, options, function (err, cookies) {
        callback(err, cookies.map(deserialize));
    });
};

PostmanCookieJar.prototype.set = function (url, cookie, callback) {
    if (typeof cookie === OBJECT) {
        cookie = serialize(cookie);
    }

    if (!(cookie && (typeof cookie === STRING || cookie instanceof ToughCookie))) {
        throw new TypeError('CookieJar.set() requires a valid cookie instance');
    }

    if (url && typeof url.toString === FUNCTION) {
        url = url.toString();
    }

    this.jar.setCookie(cookie, url, function (err, cookie) {
        typeof callback === FUNCTION && callback(err, deserialize(cookie));
    });
};

PostmanCookieJar.prototype.unset = function (url, name, callback) {
    if (!Url.isUrl(url)) {
        url = new Url(url);
    }

    if (typeof name !== STRING) {
        throw new TypeError('CookieJar.unset() requires cookie name to be a string');
    }

    this.store.removeCookie(url.getHost(), url.path && url.getPath(), name, function (err) {
        typeof callback === FUNCTION && callback(err);
    });
};

PostmanCookieJar.prototype.clear = function (filter, callback) {
    if (typeof filter === FUNCTION && !callback) {
        callback = filter;

        this.store.removeAllCookies(function (err) {
            callback(err);
        });

        return;
    }

    if (typeof filter !== OBJECT) {
        throw new TypeError('CookieJar.clear() requires filter to be an object');
    }

    var domain,
        path,
        url = filter.url;

    if (url && !Url.isUrl(url)) {
        url = new Url(url);
    }

    domain = url && url.getHost() || filter.domain;
    path = url && url.path && url.getPath() || filter.path;

    this.store.removeCookies(domain, path, function (err) {
        typeof callback === FUNCTION && callback(err);
    });
};

module.exports = PostmanCookieJar;
<|endoftext|>


<commit_before>import { applyMiddleware, compose, createStore } from 'redux';
import thunk from 'redux-thunk';

export function configureStore(rootReducer):Object {

    const finalCreateStore = compose(
      applyMiddleware(thunk)
    )(createStore);

    return finalCreateStore(rootReducer);
}
<commit_msg>[feature/reduxTools] Adding support for https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd<commit_after>import { applyMiddleware, compose, createStore } from 'redux';
import thunk from 'redux-thunk';

export function configureStore(rootReducer):Object {

    const finalCreateStore = compose(
      applyMiddleware(thunk),
      window.devToolsExtension ? window.devToolsExtension() : f => f
    )(createStore);

    return finalCreateStore(rootReducer);
}
<|endoftext|>


<commit_before>(function() {
  var req = (window.requireNode || window.require);
  const ipcRenderer = window.ipcRenderer || req('electron').ipcRenderer;

  var ratio = window.devicePixelRatio || 1.0;
  var jq = window.jQuery || window.$ || (window.Ember && window.Ember.$);
  var listen_level = 'noisy';

  var delivery_debounce = null;
  var dropped_points = [];
  ipcRenderer.on('eye-gaze-status', function(event, arg) {
    var elem = document;
    var data = arg;
    var e = jq.Event('eye-gaze-status');
    e.statuses = data;
    e.target = elem;
    jq(e.target).trigger(e);
  });
  ipcRenderer.on('eye-gaze-data', function(event, arg) {
    var elem = document.getElementById('linger');

    var data = JSON.parse(arg);
    var current_ratio = (data.scaled === false) ? 1.0 : ratio;
    data.x = (data.screenX / current_ratio) - (window.screenInnerOffsetX || window.screenX);
    data.y = (data.screenY / current_ratio) - (window.screenInnerOffsetY || window.screenY);
    var valid = false;
    if(data.type == 'linger' && listen_level == 'averaged') { valid = true; }
    if(data.type == 'over' && listen_level == 'noisy') { valid = true; }
    if(valid) {
      if(delivery_debounce) {
        dropped_points.push(data);
      } else {
        delivery_debounce = setTimeout(function() {
          delivery_debounce = null;
        }, 50);
        var e = jq.Event('gazelinger'); // TODO: this should really be gazeover for non-linger events
        if(dropped_points.length > 0) {
          for(var idx = 0; idx < dropped_points.length; idx++) {
            data.x = data.x + dropped_points[idx].x;
            data.y = data.y + dropped_points[idx].y;
          }
          data.x = data.x / (dropped_points.length + 1);
          data.y = data.y / (dropped_points.length + 1);
          dropped_points = [];
        }
        e.clientX = data.x;
        e.clientY = data.y;
        e.duration = data.duration;
        e.eyegaze_hardware = data.raw && data.raw.hardware;
        e.ts = data.ts;

        var elem_left = elem && elem.style && elem.style.left;
        if (elem) { elem.style.left = '-1000px'; }
        e.target = document.elementFromPoint(data.x, data.y);
        if (elem) { elem.style.left = elem_left; }

        jq(e.target).trigger(e);
      }
    }
  });
  ipcRenderer.on('eye-gaze-calibrate', function(event, args) {
    var data = JSON.parse(args);
    if(calibrate_check_callback) {
      calibrate_check_callback(!!data.calibratable);
    }
  });
  var calibrate_check_callback = null;
  var eye_gaze = {
    init: function(jQuery) {
      jq = jQuery;
      jq(document).on('mousemove touchstart', function(event) {
        if(event.screenX && event.clientX) {
          window.screenInnerOffsetY = event.screenY - event.clientY;
          window.screenInnerOffsetX = event.screenX - event.clientX;
        }
      });
    },
    listen: function(level) {
      level = level || 'noisy';
      ipcRenderer.send('eye-gaze-subscribe', level);
      listen_level = level;
    },
    stop_listening: function() {
      ipcRenderer.send('eye-gaze-unsubscribe');
    },
    calibrate: function() {
      ipcRenderer.send('eye-gaze-calibrate');
    },
    calibratable: function(cb) {
      calibrate_check_callback = cb;
      ipcRenderer.send('eye-gaze-calibrate-check');
    }
  };

  module.exports = eye_gaze;
})()
<commit_msg>allow jQuery to load after page without ruining events<commit_after>(function() {
  var req = (window.requireNode || window.require);
  const ipcRenderer = window.ipcRenderer || req('electron').ipcRenderer;

  var ratio = window.devicePixelRatio || 1.0;
  var jq = window.jQuery || window.$ || (window.Ember && window.Ember.$);
  var listen_level = 'noisy';

  var delivery_debounce = null;
  var dropped_points = [];
  ipcRenderer.on('eye-gaze-status', function(event, arg) {
    jq = jq || window.jQuery || window.$ || (window.Ember && window.Ember.$);
    var elem = document;
    var data = arg;
    var e = jq.Event('eye-gaze-status');
    e.statuses = data;
    e.target = elem;
    jq(e.target).trigger(e);
  });
  ipcRenderer.on('eye-gaze-data', function(event, arg) {
    var elem = document.getElementById('linger');

    var data = JSON.parse(arg);
    var current_ratio = (data.scaled === false) ? 1.0 : ratio;
    data.x = (data.screenX / current_ratio) - (window.screenInnerOffsetX || window.screenX);
    data.y = (data.screenY / current_ratio) - (window.screenInnerOffsetY || window.screenY);
    var valid = false;
    if(data.type == 'linger' && listen_level == 'averaged') { valid = true; }
    if(data.type == 'over' && listen_level == 'noisy') { valid = true; }
    if(valid) {
      if(delivery_debounce) {
        dropped_points.push(data);
      } else {
        delivery_debounce = setTimeout(function() {
          delivery_debounce = null;
        }, 50);
        jq = jq || window.jQuery || window.$ || (window.Ember && window.Ember.$);
        var e = jq.Event('gazelinger'); // TODO: this should really be gazeover for non-linger events
        if(dropped_points.length > 0) {
          for(var idx = 0; idx < dropped_points.length; idx++) {
            data.x = data.x + dropped_points[idx].x;
            data.y = data.y + dropped_points[idx].y;
          }
          data.x = data.x / (dropped_points.length + 1);
          data.y = data.y / (dropped_points.length + 1);
          dropped_points = [];
        }
        e.clientX = data.x;
        e.clientY = data.y;
        e.duration = data.duration;
        e.eyegaze_hardware = data.raw && data.raw.hardware;
        e.ts = data.ts;

        var elem_left = elem && elem.style && elem.style.left;
        if (elem) { elem.style.left = '-1000px'; }
        e.target = document.elementFromPoint(data.x, data.y);
        if (elem) { elem.style.left = elem_left; }

        jq(e.target).trigger(e);
      }
    }
  });
  ipcRenderer.on('eye-gaze-calibrate', function(event, args) {
    var data = JSON.parse(args);
    if(calibrate_check_callback) {
      calibrate_check_callback(!!data.calibratable);
    }
  });
  var calibrate_check_callback = null;
  var eye_gaze = {
    init: function(jQuery) {
      jq = jQuery;
      jq(document).on('mousemove touchstart', function(event) {
        if(event.screenX && event.clientX) {
          window.screenInnerOffsetY = event.screenY - event.clientY;
          window.screenInnerOffsetX = event.screenX - event.clientX;
        }
      });
    },
    listen: function(level) {
      level = level || 'noisy';
      ipcRenderer.send('eye-gaze-subscribe', level);
      listen_level = level;
    },
    stop_listening: function() {
      ipcRenderer.send('eye-gaze-unsubscribe');
    },
    calibrate: function() {
      ipcRenderer.send('eye-gaze-calibrate');
    },
    calibratable: function(cb) {
      calibrate_check_callback = cb;
      ipcRenderer.send('eye-gaze-calibrate-check');
    }
  };

  module.exports = eye_gaze;
})()
<|endoftext|>


<commit_before>// Copyright 2017, University of Colorado Boulder

/**
 * A trait for subtypes of Node, used to make the node behave like a 'number' input with assistive technology.
 * An accessible number spinner behaves like:
 *
 * - Arrow keys increment/decrement the value by a specified step size.
 * - Page Up and Page Down increments/decrements value by an alternative step size, usually larger than default.
 * - Home key sets value to its minimum.
 * - End key sets value to its maximum.
 *
 * This number spinner is different than typical 'number' inputs because it does not support number key control. All
 * changes go through the arrow, page up, page down, home, and end keys. For that reason, the HTML representation should
 * ideally look like: (values removed for readability)
 *
 *
Accessible Name

 *

 *
 * @author Jesse Greenberg (PhET Interactive Simulations)
 * @author Michael Barlow (PhET Interactive Simulations)
 */

define( function( require ) {
  'use strict';

  // modules
  var AccessiblePeer = require( 'SCENERY/accessibility/AccessiblePeer' );
  var CallbackTimer = require( 'SUN/CallbackTimer' );
  var Emitter = require( 'AXON/Emitter' );
  var extend = require( 'PHET_CORE/extend' );
  var inheritance = require( 'PHET_CORE/inheritance' );
  var KeyboardUtil = require( 'SCENERY/accessibility/KeyboardUtil' );
  var Node = require( 'SCENERY/nodes/Node' );
  var StringUtils = require( 'PHETCOMMON/util/StringUtils' );
  var sun = require( 'SUN/sun' );
  var Util = require( 'DOT/Util' );

  var AccessibleNumberSpinner = {

    /**
     * Implement functionality for a number spinner.
     * @public
     * @trait
     *
     * @param {function} type - The type (constructor) whose prototype we'll modify.
     */
    mixInto: function( type ) {
      assert && assert( _.includes( inheritance( type ), Node ) );

      var proto = type.prototype;

      extend( proto, {

        /**
         * This should be called in the constructor to initialize the accessible input features for the node.
         *
         * @param {Property.<number>} valueProperty
         * @param {Property.<Range>} enabledRangeProperty
         * @param {Property.<boolean>} enabledProperty
         * @param {Object} [options]
         *
         * @protected
         */
        initializeAccessibleNumberSpinner: function( valueProperty, enabledRangeProperty, enabledProperty, options ) {
          var self = this;

          var defaults = {
            a11yValueDelta: 1, // {number} - value changes by this quantity when using arrow keys
            a11yPageValueDelta: 2, // {number} - value changes by this quantity when using page up/page down

            a11yUseTimer: true, // if false value will change every native DOM keydown event, following options meaningless
            timerDelay: 400, // start to fire continuously after pressing for this long (milliseconds)
            timerInterval: 100, // fire continuously at this frequency (milliseconds),

            // a11y
            tagName: 'div',
            ariaRole: 'spinbutton',
            focusable: true,
            a11yValuePattern: '{{value}}', // {string} if you want units or additional content, add to pattern
            a11yDecimalPlaces: 0, // number of decimal places for the value read by assistive technology

            // set labelContent to give this AccessibleNumberSpinner an accessible name, required for spinbuttons
            // see https://github.com/phetsims/gravity-force-lab-basics/issues/62
            labelTagName: 'p',

            // Converts a value to a string, for extra formatting for the value read by the screen reader
            a11yFormatValue: function( value ) {
              return Util.toFixed( value, options.a11yDecimalPlaces );
            }
          };
          options = _.extend( {}, defaults, options );

          // Some options were already mutated in the constructor, only apply the accessibility-specific options here
          // so options are not doubled up, see https://github.com/phetsims/sun/issues/330
          var optionsToMutate = _.pick( options, _.keys( defaults ) );
          this.mutate( optionsToMutate );

          // this number spinner is "aria-labelledby" its own label, meaning that the label element content will be
          // read on focus
          this.addAriaLabelledbyAssociation( {
            thisElementName: AccessiblePeer.PRIMARY_SIBLING,
            otherNode: this,
            otherElementName: AccessiblePeer.LABEL_SIBLING
          } );

          // @private {Property.<number>}
          this._valueProperty = valueProperty;

          // @private {Property.<Range>}
          this._enabledRangeProperty = enabledRangeProperty;

          // @private{Property.<boolean>}
          this._enabledProperty = enabledProperty;

          // @private {number}
          this._a11yValueDelta = options.a11yValueDelta;

          // @private {number}
          this._a11yPageValueDelta = options.a11yPageValueDelta;

          // @private {boolean}
          this._a11yUseTimer = options.a11yUseTimer;

          // @private - manages timing must be disposed
          this._callbackTimer = new CallbackTimer( {
            delay: options.timerDelay,
            interval: options.timerInterval
          } );

          // @private {Emitter} emit events when increment/decrement keys are pressed down/up
          this.incrementDownEmitter = new Emitter();
          this.decrementDownEmitter = new Emitter();

          // @private {Emitter} - emit events when value is incremented/decremented
          this.valueIncrementEmitter = new Emitter();
          this.valueDecrementEmitter = new Emitter();

          // update enabled number range for AT, required for screen reader events to behave correctly, must be disposed
          var enabledRangeObserver = function( enabledRange ) {
            self.setAccessibleAttribute( 'min', enabledRange.min );
            self.setAccessibleAttribute( 'max', enabledRange.max );
          };
          this._enabledRangeProperty.link( enabledRangeObserver );

          // a callback that is added and removed from the timer depending on keystate
          var downCallback = null;

          // handle all accessible event input
          var accessibleInputListener = {
            keydown: function( event ) {
              self.emitKeyState( event, true );

              // if using the timer, handle update at interval
              if ( self._a11yUseTimer ) {
                if ( !self._callbackTimer.isRunning() ) {
                  self.handleKeyDown( event );

                  downCallback = self.handleKeyDown.bind( self, event );
                  self._callbackTimer.addCallback( downCallback );
                  self._callbackTimer.start();
                }
              }
              else {
                self.handleKeyDown( event );
              }
            },
            keyup: function( event ) {
              self.emitKeyState( event, false );

              if ( self._a11yUseTimer ) {
                self._callbackTimer.stop( false );
                self._callbackTimer.removeCallback( downCallback );
              }
            }
          };
          this.addAccessibleInputListener( accessibleInputListener );

          // when the property changes, be sure to update the accessible input value
          var accessiblePropertyListener = function( value ) {

            // format the value to a certain number of decimal places
            var formattedValue = options.a11yFormatValue( value );

            // wrap the value with optional string pattern
            formattedValue = StringUtils.fillIn( options.a11yValuePattern, {
              value: formattedValue
            } );

            self.setAccessibleAttribute( 'aria-valuetext', formattedValue );
            self.setAccessibleAttribute( 'aria-valuenow', value );
          };
          this._valueProperty.link( accessiblePropertyListener );

          // @private - called by disposeAccessibleNumberSpinner to prevent memory leaks
          this._disposeAccessibleNumberSpinner = function() {
            self._valueProperty.unlink( accessiblePropertyListener );
            self._enabledRangeProperty.unlink( enabledRangeObserver );
            self._callbackTimer.dispose();

            // emitters owned by this instance, can be disposed here
            self.incrementDownEmitter.dispose();
            self.decrementDownEmitter.dispose();
            self.valueIncrementEmitter.dispose();
            self.valueDecrementEmitter.dispose();

            self.removeAccessibleInputListener( accessibleInputListener );
          };
        },

        /**
         * Emit events related to the keystate of the spinner. Typically used to style the spinner during keyboard
         * interaction.
         * @private
         *
         * @param {UIEvent} event - the DOM event that was triggered
         * @param {boolean} isDown - whether or not event was triggered from down or up keys
         */
        emitKeyState: function( event, isDown ) {
          if ( event.keyCode === KeyboardUtil.KEY_UP_ARROW || event.keyCode === KeyboardUtil.KEY_RIGHT_ARROW ) {
            this.incrementDownEmitter.emit1( isDown );
          }
          else if ( event.keyCode === KeyboardUtil.KEY_DOWN_ARROW || event.keyCode === KeyboardUtil.KEY_LEFT_ARROW ) {
            this.decrementDownEmitter.emit1( isDown );
          }
        },

        /**
         * Handle the keydown event so that this node behaves like an accessible number input.
         * @private
         *
         * @param {DOMEvent} event
         */
        handleKeyDown: function( event ) {
          var code = event.keyCode;

          if ( this._enabledProperty.get() ) {

            // prevent user from changing value with number or the space keys, handle arrow keys on our own
            if ( KeyboardUtil.isArrowKey( code ) || KeyboardUtil.isNumberKey( code ) || code === KeyboardUtil.KEY_SPACE ) {
              event.preventDefault();
            }

            // handle the event
            if ( code === KeyboardUtil.KEY_RIGHT_ARROW || code === KeyboardUtil.KEY_UP_ARROW ) {
              this.valueIncrementEmitter.emit();
              this._valueProperty.set( Math.min( this._valueProperty.get() + this._a11yValueDelta, this._enabledRangeProperty.get().max ) );
            }
            else if ( code === KeyboardUtil.KEY_LEFT_ARROW || code === KeyboardUtil.KEY_DOWN_ARROW ) {
              this.valueDecrementEmitter.emit();
              this._valueProperty.set( Math.max( this._valueProperty.get() - this._a11yValueDelta, this._enabledRangeProperty.get().min ) );
            }
            else if ( code === KeyboardUtil.KEY_PAGE_UP ) {
              this._valueProperty.set( Math.min( this._valueProperty.get() + this._a11yPageValueDelta, this._enabledRangeProperty.get().max ) );
            }
            else if ( code === KeyboardUtil.KEY_PAGE_DOWN ) {
              this._valueProperty.set( Math.max( this._valueProperty.get() - this._a11yPageValueDelta, this._enabledRangeProperty.get().min ) );
            }
            else if ( code === KeyboardUtil.KEY_HOME ) {
              this._valueProperty.set( this._enabledRangeProperty.get().min );
            }
            else if ( code === KeyboardUtil.KEY_END ) {
              this._valueProperty.set( this._enabledRangeProperty.get().max );
            }
          }
        },

        /**
         * Make the accessibility related aspects of this node eligible for garbage collection. Call when disposing
         * the type that this trait is mixed into.
         * @public
         */
        disposeAccessibleNumberSpinner: function() {
          this._disposeAccessibleNumberSpinner();
        }
      } );
    }
  };

  sun.register( 'AccessibleNumberSpinner', AccessibleNumberSpinner );

  return AccessibleNumberSpinner;
} );
<commit_msg>remove options from defaults that shouldn't be set by client, see #388<commit_after>// Copyright 2017, University of Colorado Boulder

/**
 * A trait for subtypes of Node, used to make the node behave like a 'number' input with assistive technology.
 * An accessible number spinner behaves like:
 *
 * - Arrow keys increment/decrement the value by a specified step size.
 * - Page Up and Page Down increments/decrements value by an alternative step size, usually larger than default.
 * - Home key sets value to its minimum.
 * - End key sets value to its maximum.
 *
 * This number spinner is different than typical 'number' inputs because it does not support number key control. All
 * changes go through the arrow, page up, page down, home, and end keys. For that reason, the HTML representation should
 * ideally look like: (values removed for readability)
 *
 *
Accessible Name

 *

 *
 * @author Jesse Greenberg (PhET Interactive Simulations)
 * @author Michael Barlow (PhET Interactive Simulations)
 */

define( function( require ) {
  'use strict';

  // modules
  var AccessiblePeer = require( 'SCENERY/accessibility/AccessiblePeer' );
  var CallbackTimer = require( 'SUN/CallbackTimer' );
  var Emitter = require( 'AXON/Emitter' );
  var extend = require( 'PHET_CORE/extend' );
  var inheritance = require( 'PHET_CORE/inheritance' );
  var KeyboardUtil = require( 'SCENERY/accessibility/KeyboardUtil' );
  var Node = require( 'SCENERY/nodes/Node' );
  var StringUtils = require( 'PHETCOMMON/util/StringUtils' );
  var sun = require( 'SUN/sun' );
  var Util = require( 'DOT/Util' );

  var AccessibleNumberSpinner = {

    /**
     * Implement functionality for a number spinner.
     * @public
     * @trait
     *
     * @param {function} type - The type (constructor) whose prototype we'll modify.
     */
    mixInto: function( type ) {
      assert && assert( _.includes( inheritance( type ), Node ) );

      var proto = type.prototype;

      extend( proto, {

        /**
         * This should be called in the constructor to initialize the accessible input features for the node.
         *
         * @param {Property.<number>} valueProperty
         * @param {Property.<Range>} enabledRangeProperty
         * @param {Property.<boolean>} enabledProperty
         * @param {Object} [options]
         *
         * @protected
         */
        initializeAccessibleNumberSpinner: function( valueProperty, enabledRangeProperty, enabledProperty, options ) {
          var self = this;

          var defaults = {
            a11yValueDelta: 1, // {number} - value changes by this quantity when using arrow keys
            a11yPageValueDelta: 2, // {number} - value changes by this quantity when using page up/page down

            a11yUseTimer: true, // if false value will change every native DOM keydown event, following options meaningless
            timerDelay: 400, // start to fire continuously after pressing for this long (milliseconds)
            timerInterval: 100, // fire continuously at this frequency (milliseconds),

            // a11y
            focusable: true,
            a11yValuePattern: '{{value}}', // {string} if you want units or additional content, add to pattern
            a11yDecimalPlaces: 0, // number of decimal places for the value read by assistive technology

            // set labelContent to give this AccessibleNumberSpinner an accessible name, required for spinbuttons
            // see https://github.com/phetsims/gravity-force-lab-basics/issues/62
            labelTagName: 'p',

            // Converts a value to a string, for extra formatting for the value read by the screen reader
            a11yFormatValue: function( value ) {
              return Util.toFixed( value, options.a11yDecimalPlaces );
            }
          };
          options = _.extend( {}, defaults, options );

          // Some options were already mutated in the constructor, only apply the accessibility-specific options here
          // so options are not doubled up, see https://github.com/phetsims/sun/issues/330
          var optionsToMutate = _.pick( options, _.keys( defaults ) );

          // cannot be set by client
          assert && assert( options.tagName === undefined, 'AccessibleNumberSpinner sets tagName' );
          optionsToMutate.tagName = 'div';

          assert && assert( options.ariaRole === undefined, 'AccessibleNumberSpinner sets ariaRole' );
          optionsToMutate.ariaRole = 'spinbutton';

          this.mutate( optionsToMutate );

          // this number spinner is "aria-labelledby" its own label, meaning that the label element content will be
          // read on focus
          this.addAriaLabelledbyAssociation( {
            thisElementName: AccessiblePeer.PRIMARY_SIBLING,
            otherNode: this,
            otherElementName: AccessiblePeer.LABEL_SIBLING
          } );

          // @private {Property.<number>}
          this._valueProperty = valueProperty;

          // @private {Property.<Range>}
          this._enabledRangeProperty = enabledRangeProperty;

          // @private{Property.<boolean>}
          this._enabledProperty = enabledProperty;

          // @private {number}
          this._a11yValueDelta = options.a11yValueDelta;

          // @private {number}
          this._a11yPageValueDelta = options.a11yPageValueDelta;

          // @private {boolean}
          this._a11yUseTimer = options.a11yUseTimer;

          // @private - manages timing must be disposed
          this._callbackTimer = new CallbackTimer( {
            delay: options.timerDelay,
            interval: options.timerInterval
          } );

          // @private {Emitter} emit events when increment/decrement keys are pressed down/up
          this.incrementDownEmitter = new Emitter();
          this.decrementDownEmitter = new Emitter();

          // @private {Emitter} - emit events when value is incremented/decremented
          this.valueIncrementEmitter = new Emitter();
          this.valueDecrementEmitter = new Emitter();

          // update enabled number range for AT, required for screen reader events to behave correctly, must be disposed
          var enabledRangeObserver = function( enabledRange ) {
            self.setAccessibleAttribute( 'min', enabledRange.min );
            self.setAccessibleAttribute( 'max', enabledRange.max );
          };
          this._enabledRangeProperty.link( enabledRangeObserver );

          // a callback that is added and removed from the timer depending on keystate
          var downCallback = null;

          // handle all accessible event input
          var accessibleInputListener = {
            keydown: function( event ) {
              self.emitKeyState( event, true );

              // if using the timer, handle update at interval
              if ( self._a11yUseTimer ) {
                if ( !self._callbackTimer.isRunning() ) {
                  self.handleKeyDown( event );

                  downCallback = self.handleKeyDown.bind( self, event );
                  self._callbackTimer.addCallback( downCallback );
                  self._callbackTimer.start();
                }
              }
              else {
                self.handleKeyDown( event );
              }
            },
            keyup: function( event ) {
              self.emitKeyState( event, false );

              if ( self._a11yUseTimer ) {
                self._callbackTimer.stop( false );
                self._callbackTimer.removeCallback( downCallback );
              }
            }
          };
          this.addAccessibleInputListener( accessibleInputListener );

          // when the property changes, be sure to update the accessible input value
          var accessiblePropertyListener = function( value ) {

            // format the value to a certain number of decimal places
            var formattedValue = options.a11yFormatValue( value );

            // wrap the value with optional string pattern
            formattedValue = StringUtils.fillIn( options.a11yValuePattern, {
              value: formattedValue
            } );

            self.setAccessibleAttribute( 'aria-valuetext', formattedValue );
            self.setAccessibleAttribute( 'aria-valuenow', value );
          };
          this._valueProperty.link( accessiblePropertyListener );

          // @private - called by disposeAccessibleNumberSpinner to prevent memory leaks
          this._disposeAccessibleNumberSpinner = function() {
            self._valueProperty.unlink( accessiblePropertyListener );
            self._enabledRangeProperty.unlink( enabledRangeObserver );
            self._callbackTimer.dispose();

            // emitters owned by this instance, can be disposed here
            self.incrementDownEmitter.dispose();
            self.decrementDownEmitter.dispose();
            self.valueIncrementEmitter.dispose();
            self.valueDecrementEmitter.dispose();

            self.removeAccessibleInputListener( accessibleInputListener );
          };
        },

        /**
         * Emit events related to the keystate of the spinner. Typically used to style the spinner during keyboard
         * interaction.
         * @private
         *
         * @param {UIEvent} event - the DOM event that was triggered
         * @param {boolean} isDown - whether or not event was triggered from down or up keys
         */
        emitKeyState: function( event, isDown ) {
          if ( event.keyCode === KeyboardUtil.KEY_UP_ARROW || event.keyCode === KeyboardUtil.KEY_RIGHT_ARROW ) {
            this.incrementDownEmitter.emit1( isDown );
          }
          else if ( event.keyCode === KeyboardUtil.KEY_DOWN_ARROW || event.keyCode === KeyboardUtil.KEY_LEFT_ARROW ) {
            this.decrementDownEmitter.emit1( isDown );
          }
        },

        /**
         * Handle the keydown event so that this node behaves like an accessible number input.
         * @private
         *
         * @param {DOMEvent} event
         */
        handleKeyDown: function( event ) {
          var code = event.keyCode;

          if ( this._enabledProperty.get() ) {

            // prevent user from changing value with number or the space keys, handle arrow keys on our own
            if ( KeyboardUtil.isArrowKey( code ) || KeyboardUtil.isNumberKey( code ) || code === KeyboardUtil.KEY_SPACE ) {
              event.preventDefault();
            }

            // handle the event
            if ( code === KeyboardUtil.KEY_RIGHT_ARROW || code === KeyboardUtil.KEY_UP_ARROW ) {
              this.valueIncrementEmitter.emit();
              this._valueProperty.set( Math.min( this._valueProperty.get() + this._a11yValueDelta, this._enabledRangeProperty.get().max ) );
            }
            else if ( code === KeyboardUtil.KEY_LEFT_ARROW || code === KeyboardUtil.KEY_DOWN_ARROW ) {
              this.valueDecrementEmitter.emit();
              this._valueProperty.set( Math.max( this._valueProperty.get() - this._a11yValueDelta, this._enabledRangeProperty.get().min ) );
            }
            else if ( code === KeyboardUtil.KEY_PAGE_UP ) {
              this._valueProperty.set( Math.min( this._valueProperty.get() + this._a11yPageValueDelta, this._enabledRangeProperty.get().max ) );
            }
            else if ( code === KeyboardUtil.KEY_PAGE_DOWN ) {
              this._valueProperty.set( Math.max( this._valueProperty.get() - this._a11yPageValueDelta, this._enabledRangeProperty.get().min ) );
            }
            else if ( code === KeyboardUtil.KEY_HOME ) {
              this._valueProperty.set( this._enabledRangeProperty.get().min );
            }
            else if ( code === KeyboardUtil.KEY_END ) {
              this._valueProperty.set( this._enabledRangeProperty.get().max );
            }
          }
        },

        /**
         * Make the accessibility related aspects of this node eligible for garbage collection. Call when disposing
         * the type that this trait is mixed into.
         * @public
         */
        disposeAccessibleNumberSpinner: function() {
          this._disposeAccessibleNumberSpinner();
        }
      } );
    }
  };

  sun.register( 'AccessibleNumberSpinner', AccessibleNumberSpinner );

  return AccessibleNumberSpinner;
} );
<|endoftext|>