<commit_before>class TableDetailComponent {

    /**
     * TableDetailComponent
     * @constructor
     * @param {jQuery} $html - jQuery wrapper of the html node
     */
    constructor ($html) {
        this.$html = $html;
    }

    /**
     * Initialise
     */
    init () {
        if (typeof this.$html === 'undefined' || !this.$html.length) {
            throw new Error('$html must be passed to TableDetailComponent');
        }

        let $panelHtml = $(
            '
' +
            '
' +
            '       <button type="button" class="close table-detail__header-close" data-table-detail-close-panel aria-label="Close">×</button>' +
            '
Detail
' +
            '
' +
            '

' +
            '
'
            ),
            $elementToAppendTo,
            $containerHasMainElement = this.$html.find('main'),
            $containerHasRoleMainElement = this.$html.find('[role="main"]'),
            $triggeringElement;

        this.$table = this.$html.find('[data-table-detail-table]');

        // If main or role="main" is present append alerts to that (to satify WCAG 1.3.1 Info and Relationships)
        if ($containerHasMainElement.length) {
            $elementToAppendTo = $containerHasMainElement;
        } else if ($containerHasRoleMainElement.length) {
            $elementToAppendTo = $containerHasRoleMainElement;
        } else {
            $elementToAppendTo = this.$html.find('body');
        }

        // Add backdrop and detail panel if UI contains a table detail pattern
        if (this.$table.length) {
            $elementToAppendTo
                .append('

')
                .append($panelHtml);
        }

        // Grab detail panel bits we need
        this.$detailPanel = this.$html.find('[data-table-detail-panel]');
        this.$detailPanelBody = this.$html.find('[data-table-detail-panel-body]');
        this.$detailPanelTitle = this.$html.find('[data-table-detail-panel-title]');
        this.$tableDetailBackdrop = this.$html.find('.table-detail-backdrop');

        // Open click listener
        this.$table.find('[data-table-detail-view-detail]').on('click', (event) => {
            event.preventDefault();
            let detailContent = $(event.currentTarget).closest('tr').data('table-detail-content');
            let customDetailPanelTitle = $(event.currentTarget).closest('tr').data('table-detail-panel-custom-title');
            $triggeringElement = $(event.target);

            this.viewDetail(detailContent, customDetailPanelTitle);
        });

        // Close click listener
        this.$detailPanel.find('[data-table-detail-close-panel]').on('click', (event) => {
            event.preventDefault();
            this.closeDetail();
            $triggeringElement.focus();
        });

        // Close with backdrop click
        this.$tableDetailBackdrop.on('click', (event) => {
            event.preventDefault();
            if (this.$tableDetailBackdrop.hasClass('in')) {
                this.closeDetail();
                $triggeringElement.focus();
            }
        });

        // Close ESC button
        this.$html.on('keydown', (event) => {
            if (event.keyCode == 27) {
                this.closeDetail();
                $triggeringElement.focus();
            };
        });
    }
    /**
     * Show detail panel and populate with content and optional custom title
     * @param {String} content - string of html content to populate the detail panel body with
     * @param {String} customDetailPanelTitle - optinal custom detail panel title specific to the row clicked
     */
    viewDetail (content, customDetailPanelTitle) {
        // Set the title if present
        if (customDetailPanelTitle) {
            this.$detailPanelTitle.html(customDetailPanelTitle);
        }

        // Remove any previously added contents
        this.$detailPanelBody.empty();

        // Add attached data to detail panel body
        this.$detailPanelBody.html(content);

        // Apply backdrop
        this.$tableDetailBackdrop.addClass('in');

        // Open panel
        this.$detailPanel.addClass('table-detail--open');

        // Trap focus within the panel
        this.trapFocus();
    }

    /**
     * Hide detail panel and remove backdrop
     */
    closeDetail () {
        // Remove backdrop
        this.$tableDetailBackdrop.removeClass('in');

        // Close panel
        this.$detailPanel.removeClass('table-detail--open');
    }

    /**
     * Listen for keyboard navigation, trap tabbing within the panels focusable elements
     * @param {jQuery} $focusableElements - Collection of focusable elements in the details panel
     */
    keydownListener ($focusableElements, event) {
        let keyCode = event.keyCode || event.which;

        // If tab key is pressed
        if (keyCode === 9) {
            // Check for shift tab
            if (event.shiftKey) {
                // Focus previous, check if first element is is currently in focus, if so focus last element
                if ($focusableElements.first().is(':focus')) {
                    event.preventDefault();
                    $focusableElements.last().focus();
                }
            } else {
                // Focus next, check if last element is is currently in focus, if so focus first element
                if ($focusableElements.last().is(':focus')) {
                    event.preventDefault();
                    $focusableElements.first().focus();
                }
            }
        }
    }

    /**
     * Trap keyboard focus in the panel
     */
    trapFocus () {
        let $focusableElements = this.$detailPanel
                .find('a[href], area[href], input, select, textarea, button, iframe, object, embed, [tabindex], *[contenteditable]')
                .not('[tabindex=-1], [disabled], :hidden, [aria-hidden]'),
            $panelHasForm = this.$detailPanelBody.find('form').length > 0;

        // If modal contains a form, we should focus the first field
        if ($panelHasForm) {
            this.$detailPanelBody.find('form :input:not([disabled]):not([aria-hidden]):visible:first').focus();
        } else {
            this.$detailPanel.find('[data-table-detail-close-panel]').focus();
        }

        this.$html.on('keydown', this.keydownListener.bind(this, $focusableElements));
    }
}

module.exports = TableDetailComponent;
<commit_msg>Taming the hound 🐕<commit_after>class TableDetailComponent {

    /**
     * TableDetailComponent
     * @constructor
     * @param {jQuery} $html - jQuery wrapper of the html node
     */
    constructor ($html) {
        this.$html = $html;
    }

    /**
     * Initialise
     */
    init () {
        if (typeof this.$html === 'undefined' || !this.$html.length) {
            throw new Error('$html must be passed to TableDetailComponent');
        }

        let $panelHtml = $(
            '
' +
            '
' +
            '       <button type="button" class="close table-detail__header-close" data-table-detail-close-panel aria-label="Close">×</button>' +
            '
Detail
' +
            '
' +
            '

' +
            '
'
            ),
            $elementToAppendTo,
            $containerHasMainElement = this.$html.find('main'),
            $containerHasRoleMainElement = this.$html.find('[role="main"]'),
            $triggeringElement;

        this.$table = this.$html.find('[data-table-detail-table]');

        // If main or role="main" is present append alerts to that (to satify WCAG 1.3.1 Info and Relationships)
        if ($containerHasMainElement.length) {
            $elementToAppendTo = $containerHasMainElement;
        } else if ($containerHasRoleMainElement.length) {
            $elementToAppendTo = $containerHasRoleMainElement;
        } else {
            $elementToAppendTo = this.$html.find('body');
        }

        // Add backdrop and detail panel if UI contains a table detail pattern
        if (this.$table.length) {
            $elementToAppendTo
                .append('

')
                .append($panelHtml);
        }

        // Grab detail panel bits we need
        this.$detailPanel = this.$html.find('[data-table-detail-panel]');
        this.$detailPanelBody = this.$html.find('[data-table-detail-panel-body]');
        this.$detailPanelTitle = this.$html.find('[data-table-detail-panel-title]');
        this.$tableDetailBackdrop = this.$html.find('.table-detail-backdrop');

        // Open click listener
        this.$table.find('[data-table-detail-view-detail]').on('click', (event) => {
            event.preventDefault();
            let detailContent = $(event.currentTarget).closest('tr').data('table-detail-content');
            let customDetailPanelTitle = $(event.currentTarget).closest('tr').data('table-detail-panel-custom-title');
            $triggeringElement = $(event.target);

            this.viewDetail(detailContent, customDetailPanelTitle);
        });

        // Close click listener
        this.$detailPanel.find('[data-table-detail-close-panel]').on('click', (event) => {
            event.preventDefault();
            this.closeDetail();
            $triggeringElement.focus();
        });

        // Close with backdrop click
        this.$tableDetailBackdrop.on('click', (event) => {
            event.preventDefault();
            if (this.$tableDetailBackdrop.hasClass('in')) {
                this.closeDetail();
                $triggeringElement.focus();
            }
        });

        // Close ESC button
        this.$html.on('keydown', (event) => {
            if (event.keyCode === 27) {
                this.closeDetail();
                $triggeringElement.focus();
            }
        });
    }
    /**
     * Show detail panel and populate with content and optional custom title
     * @param {String} content - string of html content to populate the detail panel body with
     * @param {String} customDetailPanelTitle - optinal custom detail panel title specific to the row clicked
     */
    viewDetail (content, customDetailPanelTitle) {
        // Set the title if present
        if (customDetailPanelTitle) {
            this.$detailPanelTitle.html(customDetailPanelTitle);
        }

        // Remove any previously added contents
        this.$detailPanelBody.empty();

        // Add attached data to detail panel body
        this.$detailPanelBody.html(content);

        // Apply backdrop
        this.$tableDetailBackdrop.addClass('in');

        // Open panel
        this.$detailPanel.addClass('table-detail--open');

        // Trap focus within the panel
        this.trapFocus();
    }

    /**
     * Hide detail panel and remove backdrop
     */
    closeDetail () {
        // Remove backdrop
        this.$tableDetailBackdrop.removeClass('in');

        // Close panel
        this.$detailPanel.removeClass('table-detail--open');
    }

    /**
     * Listen for keyboard navigation, trap tabbing within the panels focusable elements
     * @param {jQuery} $focusableElements - Collection of focusable elements in the details panel
     */
    keydownListener ($focusableElements, event) {
        let keyCode = event.keyCode || event.which;

        // If tab key is pressed
        if (keyCode === 9) {
            // Check for shift tab
            if (event.shiftKey) {
                // Focus previous, check if first element is is currently in focus, if so focus last element
                if ($focusableElements.first().is(':focus')) {
                    event.preventDefault();
                    $focusableElements.last().focus();
                }
            } else {
                // Focus next, check if last element is is currently in focus, if so focus first element
                if ($focusableElements.last().is(':focus')) {
                    event.preventDefault();
                    $focusableElements.first().focus();
                }
            }
        }
    }

    /**
     * Trap keyboard focus in the panel
     */
    trapFocus () {
        let $focusableElements = this.$detailPanel
                .find('a[href], area[href], input, select, textarea, button, iframe, object, embed, [tabindex], *[contenteditable]')
                .not('[tabindex=-1], [disabled], :hidden, [aria-hidden]'),
            $panelHasForm = this.$detailPanelBody.find('form').length > 0;

        // If modal contains a form, we should focus the first field
        if ($panelHasForm) {
            this.$detailPanelBody.find('form :input:not([disabled]):not([aria-hidden]):visible:first').focus();
        } else {
            this.$detailPanel.find('[data-table-detail-close-panel]').focus();
        }

        this.$html.on('keydown', this.keydownListener.bind(this, $focusableElements));
    }
}

module.exports = TableDetailComponent;
<|endoftext|>


<commit_before>//Translated from connector.rb using babyruby2js
'use strict';

var main = require('../../main');
var Grid = require('../../Grid');
var Heuristic = require('./Heuristic');
var inherits = require('util').inherits;

var EMPTY = main.EMPTY, BORDER = main.BORDER;
var ALWAYS = main.ALWAYS, NEVER = main.NEVER;

/*
TODO:
- Fix under-evaluation in cases we could handle better:
  # When we see a group is "SOMETIMES" dead, we consider the connection/cut as
    a 0.5 win; in case where the connection/cut is precisely the saving/killing stone,
    we should count a full win instead.
  # See test TestAi#testConnect: the connection is actually deciding life/death of more
    than the 2 groups we look at: the 2 stones group is a brother of another group
    which will be saved/dead too depending on this connection.
- Merge "direct" and "diagonal" algos to do it right
- One other way to connect 2 groups is to "protect" the cutting point; handle this here
- When we try to "cut" (enemy color), eval should give 0 if another way of connecting exists
*/

/** @class A move that connects 2 of our groups is good.
 */
function Connector(player) {
    Heuristic.call(this, player);

    this.inflCoeff = this.getGene('infl', 0.07, 0.01, 0.5);
    this.riskCoeff = this.getGene('risk', 1, 0.1, 4.0);

    this.noEasyPrisonerYx = player.getHeuristic('NoEasyPrisoner').scoreGrid.yx;
    this.hunter = player.getHeuristic('Hunter');
}
inherits(Connector, Heuristic);
module.exports = Connector;

Connector.prototype._evalMove = function (i, j, color) {
    // If our stone would simply be captured, no luck
    var stone = this.goban.stoneAt(i, j);
    if (this.noEasyPrisonerYx[j][i] < 0 && !this.hunter.isSnapback(stone)) {
        if (main.debug) main.log.debug('Connector ' + Grid.colorName(color) + ' skips ' + stone + ' (trusting NoEasyPrisoner)');
        return 0;
    }
    // Score for connecting our groups + cutting enemies
    return this._connectsMyGroups(stone, color) +
           this._connectsMyGroups(stone, 1 - color);
};

function groupNeedsToConnect(g) {
    var gi = g._info;
    return gi.eyeCount === 0 && gi.numContactPoints === 1;
}

Connector.prototype._diagonalConnect = function (stone, color) {
    var diag = true, grp1 = null, grp2 = null, nonDiagGrp1 = null;
    var isDiagCon = false;
    var numEnemies = 0;
    for (var n = 0; n < 8; n++) {
        var s = stone.allNeighbors[n];
        diag = !diag;
        if (s === BORDER) continue;
        switch (s.color) {
        case EMPTY: continue;
        case color:
            if (!grp1) {
                grp1 = s.group;
                if (!diag) nonDiagGrp1 = s.group;
                isDiagCon = diag;
            } else {
                if (s.group === grp1) continue; // ignore if other stone of same group
                if (!diag && nonDiagGrp1 && s.group !== nonDiagGrp1) return 0; // decline direct connections
                if (!diag && !nonDiagGrp1) nonDiagGrp1 = s.group;
                grp2 = s.group;
                isDiagCon = isDiagCon || diag;
            }
            break;
        default: numEnemies++;
        }
    }
    if (!grp2) return 0;
    if (!isDiagCon)
        return 0;
    if (numEnemies >= 3)
        return 0; //TODO improve this
    return this._computeScore(stone, color, [grp1, grp2]/*REVIEW THIS*/, numEnemies, 'diagonal');
};

Connector.prototype._directConnect = function (stone, color) {
    var s1, s1b, s2, s2b, s3;
    var numStones = 0, numEnemies = 0;
    for (var n = stone.neighbors.length - 1; n >= 0; n--) {
        var s = stone.neighbors[n];
        switch (s.color) {
        case EMPTY: continue;
        case color:
            numStones++;
            if (!s1) {
                s1 = s;
            } else if (!s2) {
                if (s.group !== s1.group) s2 = s; else s1b = s;
            } else {
                if (s.group !== s2.group) s3 = s; else s2b = s;
            }
            break;
        default: numEnemies++;
        }
    }

    if (!s2) return 0; // nothing to connect here
    if (numStones === 4) return 0; // 1 empty between 4 stones; never connect unless forced to
    // 3 of our stones around: no need to connect unless enemy comes by or threatens
    if (numStones === 3 && numEnemies === 0 &&
        s1.group.lives > 1 && s2.group.lives > 1 && (!s3 || s3.group.lives > 1)) {
        return 0;
    }

    var numGroups = s3 ? 3 : 2;
    var groups = s3 ? [s1.group, s2.group, s3.group] : [s1.group, s2.group];
    // if 3rd stone in same group than 1 or 2; we keep the diagonal ones
    if (numGroups === 2 && numStones === 3) {
        if (s2b) { s1b = s2b; var swap = s1; s1 = s2; s2 = swap; }
        if (s1.i === s2.i || s1.j === s2.j) s1 = s1b;
    }
    // Case of diagonal (strong) stones
    if (s1.i !== s2.i && s1.j !== s2.j) {
        // no need to connect now if connection is granted
        if (this.distanceBetweenStones(s1, s2, color) === 0) {
            if (main.debug) main.log.debug('Connector ' + Grid.colorName(color) + ' sees no hurry to connect ' + s1 + ' and ' + s2);
            if (groupNeedsToConnect(s1.group) || groupNeedsToConnect(s2.group))
                return this.minimumScore;
            return 0;
        }
        // We count the cutting stone as enemy (we did not "see" it above because it's diagonal)
        numEnemies++;
    }
    return this._computeScore(stone, color, groups, numEnemies, 'direct');
};

Connector.prototype._computeScore = function (stone, color, groups, numEnemies, desc) {
    var score = 0;
    if (numEnemies === 0) {
        score = this.inflCoeff / this.infl[color][stone.j][stone.i];
    } else {
        var someAlive = false, g;
        for (var n = groups.length - 1; n >= 0; n--) {
            g = groups[n];
            // lives 1 or 2 are counted by Hunter/Savior; TODO: centralize how this is counted
            if (g.lives <= 2 && g.xAlive < ALWAYS) return 0;
            if (g.xDead < ALWAYS || g.xAlive > NEVER) {
                someAlive = true;
                break;
            }
        }
        if (!someAlive) return 0; // don't try to connect dead groups

        for (n = groups.length - 1; n >= 0; n--) {
            g = groups[n];
            if (g.xDead === NEVER) continue;
            score += (2 - g.xAlive) / 2 * this.groupThreat(g, /*saved=*/true); // !saved would not work so well I think
        }
        score *= this.riskCoeff;
    }
    if (main.debug) main.log.debug('Connector ' + desc + ' for ' + Grid.colorName(color) + ' gives ' +
        score.toFixed(3) + ' to ' + stone + ' (allies:' + groups.length + ' enemies: ' + numEnemies + ')');
    return score;
};

Connector.prototype._connectsMyGroups = function (stone, color) {
    var score = this._directConnect(stone, color);
    if (score) return score;
    return this._diagonalConnect(stone, color);
};
<commit_msg>minor: simplified condition in Connector<commit_after>//Translated from connector.rb using babyruby2js
'use strict';

var main = require('../../main');
var Grid = require('../../Grid');
var Heuristic = require('./Heuristic');
var inherits = require('util').inherits;

var EMPTY = main.EMPTY, BORDER = main.BORDER;
var ALWAYS = main.ALWAYS, NEVER = main.NEVER;

/*
TODO:
- Fix under-evaluation in cases we could handle better:
  # When we see a group is "SOMETIMES" dead, we consider the connection/cut as
    a 0.5 win; in case where the connection/cut is precisely the saving/killing stone,
    we should count a full win instead.
  # See test TestAi#testConnect: the connection is actually deciding life/death of more
    than the 2 groups we look at: the 2 stones group is a brother of another group
    which will be saved/dead too depending on this connection.
- Merge "direct" and "diagonal" algos to do it right
- One other way to connect 2 groups is to "protect" the cutting point; handle this here
- When we try to "cut" (enemy color), eval should give 0 if another way of connecting exists
*/

/** @class A move that connects 2 of our groups is good.
 */
function Connector(player) {
    Heuristic.call(this, player);

    this.inflCoeff = this.getGene('infl', 0.07, 0.01, 0.5);
    this.riskCoeff = this.getGene('risk', 1, 0.1, 4.0);

    this.noEasyPrisonerYx = player.getHeuristic('NoEasyPrisoner').scoreGrid.yx;
    this.hunter = player.getHeuristic('Hunter');
}
inherits(Connector, Heuristic);
module.exports = Connector;

Connector.prototype._evalMove = function (i, j, color) {
    // If our stone would simply be captured, no luck
    var stone = this.goban.stoneAt(i, j);
    if (this.noEasyPrisonerYx[j][i] < 0 && !this.hunter.isSnapback(stone)) {
        if (main.debug) main.log.debug('Connector ' + Grid.colorName(color) + ' skips ' + stone + ' (trusting NoEasyPrisoner)');
        return 0;
    }
    // Score for connecting our groups + cutting enemies
    return this._connectsMyGroups(stone, color) +
           this._connectsMyGroups(stone, 1 - color);
};

function groupNeedsToConnect(g) {
    var gi = g._info;
    return gi.eyeCount === 0 && gi.numContactPoints === 1;
}

Connector.prototype._diagonalConnect = function (stone, color) {
    var diag = true, grp1 = null, grp2 = null, nonDiagGrp1 = null;
    var isDiagCon = false;
    var numEnemies = 0;
    for (var n = 0; n < 8; n++) {
        var s = stone.allNeighbors[n];
        diag = !diag;
        if (s === BORDER) continue;
        switch (s.color) {
        case EMPTY: continue;
        case color:
            if (!grp1) {
                grp1 = s.group;
                if (!diag) nonDiagGrp1 = s.group;
                isDiagCon = diag;
            } else {
                if (s.group === grp1) continue; // ignore if other stone of same group
                if (!diag && nonDiagGrp1 && s.group !== nonDiagGrp1) return 0; // decline direct connections
                if (!diag && !nonDiagGrp1) nonDiagGrp1 = s.group;
                grp2 = s.group;
                isDiagCon = isDiagCon || diag;
            }
            break;
        default: numEnemies++;
        }
    }
    if (!grp2) return 0;
    if (!isDiagCon)
        return 0;
    if (numEnemies >= 3)
        return 0; //TODO improve this
    return this._computeScore(stone, color, [grp1, grp2]/*REVIEW THIS*/, numEnemies, 'diagonal');
};

Connector.prototype._directConnect = function (stone, color) {
    var s1, s1b, s2, s2b, s3;
    var numStones = 0, numEnemies = 0;
    for (var n = stone.neighbors.length - 1; n >= 0; n--) {
        var s = stone.neighbors[n];
        switch (s.color) {
        case EMPTY: continue;
        case color:
            numStones++;
            if (!s1) {
                s1 = s;
            } else if (!s2) {
                if (s.group !== s1.group) s2 = s; else s1b = s;
            } else {
                if (s.group !== s2.group) s3 = s; else s2b = s;
            }
            break;
        default: numEnemies++;
        }
    }

    if (!s2) return 0; // nothing to connect here
    if (numStones === 4) return 0; // 1 empty between 4 stones; never connect unless forced to
    // 3 of our stones around: no need to connect unless enemy comes by or threatens
    if (numStones === 3 && numEnemies === 0 &&
        s1.group.lives > 1 && s2.group.lives > 1 && (!s3 || s3.group.lives > 1)) {
        return 0;
    }

    var numGroups = s3 ? 3 : 2;
    var groups = s3 ? [s1.group, s2.group, s3.group] : [s1.group, s2.group];
    // if 3rd stone in same group than 1 or 2; we keep the diagonal ones
    if (numGroups === 2 && numStones === 3) {
        if (s2b) { s1b = s2b; var swap = s1; s1 = s2; s2 = swap; }
        if (s1.i === s2.i || s1.j === s2.j) s1 = s1b;
    }
    // Case of diagonal (strong) stones
    if (s1.i !== s2.i && s1.j !== s2.j) {
        // no need to connect now if connection is granted
        if (this.distanceBetweenStones(s1, s2, color) === 0) {
            if (main.debug) main.log.debug('Connector ' + Grid.colorName(color) + ' sees no hurry to connect ' + s1 + ' and ' + s2);
            if (groupNeedsToConnect(s1.group) || groupNeedsToConnect(s2.group))
                return this.minimumScore;
            return 0;
        }
        // We count the cutting stone as enemy (we did not "see" it above because it's diagonal)
        numEnemies++;
    }
    return this._computeScore(stone, color, groups, numEnemies, 'direct');
};

Connector.prototype._computeScore = function (stone, color, groups, numEnemies, desc) {
    var score = 0;
    if (numEnemies === 0) {
        score = this.inflCoeff / this.infl[color][stone.j][stone.i];
    } else {
        var someAlive = false, g;
        for (var n = groups.length - 1; n >= 0; n--) {
            g = groups[n];
            // lives 1 or 2 are counted by Hunter/Savior; TODO: centralize how this is counted
            if (g.lives <= 2 && g.xAlive < ALWAYS) return 0;
            if (g.xDead < ALWAYS) {
                someAlive = true;
                break;
            }
        }
        if (!someAlive) return 0; // don't try to connect dead groups

        for (n = groups.length - 1; n >= 0; n--) {
            g = groups[n];
            if (g.xDead === NEVER) continue;
            score += (2 - g.xAlive) / 2 * this.groupThreat(g, /*saved=*/true); // !saved would not work so well I think
        }
        score *= this.riskCoeff;
    }
    if (main.debug) main.log.debug('Connector ' + desc + ' for ' + Grid.colorName(color) + ' gives ' +
        score.toFixed(3) + ' to ' + stone + ' (allies:' + groups.length + ' enemies: ' + numEnemies + ')');
    return score;
};

Connector.prototype._connectsMyGroups = function (stone, color) {
    var score = this._directConnect(stone, color);
    if (score) return score;
    return this._diagonalConnect(stone, color);
};
<|endoftext|>


<commit_before>requirejs(['jquery', 'bootstrap', 'app/spreadsheet_utility', 'app/spreadsheet_equations', 'app/spreadsheet_calculator', 'app/spreadsheet_table'], function($, bootstrap, SpreadsheetUtility, SpreadsheetEquations, SpreadsheetCalculator, SpreadsheetTable) {

    // The entire application is namespaced behind this App variable (Singleton)
    var App = new function() {

        /*
        =================================================================
        APP VARIABLES
        =================================================================
        */

        var $spreadsheet = $('#spreadsheet');

        var spreadsheet = document.getElementById('spreadsheet');

        // Helper class for equation calculator
        var spreadsheetEquations = new SpreadsheetEquations();

        var equationCalculator = new SpreadsheetCalculator(spreadsheetEquations);

        var spreadsheetTable = new SpreadsheetTable();

        // Used for formatting cells
        var $selectedCell = null;

        /*
        =================================================================
        BUSINESS LOGIC
        =================================================================
        */

        var setUpSpreadSheetTable = function() {
            var size = 50;
            spreadsheetTable.createTable(spreadsheet, size, size);
        };

        /* Listeners */

        var onFocusInputListener = function($inputCells) {
            // On selection of cell
            $inputCells.focus(function() {
                $selectedCell = $(this);

                // Show equation
                var equationAttr = spreadsheetTable.ATTRS.EQUATION;
                if (SpreadsheetUtility.hasAttr($selectedCell.attr(equationAttr))) {
                    $selectedCell.val($selectedCell.attr(equationAttr));
                }
            });
        };

        var onEnterKeyListener = function($inputCells) {
            $inputCells.keyup(function(e) {
                // Enter key
                if (e.which == 13) {
                    $(this).blur();
                }
            });
        };

        var onBlurInputListener = function($inputCells) {
            // On leaving the cell
            $inputCells.blur(function() {
                // Save
                spreadsheetTable.storeCellData($(this));
                // Update equation cells
                equationCalculator.calculateAllEquations(spreadsheetTable);
            });
        };

        /* Button Setups */

        var setUpReloadButton = function() {
            var loadData;

            $('#reload-button').click(function() {
                // clear data
                loadData = $('#spreadsheet tr').detach();
                setTimeout(function() {
                    // Reload the HTML elements
                    loadData.appendTo($spreadsheet);
                }, 50);

            });
        };

        var toggleClassOnButtonClick = function(buttonId, className) {
            $(buttonId).click(function() {
                if ($selectedCell === null) {
                    return;
                } else if ($selectedCell.hasClass(className)) {
                    $selectedCell.removeClass(className);
                } else {
                    $selectedCell.addClass(className);
                }
            });
        };

        var removeAlignmentClasses = function() {
            // alignment classes
            var alignClasses = ['align-left','align-center','align-right'];
            if ($selectedCell !== null) {
                for (var i = 0; i < alignClasses.length; i++) {
                    $selectedCell.removeClass(alignClasses[i]);
                }
            }
        };

        var toggleAlignmentOnButtonClick = function(buttonId, className) {
            $(buttonId).click(function() {
                if ($selectedCell === null) {
                    return;
                } else {
                    removeAlignmentClasses();
                    $selectedCell.addClass(className);
                }
            });
        };

        var setUpBoldButton = function() {
            toggleClassOnButtonClick('#bold-button', 'bold');
        };

        var setUpItalicsButton = function() {
            toggleClassOnButtonClick('#italics-button', 'italics');
        };

        var setUpUnderlineButton = function() {
            toggleClassOnButtonClick('#underline-button', 'under-line');
        };

        var setUpAlignLeftButton = function() {
            toggleAlignmentOnButtonClick('#align-left-button', 'align-left');
        };

        var setUpAlignCenterButton = function() {
            toggleAlignmentOnButtonClick('#align-center-button', 'align-center');
        };

        var setUpAlignRightButton = function() {
            toggleAlignmentOnButtonClick('#align-right-button', 'align-right');
        };

        /*
        =================================================================
        APP BUSINESS LOGIC INITIALISATION
        =================================================================
        */

        var buttonsInit = function() {
            setUpReloadButton();
            setUpBoldButton();
            setUpItalicsButton();
            setUpUnderlineButton();
            setUpAlignLeftButton();
            setUpAlignCenterButton();
            setUpAlignRightButton();
        };

        var spreadsheetInit = function() {
            setUpSpreadSheetTable();
        };

        var listenersInit = function() {
            var $inputCells = $('.inputCell');

            onBlurInputListener($inputCells);
            onFocusInputListener($inputCells);
            onEnterKeyListener($inputCells);
        };

        return {
            init: function() {
                buttonsInit();      // Initialise Buttons
                spreadsheetInit();  // Initialise App code
                listenersInit();    // Initialise all listeners
            }
        };
    };

    /* Initialise app when page loads */
    $(function() {
        App.init();
    });

    return App;
});<commit_msg>refactored button initialisers<commit_after>requirejs(['jquery', 'bootstrap', 'app/spreadsheet_utility', 'app/spreadsheet_equations', 'app/spreadsheet_calculator', 'app/spreadsheet_table'], function($, bootstrap, SpreadsheetUtility, SpreadsheetEquations, SpreadsheetCalculator, SpreadsheetTable) {

    // The entire application is namespaced behind this App variable (Singleton)
    var App = new function() {

        /*
        =================================================================
        APP VARIABLES
        =================================================================
        */

        var $spreadsheet = $('#spreadsheet');

        var spreadsheet = document.getElementById('spreadsheet');

        // Helper class for equation calculator
        var spreadsheetEquations = new SpreadsheetEquations();

        var equationCalculator = new SpreadsheetCalculator(spreadsheetEquations);

        var spreadsheetTable = new SpreadsheetTable();

        // Used for formatting cells
        var $selectedCell = null;

        /*
        =================================================================
        BUSINESS LOGIC
        =================================================================
        */

        var setUpSpreadSheetTable = function() {
            var size = 50;
            spreadsheetTable.createTable(spreadsheet, size, size);
        };

        /* Listeners */

        var onFocusInputListener = function($inputCells) {
            // On selection of cell
            $inputCells.focus(function() {
                $selectedCell = $(this);

                // Show equation
                var equationAttr = spreadsheetTable.ATTRS.EQUATION;
                if (SpreadsheetUtility.hasAttr($selectedCell.attr(equationAttr))) {
                    $selectedCell.val($selectedCell.attr(equationAttr));
                }
            });
        };

        var onEnterKeyListener = function($inputCells) {
            $inputCells.keyup(function(e) {
                // Enter key
                if (e.which == 13) {
                    $(this).blur();
                }
            });
        };

        var onBlurInputListener = function($inputCells) {
            // On leaving the cell
            $inputCells.blur(function() {
                // Save
                spreadsheetTable.storeCellData($(this));
                // Update equation cells
                equationCalculator.calculateAllEquations(spreadsheetTable);
            });
        };

        /* Button Setups */

        var setUpReloadButton = function() {
            var loadData;

            $('#reload-button').click(function() {
                // clear data
                loadData = $('#spreadsheet tr').detach();
                setTimeout(function() {
                    // Reload the HTML elements
                    loadData.appendTo($spreadsheet);
                }, 50);

            });
        };

        var toggleClassOnButtonClick = function(buttonId, className) {
            $(buttonId).click(function() {
                if ($selectedCell === null) {
                    return;
                } else if ($selectedCell.hasClass(className)) {
                    $selectedCell.removeClass(className);
                } else {
                    $selectedCell.addClass(className);
                }
            });
        };

        var removeAlignmentClasses = function() {
            // alignment classes
            var alignClasses = ['align-left','align-center','align-right'];
            if ($selectedCell !== null) {
                for (var i = 0; i < alignClasses.length; i++) {
                    $selectedCell.removeClass(alignClasses[i]);
                }
            }
        };

        var toggleAlignmentOnButtonClick = function(buttonId, className) {
            $(buttonId).click(function() {
                if ($selectedCell === null) {
                    return;
                } else {
                    removeAlignmentClasses();
                    $selectedCell.addClass(className);
                }
            });
        };

        var setUpFormatterButtons = function() {
            toggleClassOnButtonClick('#bold-button', 'bold');
            toggleClassOnButtonClick('#italics-button', 'italics');
            toggleClassOnButtonClick('#underline-button', 'under-line');
        };

        var setUpAlignmentButtons = function() {
            toggleAlignmentOnButtonClick('#align-left-button', 'align-left');
            toggleAlignmentOnButtonClick('#align-center-button', 'align-center');
            toggleAlignmentOnButtonClick('#align-right-button', 'align-right');
        };

        /*
        =================================================================
        APP BUSINESS LOGIC INITIALISATION
        =================================================================
        */

        var buttonsInit = function() {
            setUpReloadButton();
            setUpFormatterButtons();
            setUpAlignmentButtons();
        };

        var spreadsheetInit = function() {
            setUpSpreadSheetTable();
        };

        var listenersInit = function() {
            var $inputCells = $('.inputCell');

            onBlurInputListener($inputCells);
            onFocusInputListener($inputCells);
            onEnterKeyListener($inputCells);
        };

        return {
            init: function() {
                buttonsInit();      // Initialise Buttons
                spreadsheetInit();  // Initialise App code
                listenersInit();    // Initialise all listeners
            }
        };
    };

    /* Initialise app when page loads */
    $(function() {
        App.init();
    });

    return App;
});<|endoftext|>


<commit_before>var Backbone   = require('backbone'),
    $          = require('jquery'),
    _          = require('underscore');

function navigate(categories, keyWords) {
  var route  = 'query?categories=' + categories.join(',') + '&search=' + encodeURIComponent(keyWords,
      router = require('routers/router').instance;

  router.navigate(route, { trigger: true });
}

var IndexView = Backbone.View.extend({
  template: require('templates/index'),

  events: {
    'click .search-button': 'submit',
    'click ul.categories button': 'toggleCheckbox'
  },

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

  toggleCheckbox: function(event) {
    $(event.target).find('.icon-ok').toggle();
  },

  submit: function(event) {
    var category,
        categories   = [],
        keyWords     = this.$('#search_name').val(),
        visibleIcons = this.$('.category .icon-ok:visible');

    _.each(visibleIcons, function(icon) {
      category = $(icon).closest('button').data('category');
      categories.push(category);
    });

    navigate(categories, keyWords);
  }
});

module.exports = IndexView;
<commit_msg>fixed syntax error<commit_after>var Backbone   = require('backbone'),
    $          = require('jquery'),
    _          = require('underscore');

function navigate(categories, keyWords) {
  var route  = 'query?categories=' + categories.join(',') + '&search=' + encodeURIComponent(keyWords),
      router = require('routers/router').instance;

  router.navigate(route, { trigger: true });
}

var IndexView = Backbone.View.extend({
  template: require('templates/index'),

  events: {
    'click .search-button': 'submit',
    'click ul.categories button': 'toggleCheckbox'
  },

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

  toggleCheckbox: function(event) {
    $(event.target).find('.icon-ok').toggle();
  },

  submit: function(event) {
    var category,
        categories   = [],
        keyWords     = this.$('#search_name').val(),
        visibleIcons = this.$('.category .icon-ok:visible');

    _.each(visibleIcons, function(icon) {
      category = $(icon).closest('button').data('category');
      categories.push(category);
    });

    navigate(categories, keyWords);
  }
});

module.exports = IndexView;
<|endoftext|>


<commit_before>function pageViewModel(gvm) {
    // projecttitle
    gvm.projecttitle = ko.observable("");
    gvm.projectId = $("#projectHeader").data('value');

    // Page specific i18n bindings
    gvm.title = ko.computed(function (){i18n.setLocale(gvm.lang()); return gvm.app() + ' - ' + i18n.__("ProjectTitle") + ": " + gvm.projecttitle();}, gvm);
    gvm.pageHeader = ko.observable("Project");
    gvm.projectname = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("ProjectName");}, gvm);
    gvm.menuLanguage = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("MenuLanguage");}, gvm);
    gvm.savePage = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("SaveBtn");}, gvm);

    gvm.competences = ko.observableArray([]);

    gvm.getProjectInfo = function() {
        $.getJSON('/api/project/' + gvm.projectId, function(data) {
            gvm.pageHeader(data[0].code + ' - ' + data[0].name);
        });
    };

    gvm.getAllData = function() {
         $.getJSON('/api/project/getAllData/' + gvm.projectId, function(data) {
            console.log(data);
         });
    };

    gvm.updateCompetence = function(id, code, description, max, weight) {
        var comp = new Competence(this, id, code, description, weight);
        gvm.competences.push(comp);
        return comp;
    };

    gvm.clearStructure = function() {
        gvm.competences.destroyAll();
    };
}

function initPage() {
    fetchProjectStructure();
    viewModel.getProjectInfo();

    $(".savePageBtn").click(function(){
        saveProjectScore();
    });
}

function fetchProjectScore()
{
    $.getJSON("/api/projectscore/" + projectid + "/" + studentid, function(data)
    {
        console.log(data);
        $.each(viewModel.competences(), function(i,item){
                    $.each(item.subcompetences(), function(i, subcomp)
                    {
                        $.each(subcomp.indicators(),function(i,indic)
                        {
                            for(var i = 0; i < data.length; i++) {
                            if (indic.id() == data[i].indicator) {
                                indic.score(data[i].score);
                                indic.scoreid(data[i].id);
                            }
                    }
                });
            });
        });
    });
}

function saveProjectScore()
{
    //console.log(viewModel.competences()[1].subcompetences()[0].indicators()[4].scoreid());
    //console.log(viewModel.competences()[0].subcompetences()[0].indicators()[0].score());
    $.ajax({
        type: "POST",
        url: "/api/projectscore/" + projectid + "/" + studentid,
        data: ko.toJSON(viewModel.competences),
        success: function(data){
            // TODO make multilangual and with modals
            alert("Saved projectscore to server");
            console.log(data);
            //window.location = "/assess/project/" + projectid + "/students";
        }
    });
}

function fetchProjectStructure() {
    viewModel.clearStructure();

    $.getJSON("/api/projectstructure/" + projectid, function(data){
        $.each(data, function(i, item){
            var competence = viewModel.updateCompetence(item.id, item.code, item.description);

            $.each(item.subcompetences, function(i, subcomp){
                var subcompetence = new SubCompetence(competence, subcomp.id, subcomp.code, subcomp.description);
                competence.subcompetences.push(subcompetence);

                $.each(subcomp.indicators, function(i, indic){
                    subcompetence.indicators.push(new Indicator(subcompetence, indic.id, indic.name, indic.description, 0,indic.id, indic.pointType));
                });
            });
        });
        fetchProjectScore();
    });
}

function Competence(viewmodel, id, code, name, subcompetences) {
    return {
        id: ko.observable(id),
        code: ko.observable(code),
        name: ko.observable(name),
        subcompetences: ko.observableArray(subcompetences)
    };
}

function SubCompetence(parent, id, code, name, indicators) {
    return {
        id: ko.observable(id),
        code: ko.observable(code),
        name: ko.observable(name),
        indicators: ko.observableArray(indicators)
    };
}

function Indicator(parent, id, name, description, score, scoreid,pointType, checked) {
    return {
        id: ko.observable(id),
        name: ko.observable(name),
        description: ko.observable(description),
        score: ko.observable(score),
        scoreid: ko.observable(scoreid || 0),
        pointType: ko.observable(pointType),
        checked : ko.observable(checked)
    };
}<commit_msg>Fixing save grades<commit_after>function pageViewModel(gvm) {
    // projecttitle
    gvm.projecttitle = ko.observable("");
    gvm.projectId = $("#projectHeader").data('value');

    // Page specific i18n bindings
    gvm.title = ko.computed(function (){i18n.setLocale(gvm.lang()); return gvm.app() + ' - ' + i18n.__("ProjectTitle") + ": " + gvm.projecttitle();}, gvm);
    gvm.pageHeader = ko.observable("Project");
    gvm.projectname = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("ProjectName");}, gvm);
    gvm.menuLanguage = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("MenuLanguage");}, gvm);
    gvm.savePage = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("SaveBtn");}, gvm);

    gvm.competences = ko.observableArray([]);

    gvm.getProjectInfo = function() {
        $.getJSON('/api/project/' + gvm.projectId, function(data) {
            gvm.pageHeader(data[0].code + ' - ' + data[0].name);
        });
    };

    gvm.getAllData = function() {
         $.getJSON('/api/project/getAllData/' + gvm.projectId, function(data) {
            console.log(data);
         });
    };

    gvm.updateCompetence = function(id, code, description, max, weight) {
        var comp = new Competence(this, id, code, description, weight);
        gvm.competences.push(comp);
        return comp;
    };

    gvm.clearStructure = function() {
        gvm.competences.destroyAll();
    };
}

function initPage() {
    fetchProjectStructure();
    viewModel.getProjectInfo();

    $(".savePageBtn").click(function(){
        saveProjectScore();
    });
}

function fetchProjectScore()
{
    $.getJSON("/api/projectscore/" + projectid + "/" + studentid, function(data)
    {
        console.log(data);
        $.each(viewModel.competences(), function(i,item){
                    $.each(item.subcompetences(), function(i, subcomp)
                    {
                        $.each(subcomp.indicators(),function(i,indic)
                        {
                            for(var i = 0; i < data.length; i++) {
                                console.log(data[i].indicator);
                            if (indic.id() == data[i].indicator) {
                                indic.score(data[i].score);
                                indic.scoreid(data[i].id);
                            }
                    }
                });
            });
        });
    });
}

function saveProjectScore()
{
    //console.log(viewModel.competences()[1].subcompetences()[0].indicators()[4].scoreid());
    //console.log(viewModel.competences()[0].subcompetences()[0].indicators()[0].score());
    $.ajax({
        type: "POST",
        url: "/api/projectscore/" + projectid + "/" + studentid,
        data: ko.toJSON(viewModel.competences),
        success: function(data){
            // TODO make multilangual and with modals
            alert("Saved projectscore to server");
            console.log(data);
            //window.location = "/assess/project/" + projectid + "/students";
        }
    });
}

function fetchProjectStructure() {
    viewModel.clearStructure();

    $.getJSON("/api/projectstructure/" + projectid, function(data){
        $.each(data, function(i, item){
            var competence = viewModel.updateCompetence(item.id, item.code, item.description);

            $.each(item.subcompetences, function(i, subcomp){
                var subcompetence = new SubCompetence(competence, subcomp.id, subcomp.code, subcomp.description);
                competence.subcompetences.push(subcompetence);

                $.each(subcomp.indicators, function(i, indic){
                    subcompetence.indicators.push(new Indicator(subcompetence, indic.id, indic.name, indic.description, 0,indic.id, indic.pointType));
                });
            });
        });
        fetchProjectScore();
    });
}

function Competence(viewmodel, id, code, name, subcompetences) {
    return {
        id: ko.observable(id),
        code: ko.observable(code),
        name: ko.observable(name),
        subcompetences: ko.observableArray(subcompetences)
    };
}

function SubCompetence(parent, id, code, name, indicators) {
    return {
        id: ko.observable(id),
        code: ko.observable(code),
        name: ko.observable(name),
        indicators: ko.observableArray(indicators)
    };
}

function Indicator(parent, id, name, description, score, scoreid,pointType, checked) {
    return {
        id: ko.observable(id),
        name: ko.observable(name),
        description: ko.observable(description),
        score: ko.observable(score),
        scoreid: ko.observable(scoreid || 0),
        pointType: ko.observable(pointType),
        checked : ko.observable(checked)
    };
}<|endoftext|>


<commit_before>function pageViewModel(gvm) {
    // projecttitle
    gvm.projecttitle = ko.observable("");
    gvm.projectId = $("#projectHeader").data('value');

    // Page specific i18n bindings
    gvm.title = ko.computed(function (){i18n.setLocale(gvm.lang()); return gvm.app() + ' - ' + i18n.__("ProjectTitle") + ": " + gvm.projecttitle();}, gvm);
    gvm.pageHeader = ko.observable("Project");
    gvm.projectname = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("ProjectName");}, gvm);

    gvm.indicators = ko.observableArray([]);

    gvm.getProjectInfo = function() {
        $.getJSON('/api/project/' + gvm.projectId, function(data) {
            gvm.pageHeader(data[0].code + ' - ' + data[0].name);
        });
    };

    gvm.getAllData = function() {
         $.getJSON('/api/project/getAllData/' + gvm.projectId, function(data) {
            console.log(data);
         });
    }

    gvm.updateIndicator = function(id,subcompetenceId,competenceId,name,description) {
        gvm.indicators.push(new Indicator(this,id,subcompetenceId,competenceId,name,description));
    };
}

function initPage() {
    viewModel.getProjectInfo();
    viewModel.getAllData();
    fetchProjectStructure();
}

function fetchProjectStructure() {

    $.getJSON("/api/projectstructure/" + projectid, function(data){
        $.each(data, function(i, item){
            $.each(item.subcompetences, function(i, subcomp){
                $.each(subcomp.indicators, function(i, indic){
                    var indicator = new Indicator(indic.id, subcomp.id, item.id, indic.name, indic.description);
                    viewModel.updateIndicator(this,indic.id,subcomp.id,item.id,indic.name,indic.description);
                });
            });
        })
    });

    console.log(viewModel.indicators);
}

/* Indicators */
function Indicator(id, subcompetenceId, competenceId, name, description) {
    return {
        id: ko.observable(id),
        subcompetenceId: ko.observable(subcompetenceId),
        competenceId: ko.observable(competenceId),
        name: ko.observable(name),
        description : ko.observable(description)
       }
}<commit_msg>assess score<commit_after>function pageViewModel(gvm) {
    // projecttitle
    gvm.projecttitle = ko.observable("");
    gvm.projectId = $("#projectHeader").data('value');

    // Page specific i18n bindings
    gvm.title = ko.computed(function (){i18n.setLocale(gvm.lang()); return gvm.app() + ' - ' + i18n.__("ProjectTitle") + ": " + gvm.projecttitle();}, gvm);
    gvm.pageHeader = ko.observable("Project");
    gvm.projectname = ko.computed(function(){i18n.setLocale(gvm.lang()); return i18n.__("ProjectName");}, gvm);

    gvm.indicators = ko.observableArray([]);

    gvm.getProjectInfo = function() {
        $.getJSON('/api/project/' + gvm.projectId, function(data) {
            gvm.pageHeader(data[0].code + ' - ' + data[0].name);
        });
    };

    gvm.getAllData = function() {
         $.getJSON('/api/project/getAllData/' + gvm.projectId, function(data) {
            console.log(data);
         });
    }

    gvm.updateIndicator = function(id,subcompetenceId,competenceId,name,description) {
        gvm.indicators.push(new Indicator(this,id,subcompetenceId,competenceId,name,description));
    };
}

function initPage() {
    viewModel.getProjectInfo();
    viewModel.getAllData();
    fetchProjectStructure();
}

function fetchProjectStructure() {

    $.getJSON("/api/projectstructure/" + projectid, function(data){
        $.each(data, function(i, item){
            $.each(item.subcompetences, function(i, subcomp){
                $.each(subcomp.indicators, function(i, indic){
                    var indicator = new Indicator(indic.id, subcomp.id, item.id, indic.name, indic.description);
                    viewModel.addIndicator(this,indic.id,subcomp.id,item.id,indic.name,indic.description);
                });
            });
        })
    });

    console.log(viewModel.indicators);
}

/* Indicators */
function Indicator(id, subcompetenceId, competenceId, name, description) {
    return {
        id: ko.observable(id),
        subcompetenceId: ko.observable(subcompetenceId),
        competenceId: ko.observable(competenceId),
        name: ko.observable(name),
        description : ko.observable(description)
       }
}<|endoftext|>


<commit_before>jQuery( function( $ ) {

	var $body = $( 'body' );

	// Retrieve feedback posts when an approriate action is taken
	$body.on( 'change', '.badgeos-feedback-filter select', badgeos_get_feedback );
	$body.on( 'submit', '.badgeos-feedback-search-form', function( event ) {
		event.preventDefault();
		badgeos_get_feedback();
	} );

	function badgeos_hide_submission_comments( submissions_wrapper ) {
		submissions_wrapper.find( '.badgeos-submission-comments-wrap' ).hide();
		submissions_wrapper.find( '.badgeos-comment-form' ).hide();
		submissions_wrapper.find( '.submission-comment-toggle' ).show();
	}

	// Hide comment form on feedback posts with toggle
	var $submissions_wrapper = $('.badgeos-feedback-container');
	badgeos_hide_submission_comments( $submissions_wrapper );
	$submissions_wrapper.on( 'click', '.submission-comment-toggle', function( event ) {
		event.preventDefault();

		var $button = $(this);
		$button.siblings( '.badgeos-submission-comments-wrap' ).fadeIn('fast');
		$button.siblings( '.badgeos-comment-form' ).fadeIn('fast');
		$button.hide();
	} );

	// Get feedback posts
	function badgeos_get_feedback() {
		$( '.badgeos-spinner' ).show();

		badgeos_setup_feedback_filters();

		$.ajax( {
			url : badgeos_feedback.ajax_url,
			data : badgeos_feedback,
			dataType : 'json',
			success : function( response ) {
				$( '.badgeos-spinner' ).hide();
				$submissions_wrapper.html( response.data.feedback );
				badgeos_hide_submission_comments( $submissions_wrapper );
			},
			error : function( x, t, m ) {
				if ( window.console ) {
					console.log( [t, m] );
				}
			}
		} );
	}

	// Setup feedback filters
	function badgeos_setup_feedback_filters() {
		if ( 'undefined' != typeof badgeos_feedback.filters && badgeos_feedback.filters ) {
			for ( var field_selector in badgeos_feedback.filters ) {
				if ( '' !== badgeos_feedback.filters[ field_selector ] ) {
					badgeos_feedback[ field_selector ] = $( badgeos_feedback.filters[ field_selector ] ).val();
				}
			}
		}
	}

	// Approve/deny feedback
	$body.on( 'click', '.badgeos-feedback-buttons .button', function( event ) {
		event.preventDefault();
		var $button = $( this );
		$.ajax( {
			url : badgeos_feedback_buttons.ajax_url,
			data : {
				'action' : 'update-feedback',
				'status' : $button.data( 'action' ),
				'feedback_id' : $button.data( 'feedback-id' ),
				'feedback_type' : $button.siblings( 'input[name=feedback_type]' ).val(),
				'achievement_id' : $button.siblings( 'input[name=achievement_id]' ).val(),
				'user_id' : $button.siblings( 'input[name=user_id]' ).val(),
				'wpms' : $button.siblings( 'input[name=wpms]' ).val(),
				'nonce' : $button.siblings( 'input[name=badgeos_feedback_review]' ).val(),
			},
			dataType : 'json',
			success : function( response ) {
				$( '.badgeos-feedback-response', $button.parent() ).remove();
				$( response.data.message ).appendTo( $button.parent() ).fadeOut( 3000 );
				$( '.badgeos-feedback-' + $button.data( 'feedback-id' ) + ' .badgeos-feedback-status' ).html( response.data.status );
				$( '.cmb_id__badgeos_submission_current td, .cmb_id__badgeos_nomination_current td' ).html( response.data.status );
				$( '.badgeos-comment-date-by' ).html( 'Status: '+ response.data.status );
			}
		} );
	} );

	// Our main achievement list AJAX call
	function badgeos_ajax_achievement_list() {
		$.ajax( {
					url : badgeos.ajax_url,
					data : {
						'action' : 'get-achievements',
						'type' : badgeos.type,
						'limit' : badgeos.limit,
						'show_parent' : badgeos.show_parent,
						'show_child' : badgeos.show_child,
						'group_id' : badgeos.group_id,
						'user_id' : badgeos.user_id,
						'wpms' : badgeos.wpms,
						'offset' : $( '#badgeos_achievements_offset' ).val(),
						'count' : $( '#badgeos_achievements_count' ).val(),
						'filter' : $( '#achievements_list_filter' ).val(),
						'search' : $( '#achievements_list_search' ).val(),
						'orderby' : badgeos.orderby,
						'order' : badgeos.order,
						'include' : badgeos.include,
						'exclude' : badgeos.exclude,
						'meta_key' : badgeos.meta_key,
						'meta_value' : badgeos.meta_value,
                        'layout' : badgeos.layout,
						'tag' : badgeos.tag
					},
					dataType : 'json',
					success : function( response ) {
						$( '.badgeos-spinner' ).hide();
						if ( response.data.message === null ) {
							//alert("That's all folks!");
						}
						else {
							$( '#badgeos-achievements-container' ).append( response.data.message );
							$( '#badgeos_achievements_offset' ).val( response.data.offset );
							$( '#badgeos_achievements_count' ).val( response.data.badge_count );
							credlyize();
							//hide/show load more button
							if ( response.data.query_count <= response.data.badge_count ) {
								$( '#achievements_list_load_more' ).hide();
							}
							else {
								$( '#achievements_list_load_more' ).show();
							}
						}
					}
				} );

	}

	// Reset all our base query vars and run an AJAX call
	function badgeos_ajax_achievement_list_reset() {

		$( '#badgeos_achievements_offset' ).val( 0 );
		$( '#badgeos_achievements_count' ).val( 0 );

		$( '#badgeos-achievements-container' ).html( '' );
		$( '#achievements_list_load_more' ).hide();

		badgeos_ajax_achievement_list();

	}

	// Listen for changes to the achievement filter
	$( '#achievements_list_filter' ).change(function() {

		badgeos_ajax_achievement_list_reset();

	} ).change();

	// Listen for search queries
	$( '#achievements_list_search_go_form' ).submit( function( event ) {

		event.preventDefault();

		badgeos_ajax_achievement_list_reset();

	} );

	// Listen for users clicking the "Load More" button
	$( '#achievements_list_load_more' ).click( function() {

		$( '.badgeos-spinner' ).show();

		badgeos_ajax_achievement_list();

	} );

	// Listen for users clicking the show/hide details link
	$( '#badgeos-achievements-container,.badgeos-single-achievement' ).on( 'click', '.badgeos-open-close-switch a', function( event ) {

		event.preventDefault();

		var link = $( this );

		if ( 'close' == link.data( 'action' ) ) {
			link.parent().siblings( '.badgeos-extras-window' ).slideUp( 300 );
			link.data( 'action', 'open' ).prop( 'class', 'show-hide-open' ).text( 'Show Details' );
		}
		else {
			link.parent().siblings( '.badgeos-extras-window' ).slideDown( 300 );
			link.data( 'action', 'close' ).prop( 'class', 'show-hide-close' ).text( 'Hide Details' );
		}

	} );

    // Display short description when hover badge
    var posY;
    var posX;
    jQuery(document).ready(function(m) {
        $(document).mousemove(function(m){
            posX = m.pageX - 215;
            posY = m.pageY;
        });
    })
    function displayDescription(e){
        $("#"+e).css('left', posX);
        $("#"+e).css('top', posY);
        $("#puce"+e).css('z-index', '2');
        $("#"+e).fadeIn("fast");
    }
    function hideDescription(e){
        $("#"+e).hide();
        $("#"+e).css('left', 0);
        $("#"+e).css('top', 0);
        $("#puce"+e).css('z-index', '0');
    }

	// Credly popup functionality
	if ( 'undefined' != typeof BadgeosCredlyData ) {
		// credly markup
		var credly = '

'
						 + BadgeosCredlyData.message + '

'
						 + ''
						 + BadgeosCredlyData.errormessage + ''
						 + '<input type="submit" class="credly-button credly-send" value="' + BadgeosCredlyData.confirm + '"/>'
						 + '' + BadgeosCredlyData.cancel + ''
						 + '

';

		// add credly popup to dom
		$body.append( credly );
		credly = $( '.credly-share-popup' );

		var credlyMessage = $( '.credly-message' );
		var credlySpinner = $( '.badgeos-spinner' );
		var credlySend = $( '.credly-button.credly-send' );
		var spinnerTimer;

		// make a function so we can call it from our ajax success functions
		function credlyize() {

			$( '.share-credly.addCredly' ).append( '' ).removeClass( 'addCredly' );

		}

		credlyize();

		// add credly share button to achievements in widget
		$body.on( 'click', '.credly-share', function( event ) {

			event.preventDefault();

			credlyHide();
			var el = $( this );
			var parent = el.parent();
			var width = parent.outerWidth();
			var elPosition = el.offset();
			var id = parent.data( 'credlyid' );

			// move credly popup
			credly.width( width ).css( {
				top : Math.floor( elPosition.top - credly.outerHeight() ), left : Math.floor( elPosition.left )
			} ).show().data( 'credlyid', id );

		} );

		$body.on( 'click', '.credly-button.credly-cancel', function( event ) {

			// Cancel Credly popup
			event.preventDefault();

			credlyHide();

		} ).on( 'click', '.credly-button.credly-send', function( event ) {

			// Our Credly button handler
			event.preventDefault();

			credlySend.hide();
			credlySpinner.show();

			badgeos_send_to_credly( credly.data( 'credlyid' ) );

		} );

		// reset hidden credly elements
		function credlyHide() {

			clearTimeout( spinnerTimer );

			credly.hide();
			credlySend.show();
			credlySpinner.hide();

			credlyMessage.hide().css( { opacity : 1.0 } ).html( BadgeosCredlyData.errormessage );

		}

		// Our Credly AJAX call
		function badgeos_send_to_credly( ID ) {

			$.ajax( {
				type : 'post',
				dataType : 'json',
				url : BadgeosCredlyData.ajax_url,
				data : {
					'action' : 'achievement_send_to_credly',
					'ID' : ID
				},
				success : function( response ) {

					// hide our loading spinner
					credlySpinner.hide();

					// show our 'saved' notification briefly
					credlyMessage.fadeTo( 'fast', 1 ).animate( { opacity : 1.0 }, 2000 ).fadeTo( 'slow', 0 ).html( response );

					setTimeout( function() {

						credlyHide();

					}, 2200 );

				},
				error : function( x, t, m ) {

					if ( t === 'timeout' ) {
						credlySpinner.hide();
						credlyMessage.show();

						setTimeout( function() {

							credlyMessage.animate( { opacity : 0 }, 500 );

							setTimeout( function() {

								credlyMessage.hide().css( { opacity : 1.0 } );
								credlySend.show();

							}, 600 );

						}, 1100 );
					}
					else {
						// hide our loading spinner
						credlySpinner.hide();

						// show our 'saved' notification briefly
						var html = '' + BadgeosCredlyData.localized_error + ' ' + t + '';

						credlyMessage.fadeTo( 'fast', 1 ).animate( { opacity : 1.0 }, 2000 ).fadeTo( 'slow', 0 ).html( html );

						setTimeout( function() {

							credlyHide();

						}, 2200 );
					}

				}

			} );

		}
	}

} );

// Addon for gridview to display short description when hover badge
var idKey = "description-";
function displayDescription(e){
    jQuery(".badgeos-item-short-description").hide();
    var elt = jQuery("#"+ idKey + e);
    var badgeElt = jQuery("#" + "badgeos-achievements-grid-item-" + e);
    if(badgeElt.position().left < jQuery(window).width()/2){
        elt.css('left'  , badgeElt.position().left  + 130);
    } else {
        elt.css('left'  , badgeElt.position().left - elt.width());
    }
    elt.css('top'   , badgeElt.position().top   + 130);
    elt.fadeIn("fast");
}
function hideDescription(e){
    jQuery(".badgeos-item-short-description").hide();
    var elt = jQuery("#"+ idKey + e);
    elt.css('left', 0);
    elt.css('top', 0);
}
//////////////////////////////////////////////
<commit_msg>Grid view : fixing bug on abstract block display<commit_after>jQuery( function( $ ) {

	var $body = $( 'body' );

	// Retrieve feedback posts when an approriate action is taken
	$body.on( 'change', '.badgeos-feedback-filter select', badgeos_get_feedback );
	$body.on( 'submit', '.badgeos-feedback-search-form', function( event ) {
		event.preventDefault();
		badgeos_get_feedback();
	} );

	function badgeos_hide_submission_comments( submissions_wrapper ) {
		submissions_wrapper.find( '.badgeos-submission-comments-wrap' ).hide();
		submissions_wrapper.find( '.badgeos-comment-form' ).hide();
		submissions_wrapper.find( '.submission-comment-toggle' ).show();
	}

	// Hide comment form on feedback posts with toggle
	var $submissions_wrapper = $('.badgeos-feedback-container');
	badgeos_hide_submission_comments( $submissions_wrapper );
	$submissions_wrapper.on( 'click', '.submission-comment-toggle', function( event ) {
		event.preventDefault();

		var $button = $(this);
		$button.siblings( '.badgeos-submission-comments-wrap' ).fadeIn('fast');
		$button.siblings( '.badgeos-comment-form' ).fadeIn('fast');
		$button.hide();
	} );

	// Get feedback posts
	function badgeos_get_feedback() {
		$( '.badgeos-spinner' ).show();

		badgeos_setup_feedback_filters();

		$.ajax( {
			url : badgeos_feedback.ajax_url,
			data : badgeos_feedback,
			dataType : 'json',
			success : function( response ) {
				$( '.badgeos-spinner' ).hide();
				$submissions_wrapper.html( response.data.feedback );
				badgeos_hide_submission_comments( $submissions_wrapper );
			},
			error : function( x, t, m ) {
				if ( window.console ) {
					console.log( [t, m] );
				}
			}
		} );
	}

	// Setup feedback filters
	function badgeos_setup_feedback_filters() {
		if ( 'undefined' != typeof badgeos_feedback.filters && badgeos_feedback.filters ) {
			for ( var field_selector in badgeos_feedback.filters ) {
				if ( '' !== badgeos_feedback.filters[ field_selector ] ) {
					badgeos_feedback[ field_selector ] = $( badgeos_feedback.filters[ field_selector ] ).val();
				}
			}
		}
	}

	// Approve/deny feedback
	$body.on( 'click', '.badgeos-feedback-buttons .button', function( event ) {
		event.preventDefault();
		var $button = $( this );
		$.ajax( {
			url : badgeos_feedback_buttons.ajax_url,
			data : {
				'action' : 'update-feedback',
				'status' : $button.data( 'action' ),
				'feedback_id' : $button.data( 'feedback-id' ),
				'feedback_type' : $button.siblings( 'input[name=feedback_type]' ).val(),
				'achievement_id' : $button.siblings( 'input[name=achievement_id]' ).val(),
				'user_id' : $button.siblings( 'input[name=user_id]' ).val(),
				'wpms' : $button.siblings( 'input[name=wpms]' ).val(),
				'nonce' : $button.siblings( 'input[name=badgeos_feedback_review]' ).val(),
			},
			dataType : 'json',
			success : function( response ) {
				$( '.badgeos-feedback-response', $button.parent() ).remove();
				$( response.data.message ).appendTo( $button.parent() ).fadeOut( 3000 );
				$( '.badgeos-feedback-' + $button.data( 'feedback-id' ) + ' .badgeos-feedback-status' ).html( response.data.status );
				$( '.cmb_id__badgeos_submission_current td, .cmb_id__badgeos_nomination_current td' ).html( response.data.status );
				$( '.badgeos-comment-date-by' ).html( 'Status: '+ response.data.status );
			}
		} );
	} );

	// Our main achievement list AJAX call
	function badgeos_ajax_achievement_list() {
		$.ajax( {
					url : badgeos.ajax_url,
					data : {
						'action' : 'get-achievements',
						'type' : badgeos.type,
						'limit' : badgeos.limit,
						'show_parent' : badgeos.show_parent,
						'show_child' : badgeos.show_child,
						'group_id' : badgeos.group_id,
						'user_id' : badgeos.user_id,
						'wpms' : badgeos.wpms,
						'offset' : $( '#badgeos_achievements_offset' ).val(),
						'count' : $( '#badgeos_achievements_count' ).val(),
						'filter' : $( '#achievements_list_filter' ).val(),
						'search' : $( '#achievements_list_search' ).val(),
						'orderby' : badgeos.orderby,
						'order' : badgeos.order,
						'include' : badgeos.include,
						'exclude' : badgeos.exclude,
						'meta_key' : badgeos.meta_key,
						'meta_value' : badgeos.meta_value,
                        'layout' : badgeos.layout,
						'tag' : badgeos.tag
					},
					dataType : 'json',
					success : function( response ) {
						$( '.badgeos-spinner' ).hide();
						if ( response.data.message === null ) {
							//alert("That's all folks!");
						}
						else {
							$( '#badgeos-achievements-container' ).append( response.data.message );
							$( '#badgeos_achievements_offset' ).val( response.data.offset );
							$( '#badgeos_achievements_count' ).val( response.data.badge_count );
							credlyize();
							//hide/show load more button
							if ( response.data.query_count <= response.data.badge_count ) {
								$( '#achievements_list_load_more' ).hide();
							}
							else {
								$( '#achievements_list_load_more' ).show();
							}
						}
					}
				} );

	}

	// Reset all our base query vars and run an AJAX call
	function badgeos_ajax_achievement_list_reset() {

		$( '#badgeos_achievements_offset' ).val( 0 );
		$( '#badgeos_achievements_count' ).val( 0 );

		$( '#badgeos-achievements-container' ).html( '' );
		$( '#achievements_list_load_more' ).hide();

		badgeos_ajax_achievement_list();

	}

	// Listen for changes to the achievement filter
	$( '#achievements_list_filter' ).change(function() {

		badgeos_ajax_achievement_list_reset();

	} ).change();

	// Listen for search queries
	$( '#achievements_list_search_go_form' ).submit( function( event ) {

		event.preventDefault();

		badgeos_ajax_achievement_list_reset();

	} );

	// Listen for users clicking the "Load More" button
	$( '#achievements_list_load_more' ).click( function() {

		$( '.badgeos-spinner' ).show();

		badgeos_ajax_achievement_list();

	} );

	// Listen for users clicking the show/hide details link
	$( '#badgeos-achievements-container,.badgeos-single-achievement' ).on( 'click', '.badgeos-open-close-switch a', function( event ) {

		event.preventDefault();

		var link = $( this );

		if ( 'close' == link.data( 'action' ) ) {
			link.parent().siblings( '.badgeos-extras-window' ).slideUp( 300 );
			link.data( 'action', 'open' ).prop( 'class', 'show-hide-open' ).text( 'Show Details' );
		}
		else {
			link.parent().siblings( '.badgeos-extras-window' ).slideDown( 300 );
			link.data( 'action', 'close' ).prop( 'class', 'show-hide-close' ).text( 'Hide Details' );
		}

	} );

    // Display short description when hover badge
    var posY;
    var posX;
    jQuery(document).ready(function(m) {
        $(document).mousemove(function(m){
            posX = m.pageX - 215;
            posY = m.pageY;
        });
    })
    function displayDescription(e){
        $("#"+e).css('left', posX);
        $("#"+e).css('top', posY);
        $("#puce"+e).css('z-index', '2');
        $("#"+e).fadeIn("fast");
    }
    function hideDescription(e){
        $("#"+e).hide();
        $("#"+e).css('left', 0);
        $("#"+e).css('top', 0);
        $("#puce"+e).css('z-index', '0');
    }

	// Credly popup functionality
	if ( 'undefined' != typeof BadgeosCredlyData ) {
		// credly markup
		var credly = '

'
						 + BadgeosCredlyData.message + '

'
						 + ''
						 + BadgeosCredlyData.errormessage + ''
						 + '<input type="submit" class="credly-button credly-send" value="' + BadgeosCredlyData.confirm + '"/>'
						 + '' + BadgeosCredlyData.cancel + ''
						 + '

';

		// add credly popup to dom
		$body.append( credly );
		credly = $( '.credly-share-popup' );

		var credlyMessage = $( '.credly-message' );
		var credlySpinner = $( '.badgeos-spinner' );
		var credlySend = $( '.credly-button.credly-send' );
		var spinnerTimer;

		// make a function so we can call it from our ajax success functions
		function credlyize() {

			$( '.share-credly.addCredly' ).append( '' ).removeClass( 'addCredly' );

		}

		credlyize();

		// add credly share button to achievements in widget
		$body.on( 'click', '.credly-share', function( event ) {

			event.preventDefault();

			credlyHide();
			var el = $( this );
			var parent = el.parent();
			var width = parent.outerWidth();
			var elPosition = el.offset();
			var id = parent.data( 'credlyid' );

			// move credly popup
			credly.width( width ).css( {
				top : Math.floor( elPosition.top - credly.outerHeight() ), left : Math.floor( elPosition.left )
			} ).show().data( 'credlyid', id );

		} );

		$body.on( 'click', '.credly-button.credly-cancel', function( event ) {

			// Cancel Credly popup
			event.preventDefault();

			credlyHide();

		} ).on( 'click', '.credly-button.credly-send', function( event ) {

			// Our Credly button handler
			event.preventDefault();

			credlySend.hide();
			credlySpinner.show();

			badgeos_send_to_credly( credly.data( 'credlyid' ) );

		} );

		// reset hidden credly elements
		function credlyHide() {

			clearTimeout( spinnerTimer );

			credly.hide();
			credlySend.show();
			credlySpinner.hide();

			credlyMessage.hide().css( { opacity : 1.0 } ).html( BadgeosCredlyData.errormessage );

		}

		// Our Credly AJAX call
		function badgeos_send_to_credly( ID ) {

			$.ajax( {
				type : 'post',
				dataType : 'json',
				url : BadgeosCredlyData.ajax_url,
				data : {
					'action' : 'achievement_send_to_credly',
					'ID' : ID
				},
				success : function( response ) {

					// hide our loading spinner
					credlySpinner.hide();

					// show our 'saved' notification briefly
					credlyMessage.fadeTo( 'fast', 1 ).animate( { opacity : 1.0 }, 2000 ).fadeTo( 'slow', 0 ).html( response );

					setTimeout( function() {

						credlyHide();

					}, 2200 );

				},
				error : function( x, t, m ) {

					if ( t === 'timeout' ) {
						credlySpinner.hide();
						credlyMessage.show();

						setTimeout( function() {

							credlyMessage.animate( { opacity : 0 }, 500 );

							setTimeout( function() {

								credlyMessage.hide().css( { opacity : 1.0 } );
								credlySend.show();

							}, 600 );

						}, 1100 );
					}
					else {
						// hide our loading spinner
						credlySpinner.hide();

						// show our 'saved' notification briefly
						var html = '' + BadgeosCredlyData.localized_error + ' ' + t + '';

						credlyMessage.fadeTo( 'fast', 1 ).animate( { opacity : 1.0 }, 2000 ).fadeTo( 'slow', 0 ).html( html );

						setTimeout( function() {

							credlyHide();

						}, 2200 );
					}

				}

			} );

		}
	}

} );

// Addon for gridview to display short description when hover badge
var idKey = "description-";
function displayDescription(e){
    jQuery(".badgeos-item-short-description").hide();
    var elt = jQuery("#"+ idKey + e);
    var badgeElt = jQuery("#" + "badgeos-achievements-grid-item-" + e);
    if (jQuery(window).width() < 750){
        elt.css('left'  , 0);
    } else {
        if(badgeElt.position().left < jQuery("#badgeos-achievements-container").width()/2){
            elt.css('left'  , badgeElt.position().left  + 130);
        } else {
            elt.css('left'  , badgeElt.position().left - elt.width());
        }
    }
    elt.css('top'   , badgeElt.position().top   + 130);
    elt.fadeIn("fast");
}
function hideDescription(e){
    var elt = jQuery("#"+ idKey + e);
    elt.stop();
    jQuery(".badgeos-item-short-description").hide();
}
//////////////////////////////////////////////
<|endoftext|>


<commit_before>'use strict';

const ccxt = require ('ccxt')
    , {
        sleep,
        isNode,
        isJsonEncodedObject,
        RequestTimeout,
        NetworkError,
        deepExtend,
        milliseconds,
    } = ccxt
    , Future = require ('./Future')
    , WebSocket = isNode ? require ('ws') : window.WebSocket

module.exports = class WebSocketClient {

    constructor (url, onMessageCallback, onErrorCallback, onCloseCallback, config = {}) {

        const defaults = {
            url,
            onMessageCallback,
            onErrorCallback,
            onCloseCallback,
            protocols: undefined, // ws protocols
            options: undefined,   // ws options
            futures: {},
            subscriptions: {},
            connectionStarted: undefined, // initiation timestamp in milliseconds
            connectionEstablished: undefined, // success timestamp in milliseconds
            // connected: Future (), // connection-related Future
            connectionTimer: undefined, // connection-related setTimeout
            connectionTimeout: 30000, // 30 seconds by default, false to disable
            pingInterval: undefined, // ping-related interval
            keepAlive: 3000, // ping-pong keep-alive frequency
            // timeout is not used atm
            // timeout: 30000, // throw if a request is not satisfied in 30 seconds, false to disable
            ws: {
                readyState: undefined,
            },
        }

        Object.assign (this, deepExtend (defaults, config))
        this.connected = Future ()
    }

    future (messageHash) {
        if (!this.futures[messageHash]) {
            this.futures[messageHash] = Future ()
        }
        return this.futures[messageHash]
    }

    resolve (result, messageHash = undefined) {
        if (messageHash) {
            if (this.futures[messageHash]) {
                const promise = this.futures[messageHash]
                promise.resolve (result)
                delete this.futures[messageHash]
            }
        } else {
            const messageHashes = Object.keys (this.futures)
            for (let i = 0; i < messageHashes.length; i++) {
                this.resolve (result, messageHashes[i])
            }
        }
        return result
    }

    reject (result, messageHash = undefined) {
        if (messageHash) {
            if (this.futures[messageHash]) {
                const promise = this.futures[messageHash]
                promise.reject (result)
                delete this.futures[messageHash]
            }
        } else {
            const messageHashes = Object.keys (this.futures)
            for (let i = 0; i < messageHashes.length; i++) {
                this.reject (result, messageHashes[i])
            }
        }
    }

    createWebsocket () {
        console.log (new Date (), 'connecting...')
        this.connectionStarted = milliseconds ()
        this.setConnectionTimeout ()
        this.ws = new WebSocket (this.url, this.protocols, this.options)
        this.ws
            .on ('open', this.onOpen.bind (this))
            .on ('ping', this.onPing.bind (this))
            .on ('pong', this.onPong.bind (this))
            .on ('error', this.onError.bind (this))
            .on ('close', this.onClose.bind (this))
            .on ('upgrade', this.onUpgrade.bind (this))
            .on ('message', this.onMessage.bind (this))
        // this.ws.terminate () // debugging
        // this.ws.close () // debugging
    }

    connect (backoffDelay = 0) {
        if ((this.ws.readyState !== WebSocket.OPEN) &&
            (this.ws.readyState !== WebSocket.CONNECTING)) {
            // prevent multiple calls overwriting each other
            this.ws.readyState = WebSocket.CONNECTING
            // exponential backoff for consequent ws connections if necessary
            if (backoffDelay) {
                sleep (backoffDelay).then (this.createWebsocket.bind (this))
            } else {
                this.createWebsocket ()
            }
        }
        return this.connected
    }

    reset (error) {
        this.clearConnectionTimeout ()
        this.clearPingInterval ()
        this.connected.reject (error)
        this.reject (error)
    }

    onConnectionTimeout () {
        if (this.ws.readyState !== WebSocket.OPEN) {
            const error = new RequestTimeout ('Connection to ' + this.url + ' failed due to a timeout')
            this.reset (error)
            this.onErrorCallback (this, error)
        }
    }

    setConnectionTimeout () {
        if (this.connectionTimeout) {
            const onConnectionTimeout = this.onConnectionTimeout.bind (this)
            this.connectionTimer = setTimeout (onConnectionTimeout, this.connectionTimeout)
        }
    }

    clearConnectionTimeout () {
        if (this.connectionTimer) {
            this.connectionTimer = clearTimeout (this.connectionTimer)
        }
    }

    setPingInterval () {
        if (this.keepAlive) {
            const onPingInterval = this.onPingInterval.bind (this)
            this.pingInterval = setInterval (onPingInterval, this.keepAlive)
        }
    }

    clearPingInterval () {
        if (this.pingInterval) {
            this.pingInterval = clearInterval (this.pingInterval)
        }
    }

    onPingInterval () {
        if ((this.lastPong + this.pingRate) < milliseconds ()) {
            this.reset (new RequestTimeout ('Connection to ' + this.url + ' timed out'))
        } else {
            if (this.ws.readyState === WebSocket.OPEN) {
                this.ws.ping ()
            }
        }
    }

    onOpen () {
        console.log (new Date (), 'onOpen')
        this.connectionEstablished = milliseconds ()
        this.connected.resolve (this.url)
        // this.ws.terminate () // debugging
        this.clearConnectionTimeout ()
        this.setPingInterval ()
    }

    // this method is not used at this time, because in JS the ws client will
    // respond to pings coming from the server with pongs automatically
    // however, some devs may want to track connection states in their app
    onPing () {
        console.log (new Date (), 'onPing')
    }

    onPong () {
        this.lastPong = milliseconds ()
        console.log (new Date (), 'onPong')
    }

    onError (error) {
        console.log (new Date (), 'onError', error.message)
        // convert ws errors to ccxt errors if necessary
        error = new NetworkError (error.message)
        this.error = error
        this.reset (error)
        this.onErrorCallback (this, error)
    }

    onClose (message) {
        console.log (new Date (), 'onClose', message)
        this.reset (new NetworkError (message))
        this.onCloseCallback (this, message)
    }

    // this method is not used at this time
    // but may be used to read protocol-level data like cookies, headers, etc
    onUpgrade (message) {
        console.log (new Date (), 'onUpgrade')
    }

    send (message) {
        this.ws.send (JSON.stringify (message))
    }

    close () {
        this.reconnect = false
        this.ws.close ()
    }

    onMessage (message) {
        try {
            message = isJsonEncodedObject (message) ? JSON.parse (message) : message
        } catch (e) {
            // reset with a json encoding error ?
        }
        this.onMessageCallback (this, message)
    }
}

/*

'use strict'

// @author frosty00
// @email carlo.revelli@berkeley.edu

const isNode = (typeof window === 'undefined')
const WebSocket = isNode ? require ('ws') : window.WebSocket
const Future = require ('./Future')
const { RequestTimeout } = require ('ccxt/js/base/errors')

class WebSocketClient {
    constructor (url, handler, timeout = 5000) {
        this.url = url
        this.handler = handler
        this.connection = null
        this.connectedFuture = new Future ()
        this.closedFuture = new Future ()
        this.timedoutFuture = new Future ()
        this.timeout = timeout
        this.lastPong = Number.MAX_SAFE_INTEGER
        this.futures = {}
        this.subscriptions = {}
    }

    async checkTimeout () {
        // ping every second
        const pinger = setInterval (() => {
            this.connection.ping ()
            if (new Date ().getTime () - this.lastPong > this.timeout) {
                this.timedoutFuture.resolve ()
            }
        }, 1000)
        await this.timedoutFuture.promise ()
        clearInterval (pinger)
        for (let messageKey of Object.keys (this.futures)) {
            let error = new RequestTimeout ('Websocket did not recieve a pong in reply to a ping within ' + this.timeout + ' seconds');
            this.futures[messageKey].reject (error)
        }
        this.futures = {}
    }

    async connect () {
        if (!this.isConnected ()) {
            this.connection = new WebSocket (this.url)
            this.connection.on ('open', () => {
                this.checkTimeout ()
                this.connectedFuture.resolve ()
            })
            this.connection.on ('message', this.onMessage.bind (this))
            this.connection.on ('close', this.closedFuture.resolve)
            this.connection.on ('pong', () => {
                this.lastPong = new Date ().getTime ()
            })
            return this.connectedFuture.promise ()
        }
    }

    isConnected () {
        return this.connection !== null && this.connection.readyState === WebSocket.OPEN
    }

    onMessage (message) {
        try {
            this.handler (this, JSON.parse (message))
        } catch (e) {
            console.log (message)
            // encoding error (need to figure out how to handle)
        }
    }

    async send (data) {
        await this.connect ()
        this.connection.send (JSON.stringify (data))
    }

    async close () {
        if (this.connection === null) {
            return;
        }
        this.connection.close ()
        await this.closedFuture.promise ()
        this.connection = null
    }

    static async registerFuture (url, messageHash, entry, apiKey, subscribe = undefined) {
        const index = url + (apiKey ? apiKey : '')
        const client = WebSocketClient.clients[index] || (WebSocketClient.clients[index] = new WebSocketClient (url, entry))
        const future = client.futures[messageHash] || (client.futures[messageHash] = new Future ())
        if (subscribe === undefined) {
            await client.connect ()
        } else if (!client.subscriptions[messageHash]) {
            client.send (subscribe)
            client.subscriptions[messageHash] = true
        }
        const result = await future.promise ()
        delete client.futures[messageHash]
        return result
    }
}

WebSocketClient.clients = {}
module.exports = WebSocketClient

*/
<commit_msg>WebSocketClient cleanup<commit_after>'use strict';

const ccxt = require ('ccxt')
    , {
        sleep,
        isNode,
        isJsonEncodedObject,
        RequestTimeout,
        NetworkError,
        deepExtend,
        milliseconds,
    } = ccxt
    , Future = require ('./Future')
    , WebSocket = isNode ? require ('ws') : window.WebSocket

module.exports = class WebSocketClient {

    constructor (url, onMessageCallback, onErrorCallback, onCloseCallback, config = {}) {

        const defaults = {
            url,
            onMessageCallback,
            onErrorCallback,
            onCloseCallback,
            protocols: undefined, // ws protocols
            options: undefined,   // ws options
            futures: {},
            subscriptions: {},
            connectionStarted: undefined, // initiation timestamp in milliseconds
            connectionEstablished: undefined, // success timestamp in milliseconds
            // connected: Future (), // connection-related Future
            connectionTimer: undefined, // connection-related setTimeout
            connectionTimeout: 30000, // 30 seconds by default, false to disable
            pingInterval: undefined, // ping-related interval
            keepAlive: 3000, // ping-pong keep-alive frequency
            // timeout is not used atm
            // timeout: 30000, // throw if a request is not satisfied in 30 seconds, false to disable
            ws: {
                readyState: undefined,
            },
        }

        Object.assign (this, deepExtend (defaults, config))
        this.connected = Future ()
    }

    future (messageHash) {
        if (!this.futures[messageHash]) {
            this.futures[messageHash] = Future ()
        }
        return this.futures[messageHash]
    }

    resolve (result, messageHash = undefined) {
        if (messageHash) {
            if (this.futures[messageHash]) {
                const promise = this.futures[messageHash]
                promise.resolve (result)
                delete this.futures[messageHash]
            }
        } else {
            const messageHashes = Object.keys (this.futures)
            for (let i = 0; i < messageHashes.length; i++) {
                this.resolve (result, messageHashes[i])
            }
        }
        return result
    }

    reject (result, messageHash = undefined) {
        if (messageHash) {
            if (this.futures[messageHash]) {
                const promise = this.futures[messageHash]
                promise.reject (result)
                delete this.futures[messageHash]
            }
        } else {
            const messageHashes = Object.keys (this.futures)
            for (let i = 0; i < messageHashes.length; i++) {
                this.reject (result, messageHashes[i])
            }
        }
    }

    createWebsocket () {
        console.log (new Date (), 'connecting...')
        this.connectionStarted = milliseconds ()
        this.setConnectionTimeout ()
        this.ws = new WebSocket (this.url, this.protocols, this.options)
        this.ws
            .on ('open', this.onOpen.bind (this))
            .on ('ping', this.onPing.bind (this))
            .on ('pong', this.onPong.bind (this))
            .on ('error', this.onError.bind (this))
            .on ('close', this.onClose.bind (this))
            .on ('upgrade', this.onUpgrade.bind (this))
            .on ('message', this.onMessage.bind (this))
        // this.ws.terminate () // debugging
        // this.ws.close () // debugging
    }

    connect (backoffDelay = 0) {
        if ((this.ws.readyState !== WebSocket.OPEN) &&
            (this.ws.readyState !== WebSocket.CONNECTING)) {
            // prevent multiple calls overwriting each other
            this.ws.readyState = WebSocket.CONNECTING
            // exponential backoff for consequent ws connections if necessary
            if (backoffDelay) {
                sleep (backoffDelay).then (this.createWebsocket.bind (this))
            } else {
                this.createWebsocket ()
            }
        }
        return this.connected
    }

    reset (error) {
        this.clearConnectionTimeout ()
        this.clearPingInterval ()
        this.connected.reject (error)
        this.reject (error)
    }

    onConnectionTimeout () {
        if (this.ws.readyState !== WebSocket.OPEN) {
            const error = new RequestTimeout ('Connection to ' + this.url + ' failed due to a timeout')
            this.reset (error)
            this.onErrorCallback (this, error)
        }
    }

    setConnectionTimeout () {
        if (this.connectionTimeout) {
            const onConnectionTimeout = this.onConnectionTimeout.bind (this)
            this.connectionTimer = setTimeout (onConnectionTimeout, this.connectionTimeout)
        }
    }

    clearConnectionTimeout () {
        if (this.connectionTimer) {
            this.connectionTimer = clearTimeout (this.connectionTimer)
        }
    }

    setPingInterval () {
        if (this.keepAlive) {
            const onPingInterval = this.onPingInterval.bind (this)
            this.pingInterval = setInterval (onPingInterval, this.keepAlive)
        }
    }

    clearPingInterval () {
        if (this.pingInterval) {
            this.pingInterval = clearInterval (this.pingInterval)
        }
    }

    onPingInterval () {
        if ((this.lastPong + this.pingRate) < milliseconds ()) {
            this.reset (new RequestTimeout ('Connection to ' + this.url + ' timed out'))
        } else {
            if (this.ws.readyState === WebSocket.OPEN) {
                this.ws.ping ()
            }
        }
    }

    onOpen () {
        console.log (new Date (), 'onOpen')
        this.connectionEstablished = milliseconds ()
        this.connected.resolve (this.url)
        // this.ws.terminate () // debugging
        this.clearConnectionTimeout ()
        this.setPingInterval ()
    }

    // this method is not used at this time, because in JS the ws client will
    // respond to pings coming from the server with pongs automatically
    // however, some devs may want to track connection states in their app
    onPing () {
        console.log (new Date (), 'onPing')
    }

    onPong () {
        this.lastPong = milliseconds ()
        console.log (new Date (), 'onPong')
    }

    onError (error) {
        console.log (new Date (), 'onError', error.message)
        // convert ws errors to ccxt errors if necessary
        error = new NetworkError (error.message)
        this.error = error
        this.reset (error)
        this.onErrorCallback (this, error)
    }

    onClose (message) {
        console.log (new Date (), 'onClose', message)
        this.reset (new NetworkError (message))
        this.onCloseCallback (this, message)
    }

    // this method is not used at this time
    // but may be used to read protocol-level data like cookies, headers, etc
    onUpgrade (message) {
        console.log (new Date (), 'onUpgrade')
    }

    send (message) {
        this.ws.send (JSON.stringify (message))
    }

    close () {
        this.reconnect = false
        this.ws.close ()
    }

    onMessage (message) {
        try {
            message = isJsonEncodedObject (message) ? JSON.parse (message) : message
        } catch (e) {
            // reset with a json encoding error ?
        }
        this.onMessageCallback (this, message)
    }
}
<|endoftext|>