<commit_before>var APP = APP || {};

APP.ExperimentViewer = function(theIndex, theData) {
    this.mIndex = theIndex;
    this.mRawData = theData;
    this.mGame = this.mRawData.games[this.mIndex];
    this.mChart = null;
    this.mChartControlers = [];

    this.mChartConfig = {
        chart: {
            zoomType: 'x'
        },
        title: {
            text: this.mGame.name
        },
        xAxis: {
            title: {
                text: 'Time'
            },
            type: 'datetime',
            plotLines: []
        },
        yAxis: {
            title: {
                text: 'HR value'
            }
        },
        legend: {
            enabled: false
        },
        plotOptions: {
            area: {
                marker: {
                    radius: 1
                },
                lineWidth: 1,
                states: {
                    hover: {
                        lineWidth: 1
                    }
                },
                threshold: null
            }
        },
        series: []
    };
};

APP.ExperimentViewer.prototype.setSerieVisibility = function(theSerie, theVisibility) {
    if(!theSerie) {
        return;
    }

    if(theVisibility) {
        theSerie.show();
    } else {
        theSerie.hide();
    }
};

APP.ExperimentViewer.prototype.addChartController = function(theInfo) {
    var i,
        aAlreadyExists = false;

    for(i = 0; i < this.mChartControlers.length; i++) {
        if(this.mChartControlers[i].id == theInfo.id) {
            aAlreadyExists = true;
            break;
        }
    }

    if(!aAlreadyExists) {
        this.mChartControlers.push(theInfo);
    }
}

APP.ExperimentViewer.prototype.heartRate = function(theStatus, theRedraw) {
    var aHr,
        i,
        aStart,
        aSerie,
        aId = 'heartRate',
        aData = [];

    theRedraw = theRedraw == undefined ? true : theRedraw;
    theStatus = theStatus == undefined ? true : theStatus;

    aSerie = this.mChart.get(aId);

    if(aSerie) {
        // Series exists.
        this.setSerieVisibility(aSerie, theStatus);

    } else {
        // Serie does not exist.
        if(theStatus) {
            aHr = this.mGame.hr;
            aStart = this.mGame.start;

            for(i = 0; i < aHr.length; i++) {
                aData.push([i * 1000, aHr[i]]);
            }

            this.mChart.addSeries({
                type: 'area',
                name: 'HR',
                data: aData,
                id: aId
            }, theRedraw);

            this.addChartController({id: aId, name: 'HR'});
        }
    }
};

APP.ExperimentViewer.prototype.stressReport = function(theStatus, theRedraw) {
    var aQuestionnaire = this.mRawData.questionnaires[this.mIndex].data,
        aId = 'stressReport';

    theRedraw = theRedraw == undefined ? true : theRedraw;
    theStatus = theStatus == undefined ? true : theStatus;

    aSerie = this.mChart.get(aId);

    if(aSerie) {
        // Series exists.
        this.setSerieVisibility(aSerie, theStatus);

    } else {
        // Serie does not exist.
        if(theStatus) {
            this.mChart.addSeries({
                id: aId,
                type: 'line',
                name: 'Self-reported stress',
                color: '#FF0000',
                data: [
                    {
                        name: 'Stress at beginning',
                        color: '#FF0000',
                        y: aQuestionnaire[1].a / 5 * 100, // TODO: turn magic number into a constant
                        x: 5000
                    },
                    {
                        name: 'Stress at end',
                        color: '#FF0000',
                        y: aQuestionnaire[3].a / 5 * 100,  // TODO: turn magic number into a constant
                        x: (this.mGame.hr.length - 8) * 1000
                    }
                ],
                dataLabels : {
                    enabled : true,
                    formatter : function() {
                        return this.y / 100 * 5;  // TODO: turn magic number into a constant
                    }
                },
                tooltip: {
                    pointFormatter: function () {
                        return '' + (this.y / 100 * 5) + '';  // TODO: turn magic number into a constant
                    }
                }
            }, theRedraw);

            this.addChartController({id: aId, name: 'Stress'});
        }
    }
};

APP.ExperimentViewer.prototype.boredomReport = function(theStatus, theRedraw) {
    var aQuestionnaire = this.mRawData.questionnaires[this.mIndex].data,
        aId = 'boredomReport';

    theRedraw = theRedraw == undefined ? true : theRedraw;
    theStatus = theStatus == undefined ? true : theStatus;

    aSerie = this.mChart.get(aId);

    if(aSerie) {
        // Series exists.
        this.setSerieVisibility(aSerie, theStatus);

    } else {
        // Serie does not exist.
        if(theStatus) {
            this.mChart.addSeries({
                id: aId,
                type: 'line',
                name: 'Self-reported boredom',
                color: '#00FF00',
                data: [
                    {
                        name: 'Boredom at beginning',
                        color: '#00FF00',
                        y: aQuestionnaire[0].a / 5 * 100,  // TODO: turn magic number into a constant
                        x: 5000
                    },
                    {
                        name: 'Boredom at end',
                        color: '#00FF00',
                        y: aQuestionnaire[2].a / 5 * 100,  // TODO: turn magic number into a constant
                        x: (this.mGame.hr.length - 8) * 1000
                    }
                ],
                dataLabels : {
                    enabled : true,
                    formatter : function() {
                        return this.y / 100 * 5;  // TODO: turn magic number into a constant
                    }
                },
                tooltip: {
                    pointFormatter: function () {
                        return '' + (this.y / 100 * 5) + '';  // TODO: turn magic number into a constant
                    }
                }
            }, theRedraw);

            this.addChartController({id: aId, name: 'Boredom'});
        }
    }
};

APP.ExperimentViewer.prototype.hrBaseline = function(theStatus, theRedraw) {
    var aId = 'hrBaseline';

    theRedraw = theRedraw == undefined ? true : theRedraw;
    theStatus = theStatus == undefined ? true : theStatus;

    if(theStatus) {
        this.mChart.yAxis[0].addPlotLine({
            id: aId,
            value: this.mRawData.baseline,
            color: '#8C00FF',
            dashStyle: 'shortdash',
            width: 3,
            label: {
                text: 'HR baseline'
            }
        }, theRedraw);

        this.addChartController({id: aId, name: 'HR baseline'});
    } else {
        this.mChart.yAxis[0].removePlotLine(aId);
    }
};

APP.ExperimentViewer.prototype.hrBaselinedMeans = function(theStatus, theRedraw) {
    var i,
        aSeriesData = [],
        aTotal,
        aPart,
        aScale = 5,
        aId = 'hrBaselinedMeans';

    theRedraw = theRedraw == undefined ? true : theRedraw;
    theStatus = theStatus == undefined ? true : theStatus;

    aSerie = this.mChart.get(aId);

    if(theStatus) {
        if(aSerie) {
            this.setSerieVisibility(aSerie, true);

        } else {
            aTotal = (this.mGame.end - this.mGame.start) * 1000;
            aPart = aTotal / this.mGame['hr-means-baseline'].length;

            for(i = 0; i < this.mGame['hr-means-baseline'].length; i++) {
                aSeriesData.push([aPart * i + aPart / 2, this.mGame['hr-means-baseline'][i] * aScale]);
            }

            this.mChart.addSeries({
                id: aId,
                type: 'spline',
                name: 'HR variation relative to baseline',
                color: '#FFFF00',
                data: aSeriesData,
                dataLabels : {
                    enabled : false,
                    formatter : function() {
                        return this.y / aScale;
                    }
                },
                tooltip: {
                    pointFormatter: function () {
                        return '' + (this.y / aScale) + '';
                    },
                    xDateFormat: 'HR variation:'
                }
            }, theRedraw);

            this.addChartController({id: aId, name: 'HR baselined means'});
        }

        this.mChart.yAxis[0].removePlotLine(aId);
        this.mChart.yAxis[0].addPlotLine({
            id: aId,
            value: 0,
            color: '#000',
            dashStyle: 'shortdash',
            width: 3,
            label: {
                text: 'Equal to baseline'
            }
        }, theRedraw);

    } else {
        if(aSerie) {
            this.setSerieVisibility(aSerie, false);
        }
        this.mChart.yAxis[0].removePlotLine(aId);
    }
};

APP.ExperimentViewer.prototype.isInGameAction = function(theName) {
    return theName != 'question' && theName != 'newBlock';
};

APP.ExperimentViewer.prototype.isInGameEvent = function(theName) {
    return theName == 'question' || theName == 'newBlock' || theName == 'difficulty';
};

APP.ExperimentViewer.prototype.inGameActions = function(theStatus, theRedraw) {
    var i,
        aSeriesData = [],
        aTime,
        aActions,
        aId = 'inGameActions';

    theRedraw = theRedraw == undefined ? true : theRedraw;
    theStatus = theStatus == undefined ? true : theStatus;
    aActions  = this.mGame.actions;

    aSerie = this.mChart.get(aId);

    if(aSerie) {
        // Series exists.
        this.setSerieVisibility(aSerie, theStatus);

    } else {
        // Serie does not exist.
        if(theStatus) {
            for(i = 0; i < aActions.length; i++) {
                aTime = (aActions[i].timestamp/1000 - this.mGame.start) * 1000;

                if(this.isInGameAction(aActions.action)) {
                    aSeriesData.push({
                        x: aTime,
                        y: aActions[i].value,
                        name: aActions[i].action,
                        color: '#FF00FF'
                    });
                }
            }

            this.mChart.addSeries({
                id: aId,
                type: 'spline',
                name: 'In-game actions',
                color: '#FF8800',
                data: aSeriesData,
                tooltip: {
                    pointFormatter: function () {
                        return ' ';
                    },
                }
            }, theRedraw);

            this.addChartController({id: aId, name: 'In-game actions'});
        }
    }
};

APP.ExperimentViewer.prototype.inGameEvents = function(theStatus, theRedraw) {
    var i,
        aTime,
        aActions,
        aId = 'inGameEvents',
        aExists = false;

    theRedraw = theRedraw == undefined ? true : theRedraw;
    theStatus = theStatus == undefined ? true : theStatus;
    aActions  = this.mGame.actions;

    if(theStatus) {

        for(i = 0; i < aActions.length; i++) {
            aTime = (aActions[i].timestamp/1000 - this.mGame.start) * 1000;

            if(this.isInGameEvent(aActions[i].action)) {
                this.mChart.xAxis[0].addPlotLine({
                    color: '#FF00FF',
                    dashStyle: 'longdashdot',
                    value: aTime,
                    width: 1,
                    id: 'events' + i
                }, false);
            }
        }

        this.addChartController({id: aId, name: 'In-game events'});

    } else {
        for(i = 0; i < aActions.length; i++) {
            if(this.isInGameEvent(aActions[i].action)) {
                this.mChart.xAxis[0].removePlotLine('events' + i, false);
            }
        }
    }

    this.mChart.redraw();
};

APP.ExperimentViewer.prototype.enjoymentArea = function(theStatus, theRedraw) {
    var aAnswer = this.mRawData.questionnaires[this.mIndex].data[4].a | 0,
        aLabel = this.mRawData.questionnaires[this.mIndex].data[4].al,
        aTotal,
        aId = 'enjoymentArea',
        aPart;

    theRedraw = theRedraw == undefined ? true : theRedraw;
    theStatus = theStatus == undefined ? true : theStatus;

    aTotal = this.mGame.end - this.mGame.start;
    aPart = aTotal / 5;
    aAnswer--;

    if(theStatus) {
        this.mChart.xAxis[0].addPlotBand({
            color: '#E3FFE0',
            from: aAnswer * aPart * 1000,
            to: (aAnswer * aPart + aPart) * 1000,
            label: {
                text: 'Enjoyed the most
' + aLabel
            },
            id: aId
        });

        this.addChartController({id: aId, name: 'Enjoyment area'});

    } else {
        this.mChart.xAxis[0].removePlotBand(aId, theRedraw);
    }
};

APP.ExperimentViewer.prototype.handleChartOptionChange = function(theEvent) {
    var aIndex,
        aMethod,
        aSelf;

    // Get information regarding the viewer that triggered this click
    aIndex = $(this).data('index');
    aSelf  = APP.instance.getExperimentViewerByIndex(aIndex);

    // Call the propert function to handle the data
    aMethod  = $(this).data('method');
    aSelf[aMethod](theEvent.target.checked);
};

APP.ExperimentViewer.prototype.initChartControlOptions = function(theContainerId) {
    var i,
        aOut = '',
        aId;

    for(i = 0; i < this.mChartControlers.length; i++) {
        aId = this.mChartControlers[i].id + this.mIndex;
        aOut += '<input type="checkbox" name="' + aId + '" id="' + aId + '" data-method="' + this.mChartControlers[i].id + '" data-index="' + this.mIndex + '" checked="checked" />' + this.mChartControlers[i].name +'';
    }

    $('#' + theContainerId).append('
' + aOut + '
');
    $('.chart-controls input').off().on('change', this.handleChartOptionChange);
};

APP.ExperimentViewer.prototype.init = function(theContainerId) {
    var aId = 'chart' + this.mIndex;

    $('#' + theContainerId).append('

');

    this.mChartConfig.chart.renderTo = aId;
    this.mChart = new Highcharts.Chart(this.mChartConfig);

    this.heartRate(true, false);
    this.stressReport(true, false);
    this.boredomReport(true, false);
    this.hrBaseline(true, false);
    this.hrBaselinedMeans(true, false);
    this.inGameActions(true, false);
    this.inGameEvents(true, false);
    this.enjoymentArea(true, false);

    this.mChart.redraw(false);

    this.initChartControlOptions(theContainerId);
};
<commit_msg>Presets for chart analysis<commit_after>var APP = APP || {};

APP.ExperimentViewer = function(theIndex, theData) {
    this.mIndex = theIndex;
    this.mRawData = theData;
    this.mGame = this.mRawData.games[this.mIndex];
    this.mChart = null;
    this.mChartControlers = [];
    this.mChartControlPresets = {
        'None': [],
        'HR (complete)': ['heartRate', 'hrBaseline', 'hrBaselinedMeans'],
        'HR (basic)': ['heartRate', 'hrBaseline'],
        'Emotions': ['stressReport', 'boredomReport', 'enjoymentArea', 'hrBaselinedMeans'],
        'In-game (basic)': ['inGameActions', 'inGameEvents'],
        'In-game (medium)': ['heartRate', 'hrBaseline', 'hrBaselinedMeans', 'inGameActions', 'inGameEvents'],
        'In-game (complete)': ['heartRate', 'hrBaseline', 'hrBaselinedMeans', 'enjoymentArea', 'inGameActions', 'inGameEvents']
    };
    this.mChartConfig = {
        chart: {
            zoomType: 'x'
        },
        title: {
            text: this.mGame.name
        },
        xAxis: {
            title: {
                text: 'Time'
            },
            type: 'datetime',
            plotLines: []
        },
        yAxis: {
            title: {
                text: 'HR value'
            }
        },
        legend: {
            enabled: false
        },
        plotOptions: {
            area: {
                marker: {
                    radius: 1
                },
                lineWidth: 1,
                states: {
                    hover: {
                        lineWidth: 1
                    }
                },
                threshold: null
            }
        },
        series: []
    };
};

APP.ExperimentViewer.prototype.setSerieVisibility = function(theSerie, theVisibility) {
    if(!theSerie) {
        return;
    }

    if(theVisibility) {
        theSerie.show();
    } else {
        theSerie.hide();
    }
};

APP.ExperimentViewer.prototype.addChartController = function(theInfo) {
    var i,
        aAlreadyExists = false;

    for(i = 0; i < this.mChartControlers.length; i++) {
        if(this.mChartControlers[i].id == theInfo.id) {
            aAlreadyExists = true;
            break;
        }
    }

    if(!aAlreadyExists) {
        this.mChartControlers.push(theInfo);
    }
}

APP.ExperimentViewer.prototype.heartRate = function(theStatus, theRedraw) {
    var aHr,
        i,
        aStart,
        aSerie,
        aId = 'heartRate',
        aData = [];

    theRedraw = theRedraw == undefined ? true : theRedraw;
    theStatus = theStatus == undefined ? true : theStatus;

    aSerie = this.mChart.get(aId);

    if(aSerie) {
        // Series exists.
        this.setSerieVisibility(aSerie, theStatus);

    } else {
        // Serie does not exist.
        if(theStatus) {
            aHr = this.mGame.hr;
            aStart = this.mGame.start;

            for(i = 0; i < aHr.length; i++) {
                aData.push([i * 1000, aHr[i]]);
            }

            this.mChart.addSeries({
                type: 'area',
                name: 'HR',
                data: aData,
                id: aId
            }, theRedraw);

            this.addChartController({id: aId, name: 'HR'});
        }
    }
};

APP.ExperimentViewer.prototype.stressReport = function(theStatus, theRedraw) {
    var aQuestionnaire = this.mRawData.questionnaires[this.mIndex].data,
        aId = 'stressReport';

    theRedraw = theRedraw == undefined ? true : theRedraw;
    theStatus = theStatus == undefined ? true : theStatus;

    aSerie = this.mChart.get(aId);

    if(aSerie) {
        // Series exists.
        this.setSerieVisibility(aSerie, theStatus);

    } else {
        // Serie does not exist.
        if(theStatus) {
            this.mChart.addSeries({
                id: aId,
                type: 'line',
                name: 'Self-reported stress',
                color: '#FF0000',
                data: [
                    {
                        name: 'Stress at beginning',
                        color: '#FF0000',
                        y: aQuestionnaire[1].a / 5 * 100, // TODO: turn magic number into a constant
                        x: 5000
                    },
                    {
                        name: 'Stress at end',
                        color: '#FF0000',
                        y: aQuestionnaire[3].a / 5 * 100,  // TODO: turn magic number into a constant
                        x: (this.mGame.hr.length - 8) * 1000
                    }
                ],
                dataLabels : {
                    enabled : true,
                    formatter : function() {
                        return this.y / 100 * 5;  // TODO: turn magic number into a constant
                    }
                },
                tooltip: {
                    pointFormatter: function () {
                        return '' + (this.y / 100 * 5) + '';  // TODO: turn magic number into a constant
                    }
                }
            }, theRedraw);

            this.addChartController({id: aId, name: 'Stress'});
        }
    }
};

APP.ExperimentViewer.prototype.boredomReport = function(theStatus, theRedraw) {
    var aQuestionnaire = this.mRawData.questionnaires[this.mIndex].data,
        aId = 'boredomReport';

    theRedraw = theRedraw == undefined ? true : theRedraw;
    theStatus = theStatus == undefined ? true : theStatus;

    aSerie = this.mChart.get(aId);

    if(aSerie) {
        // Series exists.
        this.setSerieVisibility(aSerie, theStatus);

    } else {
        // Serie does not exist.
        if(theStatus) {
            this.mChart.addSeries({
                id: aId,
                type: 'line',
                name: 'Self-reported boredom',
                color: '#00FF00',
                data: [
                    {
                        name: 'Boredom at beginning',
                        color: '#00FF00',
                        y: aQuestionnaire[0].a / 5 * 100,  // TODO: turn magic number into a constant
                        x: 5000
                    },
                    {
                        name: 'Boredom at end',
                        color: '#00FF00',
                        y: aQuestionnaire[2].a / 5 * 100,  // TODO: turn magic number into a constant
                        x: (this.mGame.hr.length - 8) * 1000
                    }
                ],
                dataLabels : {
                    enabled : true,
                    formatter : function() {
                        return this.y / 100 * 5;  // TODO: turn magic number into a constant
                    }
                },
                tooltip: {
                    pointFormatter: function () {
                        return '' + (this.y / 100 * 5) + '';  // TODO: turn magic number into a constant
                    }
                }
            }, theRedraw);

            this.addChartController({id: aId, name: 'Boredom'});
        }
    }
};

APP.ExperimentViewer.prototype.hrBaseline = function(theStatus, theRedraw) {
    var aId = 'hrBaseline';

    theRedraw = theRedraw == undefined ? true : theRedraw;
    theStatus = theStatus == undefined ? true : theStatus;

    if(theStatus) {
        this.mChart.yAxis[0].addPlotLine({
            id: aId,
            value: this.mRawData.baseline,
            color: '#8C00FF',
            dashStyle: 'shortdash',
            width: 3,
            label: {
                text: 'HR baseline'
            }
        }, theRedraw);

        this.addChartController({id: aId, name: 'HR baseline'});
    } else {
        this.mChart.yAxis[0].removePlotLine(aId);
    }
};

APP.ExperimentViewer.prototype.hrBaselinedMeans = function(theStatus, theRedraw) {
    var i,
        aSeriesData = [],
        aTotal,
        aPart,
        aScale = 5,
        aId = 'hrBaselinedMeans';

    theRedraw = theRedraw == undefined ? true : theRedraw;
    theStatus = theStatus == undefined ? true : theStatus;

    aSerie = this.mChart.get(aId);

    if(theStatus) {
        if(aSerie) {
            this.setSerieVisibility(aSerie, true);

        } else {
            aTotal = (this.mGame.end - this.mGame.start) * 1000;
            aPart = aTotal / this.mGame['hr-means-baseline'].length;

            for(i = 0; i < this.mGame['hr-means-baseline'].length; i++) {
                aSeriesData.push([aPart * i + aPart / 2, this.mGame['hr-means-baseline'][i] * aScale]);
            }

            this.mChart.addSeries({
                id: aId,
                type: 'spline',
                name: 'HR variation relative to baseline',
                color: '#FFFF00',
                data: aSeriesData,
                dataLabels : {
                    enabled : false,
                    formatter : function() {
                        return this.y / aScale;
                    }
                },
                tooltip: {
                    pointFormatter: function () {
                        return '' + (this.y / aScale) + '';
                    },
                    xDateFormat: 'HR variation:'
                }
            }, theRedraw);

            this.addChartController({id: aId, name: 'HR baselined means'});
        }

        this.mChart.yAxis[0].removePlotLine(aId);
        this.mChart.yAxis[0].addPlotLine({
            id: aId,
            value: 0,
            color: '#000',
            dashStyle: 'shortdash',
            width: 3,
            label: {
                text: 'Equal to baseline'
            }
        }, theRedraw);

    } else {
        if(aSerie) {
            this.setSerieVisibility(aSerie, false);
        }
        this.mChart.yAxis[0].removePlotLine(aId);
    }
};

APP.ExperimentViewer.prototype.isInGameAction = function(theName) {
    return theName != 'question' && theName != 'newBlock';
};

APP.ExperimentViewer.prototype.isInGameEvent = function(theName) {
    return theName == 'question' || theName == 'newBlock' || theName == 'difficulty';
};

APP.ExperimentViewer.prototype.inGameActions = function(theStatus, theRedraw) {
    var i,
        aSeriesData = [],
        aTime,
        aActions,
        aId = 'inGameActions';

    theRedraw = theRedraw == undefined ? true : theRedraw;
    theStatus = theStatus == undefined ? true : theStatus;
    aActions  = this.mGame.actions;

    aSerie = this.mChart.get(aId);

    if(aSerie) {
        // Series exists.
        this.setSerieVisibility(aSerie, theStatus);

    } else {
        // Serie does not exist.
        if(theStatus) {
            for(i = 0; i < aActions.length; i++) {
                aTime = (aActions[i].timestamp/1000 - this.mGame.start) * 1000;

                if(this.isInGameAction(aActions.action)) {
                    aSeriesData.push({
                        x: aTime,
                        y: aActions[i].value,
                        name: aActions[i].action,
                        color: '#FF00FF'
                    });
                }
            }

            this.mChart.addSeries({
                id: aId,
                type: 'spline',
                name: 'In-game actions',
                color: '#FF8800',
                data: aSeriesData,
                tooltip: {
                    pointFormatter: function () {
                        return ' ';
                    },
                }
            }, theRedraw);

            this.addChartController({id: aId, name: 'In-game actions'});
        }
    }
};

APP.ExperimentViewer.prototype.inGameEvents = function(theStatus, theRedraw) {
    var i,
        aTime,
        aActions,
        aId = 'inGameEvents',
        aExists = false;

    theRedraw = theRedraw == undefined ? true : theRedraw;
    theStatus = theStatus == undefined ? true : theStatus;
    aActions  = this.mGame.actions;

    if(theStatus) {

        for(i = 0; i < aActions.length; i++) {
            aTime = (aActions[i].timestamp/1000 - this.mGame.start) * 1000;

            if(this.isInGameEvent(aActions[i].action)) {
                this.mChart.xAxis[0].addPlotLine({
                    color: '#FF00FF',
                    dashStyle: 'longdashdot',
                    value: aTime,
                    width: 1,
                    id: 'events' + i
                }, false);
            }
        }

        this.addChartController({id: aId, name: 'In-game events'});

    } else {
        for(i = 0; i < aActions.length; i++) {
            if(this.isInGameEvent(aActions[i].action)) {
                this.mChart.xAxis[0].removePlotLine('events' + i, false);
            }
        }
    }

    this.mChart.redraw();
};

APP.ExperimentViewer.prototype.enjoymentArea = function(theStatus, theRedraw) {
    var aAnswer = this.mRawData.questionnaires[this.mIndex].data[4].a | 0,
        aLabel = this.mRawData.questionnaires[this.mIndex].data[4].al,
        aTotal,
        aId = 'enjoymentArea',
        aPart;

    theRedraw = theRedraw == undefined ? true : theRedraw;
    theStatus = theStatus == undefined ? true : theStatus;

    aTotal = this.mGame.end - this.mGame.start;
    aPart = aTotal / 5;
    aAnswer--;

    if(theStatus) {
        this.mChart.xAxis[0].addPlotBand({
            color: '#E3FFE0',
            from: aAnswer * aPart * 1000,
            to: (aAnswer * aPart + aPart) * 1000,
            label: {
                text: 'Enjoyed the most
' + aLabel
            },
            id: aId
        });

        this.addChartController({id: aId, name: 'Enjoyment area'});

    } else {
        this.mChart.xAxis[0].removePlotBand(aId, theRedraw);
    }
};

APP.ExperimentViewer.prototype.handleChartOptionChange = function(theEvent) {
    var aIndex,
        aMethod,
        aSelf;

    // Get information regarding the viewer that triggered this click
    aIndex = $(this).data('index');
    aSelf  = APP.instance.getExperimentViewerByIndex(aIndex);

    // Call the propert function to handle the data
    aMethod  = $(this).data('method');
    aSelf[aMethod](theEvent.target.checked);
};

APP.ExperimentViewer.prototype.handleChartPreset = function(theEvent) {
    var aIndex,
        aValue,
        i,
        aPreset,
        aId,
        aShouldEnable,
        aSelf;

    // Get information regarding the viewer that triggered this click
    aIndex = $(this).data('index');
    aSelf  = APP.instance.getExperimentViewerByIndex(aIndex);

    aValue  = $(this).val();
    aPreset = aSelf.mChartControlPresets[aValue];

    for(i = 0; i < aSelf.mChartControlers.length; i++) {
        aShouldEnable = aPreset.indexOf(aSelf.mChartControlers[i].id) != -1;
        aId = '#' + aSelf.mChartControlers[i].id + aSelf.mIndex;

        if((aShouldEnable && !$(aId).is(':checked')) || (!aShouldEnable && $(aId).is(':checked'))) {
            $(aId).trigger('click');
        }
    }
};

APP.ExperimentViewer.prototype.initChartControlOptions = function(theContainerId) {
    var i,
        aOut = '',
        aName,
        aId;

    for(i = 0; i < this.mChartControlers.length; i++) {
        aId = this.mChartControlers[i].id + this.mIndex;
        aOut += '<input type="checkbox" name="' + aId + '" id="' + aId + '" data-method="' + this.mChartControlers[i].id + '" data-index="' + this.mIndex + '" checked="checked" />' + this.mChartControlers[i].name +'';
    }

    aOut += 'Select: <select name="preset" data-index="' + this.mIndex + '"><option value=""></option>';
    for(aName in this.mChartControlPresets) {
        aOut += '<option value="' + aName + '">' + aName + '</option>';
    }
    aOut += '</select>';

    $('#' + theContainerId).append('
' + aOut + '
');
    $('.chart-controls input').off().on('change', this.handleChartOptionChange);
    $('.chart-controls select').off().on('change', this.handleChartPreset);
};

APP.ExperimentViewer.prototype.init = function(theContainerId) {
    var aId = 'chart' + this.mIndex;

    $('#' + theContainerId).append('

');

    this.mChartConfig.chart.renderTo = aId;
    this.mChart = new Highcharts.Chart(this.mChartConfig);

    this.heartRate(true, false);
    this.stressReport(true, false);
    this.boredomReport(true, false);
    this.hrBaseline(true, false);
    this.hrBaselinedMeans(true, false);
    this.inGameActions(true, false);
    this.inGameEvents(true, false);
    this.enjoymentArea(true, false);

    this.mChart.redraw(false);

    this.initChartControlOptions(theContainerId);
};
<|endoftext|>


<commit_before>/*
 * Copyright (c) 2015-2016 Codenvy, S.A.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Codenvy, S.A. - initial API and implementation
 */
'use strict';

/**
 * This class is handling the service for viewing the IDE
 * @author Florent Benoit
 */
class IdeSvc {

  /**
   * Default constructor that is using resource
   * @ngInject for Dependency injection
   */
  constructor(cheAPI, $rootScope, lodash, $mdDialog, userDashboardConfig, $timeout, $websocket, $sce, proxySettings, $location, routeHistory, $q, $log, cheWorkspace) {
    this.cheAPI = cheAPI;
    this.$rootScope = $rootScope;
    this.lodash = lodash;
    this.$mdDialog = $mdDialog;
    this.$timeout = $timeout;
    this.$websocket = $websocket;
    this.userDashboardConfig = userDashboardConfig;
    this.$sce = $sce;
    this.proxySettings = proxySettings;
    this.$location = $location;
    this.routeHistory = routeHistory;
    this.$q = $q;
    this.$log = $log;
    this.cheWorkspace = cheWorkspace;

    this.ideParams = new Map();

    this.lastWorkspace = null;
    this.openedWorkspace = null;

    this.listeningChannels = [];
  }

  displayIDE() {
    this.$rootScope.showIDE = true;
  }

  restoreIDE() {
    this.$rootScope.restoringIDE = true;
    this.displayIDE();
  }

  hasIdeLink() {
    return this.$rootScope.ideIframeLink && (this.$rootScope.ideIframeLink !== null);
  }

  handleError(error) {
    this.$log.error(error);
  }

  startIde(workspace) {
    if (this.lastWorkspace) {
      this.cleanupChannels(this.lastWorkspace.id);
    }
    this.lastWorkspace = workspace;

    if (this.openedWorkspace && this.openedWorkspace.id === workspace.id) {
      this.openedWorkspace = null;
    }

    this.updateRecentWorkspace(workspace.id);

    let bus = this.cheAPI.getWebsocket().getBus(workspace.id);

    let startWorkspaceDefer = this.$q.defer();
    this.startWorkspace(bus, workspace).then(() => {
      // update list of workspaces
      // for new workspace to show in recent workspaces
      this.cheAPI.cheWorkspace.fetchWorkspaces();

      this.cheWorkspace.fetchStatusChange(workspace.id, 'RUNNING').then(() => {
        return this.cheWorkspace.fetchWorkspaceDetails(workspace.id);
      }).then(() => {
        startWorkspaceDefer.resolve();
      }, (error) => {
        this.handleError(error);
        startWorkspaceDefer.reject(error);
      });
      this.cheWorkspace.fetchStatusChange(workspace.id, 'ERROR').then((data) => {
        startWorkspaceDefer.reject(data);
      });
    }, (error) => {
      startWorkspaceDefer.reject(error);
    });

    return startWorkspaceDefer.promise.then(() => {
      if (this.lastWorkspace && workspace.id === this.lastWorkspace.id) {
        // Now that the container is started, wait for the extension server. For this, needs to get runtime details
        let websocketUrl = this.cheWorkspace.getWebsocketUrl(workspace.id);
        // try to connect
        this.websocketReconnect = 50;
        this.connectToExtensionServer(websocketUrl, workspace.id);
      } else {
        this.cleanupChannels(workspace.id);
      }
      return this.$q.resolve();
    }, (error) => {
      if (this.lastWorkspace && workspace.id === this.lastWorkspace.id) {
        this.cleanupChannels(workspace.id);
      }
      return this.$q.reject(error);
    });
  }

  startWorkspace(bus, data) {

    let startWorkspacePromise = this.cheAPI.getWorkspace().startWorkspace(data.id, data.config.defaultEnv);

    startWorkspacePromise.then((data) => {
      // get channels
      let environments = data.config.environments;
      let defaultEnvName = data.config.defaultEnv;
      let defaultEnvironment = this.lodash.find(environments, (environment) => {
        return environment.name === defaultEnvName;
      });

      let machineConfigsLinks = defaultEnvironment.machineConfigs[0].links;
      let findStatusLink = this.lodash.find(machineConfigsLinks, (machineConfigsLink) => {
        return machineConfigsLink.rel === 'get machine status channel';
      });

      let workspaceId = data.id;

      let agentChannel = 'workspace:' + data.id + ':ext-server:output';
      let statusChannel = findStatusLink ? findStatusLink.parameters[0].defaultValue : null;

      this.listeningChannels.push(statusChannel);
      // for now, display log of status channel in case of errors
      bus.subscribe(statusChannel, (message) => {
        if (message.eventType === 'DESTROYED' && message.workspaceId === data.id && !this.$rootScope.showIDE) {
          // need to show the error
          this.$mdDialog.show(
            this.$mdDialog.alert()
              .title('Unable to start workspace')
              .content('Unable to start workspace. It may be linked to OutOfMemory or the container has been destroyed')
              .ariaLabel('Workspace start')
              .ok('OK')
          );
        }
        if (message.eventType === 'ERROR' && message.workspaceId === data.id) {
          // need to show the error
          this.$mdDialog.show(
            this.$mdDialog.alert()
              .title('Error when starting workspace')
              .content('Unable to start workspace. Error when trying to start the workspace: ' + message.error)
              .ariaLabel('Workspace start')
              .ok('OK')
          );
        }
        this.$log.log('Status channel of workspaceID', workspaceId, message);
      });

      this.listeningChannels.push(agentChannel);
      bus.subscribe(agentChannel, (message) => {
        if (message.eventType === 'ERROR' && message.workspaceId === data.id) {
          // need to show the error
          this.$mdDialog.show(
            this.$mdDialog.alert()
              .title('Error when starting agent')
              .content('Unable to start workspace agent. Error when trying to start the workspace agent: ' + message.error)
              .ariaLabel('Workspace agent start')
              .ok('OK')
          );
        }
      });
    }, (error) => {
      this.handleError(error);
      this.$q.reject(error);
    });

    return startWorkspacePromise;
  }

  connectToExtensionServer(websocketURL, workspaceId) {
    // try to connect
    let websocketStream = this.$websocket(websocketURL);

    // on success, create project
    websocketStream.onOpen(() => {
      this.cleanupChannels(workspaceId, websocketStream);
    });

    // on error, retry to connect or after a delay, abort
    websocketStream.onError((error) => {
      this.websocketReconnect--;
      if (this.websocketReconnect > 0) {
        this.$timeout(() => {
          this.connectToExtensionServer(websocketURL, workspaceId);
        }, 1000);
      } else {
        this.cleanupChannels(workspaceId, websocketStream);
        this.$log.error('error when starting remote extension', error);
        // need to show the error
        this.$mdDialog.show(
          this.$mdDialog.alert()
            .title('Unable to create project')
            .content('Unable to connect to the remote extension server after workspace creation')
            .ariaLabel('Project creation')
            .ok('OK')
        );
      }
    });
  }

  setLoadingParameter(paramName, paramValue) {
    this.ideParams.set(paramName, paramValue);
  }

  setIDEAction(ideAction) {
    this.ideAction = ideAction;
  }

  openIde(workspaceId) {
    this.$rootScope.hideNavbar = false;

    this.updateRecentWorkspace(workspaceId);

    let inDevMode = this.userDashboardConfig.developmentMode;
    let randVal = Math.floor((Math.random() * 1000000) + 1);
    let appendUrl = '?uid=' + randVal;

    let workspace = this.cheWorkspace.getWorkspaceById(workspaceId);
    this.openedWorkspace = workspace;

    let ideUrlLink = this.getHrefLink(workspace, 'ide url');

    if (this.ideAction != null) {
      appendUrl = appendUrl + '&action=' + this.ideAction;

      // reset action
      this.ideAction = null;
    }

    if (this.ideParams) {
      for (var [key, val] of this.ideParams) {
        appendUrl = appendUrl + '&' + key + '=' + val;
      }
      this.ideParams.clear();
    }

    if (inDevMode) {
      this.$rootScope.ideIframeLink = this.$sce.trustAsResourceUrl(ideUrlLink + appendUrl);
    } else {
      this.$rootScope.ideIframeLink = ideUrlLink + appendUrl;
    }

    let defer = this.$q.defer();
    if (workspace.status === 'RUNNING') {
      defer.resolve();
    } else {
      this.cheWorkspace.fetchStatusChange(workspace.id, 'STARTING').then(() => {
        defer.resolve();
      }, (error) => {
        defer.reject(error);
        this.$log.error('Unable to start workspace: ', error);
      })
    }
    defer.promise.then(() => {
      // update list of recent workspaces
      this.cheWorkspace.fetchWorkspaces();
    });
  }

  /**
   * Cleanup the websocket channels (unsubscribe)
   */
  cleanupChannels(workspaceId, websocketStream) {
    if (websocketStream != null) {
      websocketStream.close();
    }

    let workspaceBus = this.cheAPI.getWebsocket().getBus(workspaceId);

    if (workspaceBus != null) {
      this.listeningChannels.forEach((channel) => {
        workspaceBus.unsubscribe(channel);
      });
      this.listeningChannels.length = 0;
    }
  }

  /**
   * Gets link from a workspace
   * @param workspace the workspace on which analyze the links
   * @param name the name of the link to find (rel attribute)
   * @returns empty or the href attribute of the link
   */
  getHrefLink(workspace, name) {
    let links = workspace.links;
    var i = 0;
    while (i < links.length) {
      let link = links[i];
      if (link.rel === name) {
        return link.href;
      }
      i++;
    }
    return '';
  }

  /**
   * Emit event to move workspace immediately
   * to top of the recent workspaces list
   *
   * @param workspaceId
   */
  updateRecentWorkspace(workspaceId) {
    this.$rootScope.$broadcast('recent-workspace:set', workspaceId);
  }
}

export default IdeSvc;
<commit_msg>CHE-1415: improve error message on workspace runtime killed<commit_after>/*
 * Copyright (c) 2015-2016 Codenvy, S.A.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Codenvy, S.A. - initial API and implementation
 */
'use strict';

/**
 * This class is handling the service for viewing the IDE
 * @author Florent Benoit
 */
class IdeSvc {

  /**
   * Default constructor that is using resource
   * @ngInject for Dependency injection
   */
  constructor(cheAPI, $rootScope, lodash, $mdDialog, userDashboardConfig, $timeout, $websocket, $sce, proxySettings, $location, routeHistory, $q, $log, cheWorkspace) {
    this.cheAPI = cheAPI;
    this.$rootScope = $rootScope;
    this.lodash = lodash;
    this.$mdDialog = $mdDialog;
    this.$timeout = $timeout;
    this.$websocket = $websocket;
    this.userDashboardConfig = userDashboardConfig;
    this.$sce = $sce;
    this.proxySettings = proxySettings;
    this.$location = $location;
    this.routeHistory = routeHistory;
    this.$q = $q;
    this.$log = $log;
    this.cheWorkspace = cheWorkspace;

    this.ideParams = new Map();

    this.lastWorkspace = null;
    this.openedWorkspace = null;

    this.listeningChannels = [];
  }

  displayIDE() {
    this.$rootScope.showIDE = true;
  }

  restoreIDE() {
    this.$rootScope.restoringIDE = true;
    this.displayIDE();
  }

  hasIdeLink() {
    return this.$rootScope.ideIframeLink && (this.$rootScope.ideIframeLink !== null);
  }

  handleError(error) {
    this.$log.error(error);
  }

  startIde(workspace) {
    if (this.lastWorkspace) {
      this.cleanupChannels(this.lastWorkspace.id);
    }
    this.lastWorkspace = workspace;

    if (this.openedWorkspace && this.openedWorkspace.id === workspace.id) {
      this.openedWorkspace = null;
    }

    this.updateRecentWorkspace(workspace.id);

    let bus = this.cheAPI.getWebsocket().getBus(workspace.id);

    let startWorkspaceDefer = this.$q.defer();
    this.startWorkspace(bus, workspace).then(() => {
      // update list of workspaces
      // for new workspace to show in recent workspaces
      this.cheAPI.cheWorkspace.fetchWorkspaces();

      this.cheWorkspace.fetchStatusChange(workspace.id, 'RUNNING').then(() => {
        return this.cheWorkspace.fetchWorkspaceDetails(workspace.id);
      }).then(() => {
        startWorkspaceDefer.resolve();
      }, (error) => {
        this.handleError(error);
        startWorkspaceDefer.reject(error);
      });
      this.cheWorkspace.fetchStatusChange(workspace.id, 'ERROR').then((data) => {
        startWorkspaceDefer.reject(data);
      });
    }, (error) => {
      startWorkspaceDefer.reject(error);
    });

    return startWorkspaceDefer.promise.then(() => {
      if (this.lastWorkspace && workspace.id === this.lastWorkspace.id) {
        // Now that the container is started, wait for the extension server. For this, needs to get runtime details
        let websocketUrl = this.cheWorkspace.getWebsocketUrl(workspace.id);
        // try to connect
        this.websocketReconnect = 50;
        this.connectToExtensionServer(websocketUrl, workspace.id);
      } else {
        this.cleanupChannels(workspace.id);
      }
      return this.$q.resolve();
    }, (error) => {
      if (this.lastWorkspace && workspace.id === this.lastWorkspace.id) {
        this.cleanupChannels(workspace.id);
      }
      return this.$q.reject(error);
    });
  }

  startWorkspace(bus, data) {

    let startWorkspacePromise = this.cheAPI.getWorkspace().startWorkspace(data.id, data.config.defaultEnv);

    startWorkspacePromise.then((data) => {
      // get channels
      let environments = data.config.environments;
      let defaultEnvName = data.config.defaultEnv;
      let defaultEnvironment = this.lodash.find(environments, (environment) => {
        return environment.name === defaultEnvName;
      });

      let machineConfigsLinks = defaultEnvironment.machineConfigs[0].links;
      let findStatusLink = this.lodash.find(machineConfigsLinks, (machineConfigsLink) => {
        return machineConfigsLink.rel === 'get machine status channel';
      });

      let workspaceId = data.id;

      let agentChannel = 'workspace:' + data.id + ':ext-server:output';
      let statusChannel = findStatusLink ? findStatusLink.parameters[0].defaultValue : null;

      this.listeningChannels.push(statusChannel);
      // for now, display log of status channel in case of errors
      bus.subscribe(statusChannel, (message) => {
        if (message.eventType === 'DESTROYED' && message.workspaceId === data.id && !this.$rootScope.showIDE) {
          // need to show the error
          this.$mdDialog.show(
            this.$mdDialog.alert()
              .title('Unable to start the workspace runtime')
              .content('Your workspace runtime is no longer available. It was either destroyed or ran out of memory.')
              .ariaLabel('Workspace start')
              .ok('OK')
          );
        }
        if (message.eventType === 'ERROR' && message.workspaceId === data.id) {
          // need to show the error
          this.$mdDialog.show(
            this.$mdDialog.alert()
              .title('Error when starting workspace')
              .content('Unable to start workspace. Error when trying to start the workspace: ' + message.error)
              .ariaLabel('Workspace start')
              .ok('OK')
          );
        }
        this.$log.log('Status channel of workspaceID', workspaceId, message);
      });

      this.listeningChannels.push(agentChannel);
      bus.subscribe(agentChannel, (message) => {
        if (message.eventType === 'ERROR' && message.workspaceId === data.id) {
          // need to show the error
          this.$mdDialog.show(
            this.$mdDialog.alert()
              .title('Error when starting agent')
              .content('Unable to start workspace agent. Error when trying to start the workspace agent: ' + message.error)
              .ariaLabel('Workspace agent start')
              .ok('OK')
          );
        }
      });
    }, (error) => {
      this.handleError(error);
      this.$q.reject(error);
    });

    return startWorkspacePromise;
  }

  connectToExtensionServer(websocketURL, workspaceId) {
    // try to connect
    let websocketStream = this.$websocket(websocketURL);

    // on success, create project
    websocketStream.onOpen(() => {
      this.cleanupChannels(workspaceId, websocketStream);
    });

    // on error, retry to connect or after a delay, abort
    websocketStream.onError((error) => {
      this.websocketReconnect--;
      if (this.websocketReconnect > 0) {
        this.$timeout(() => {
          this.connectToExtensionServer(websocketURL, workspaceId);
        }, 1000);
      } else {
        this.cleanupChannels(workspaceId, websocketStream);
        this.$log.error('error when starting remote extension', error);
        // need to show the error
        this.$mdDialog.show(
          this.$mdDialog.alert()
            .title('Unable to create project')
            .content('Unable to connect to the remote extension server after workspace creation')
            .ariaLabel('Project creation')
            .ok('OK')
        );
      }
    });
  }

  setLoadingParameter(paramName, paramValue) {
    this.ideParams.set(paramName, paramValue);
  }

  setIDEAction(ideAction) {
    this.ideAction = ideAction;
  }

  openIde(workspaceId) {
    this.$rootScope.hideNavbar = false;

    this.updateRecentWorkspace(workspaceId);

    let inDevMode = this.userDashboardConfig.developmentMode;
    let randVal = Math.floor((Math.random() * 1000000) + 1);
    let appendUrl = '?uid=' + randVal;

    let workspace = this.cheWorkspace.getWorkspaceById(workspaceId);
    this.openedWorkspace = workspace;

    let ideUrlLink = this.getHrefLink(workspace, 'ide url');

    if (this.ideAction != null) {
      appendUrl = appendUrl + '&action=' + this.ideAction;

      // reset action
      this.ideAction = null;
    }

    if (this.ideParams) {
      for (var [key, val] of this.ideParams) {
        appendUrl = appendUrl + '&' + key + '=' + val;
      }
      this.ideParams.clear();
    }

    if (inDevMode) {
      this.$rootScope.ideIframeLink = this.$sce.trustAsResourceUrl(ideUrlLink + appendUrl);
    } else {
      this.$rootScope.ideIframeLink = ideUrlLink + appendUrl;
    }

    let defer = this.$q.defer();
    if (workspace.status === 'RUNNING') {
      defer.resolve();
    } else {
      this.cheWorkspace.fetchStatusChange(workspace.id, 'STARTING').then(() => {
        defer.resolve();
      }, (error) => {
        defer.reject(error);
        this.$log.error('Unable to start workspace: ', error);
      })
    }
    defer.promise.then(() => {
      // update list of recent workspaces
      this.cheWorkspace.fetchWorkspaces();
    });
  }

  /**
   * Cleanup the websocket channels (unsubscribe)
   */
  cleanupChannels(workspaceId, websocketStream) {
    if (websocketStream != null) {
      websocketStream.close();
    }

    let workspaceBus = this.cheAPI.getWebsocket().getBus(workspaceId);

    if (workspaceBus != null) {
      this.listeningChannels.forEach((channel) => {
        workspaceBus.unsubscribe(channel);
      });
      this.listeningChannels.length = 0;
    }
  }

  /**
   * Gets link from a workspace
   * @param workspace the workspace on which analyze the links
   * @param name the name of the link to find (rel attribute)
   * @returns empty or the href attribute of the link
   */
  getHrefLink(workspace, name) {
    let links = workspace.links;
    var i = 0;
    while (i < links.length) {
      let link = links[i];
      if (link.rel === name) {
        return link.href;
      }
      i++;
    }
    return '';
  }

  /**
   * Emit event to move workspace immediately
   * to top of the recent workspaces list
   *
   * @param workspaceId
   */
  updateRecentWorkspace(workspaceId) {
    this.$rootScope.$broadcast('recent-workspace:set', workspaceId);
  }
}

export default IdeSvc;
<|endoftext|>


<commit_before>// Great new taggable widget that requires jQuery UI to be loaded.
// Enhances an existing text field (progressive enhancement). See
// the README

function pkInlineTaggableWidget(selector, options)
{
	$(selector).each(function()
	{
		// Semi-global
		var typeaheadUrl = options['typeahead-url'];
		var tagsLabel = options['tags-label'];
		var popularTags = options['popular-tags'];
		var existingTags = {};
		var allTags = options['all-tags'];
		var commitSelector = options['commit-selector'];
		var commitEvent = options['commit-event'];

		if (typeof(tagsLabel) == 'undefined')
		{
			tagsLabel = "Existing Tags";
		};
		if (typeof(popularTags) == 'undefined')
		{
			popularTags = {};
		};
		if (typeof(allTags) == 'undefined')
		{
			allTags == {};
		};
		if (typeof(commitEvent) == 'undefined')
		{
			commitEvent = 'click';
		}

		// populate existingTags
		if ($(this).val().trim() != '')
		{
			var lp = $(this).val().split(',');
			for (x in lp)
			{
				existingTags[lp[x].trim()] = lp[x].trim();
			}
		}

		function makePopularLink(attributes, title, text)
		{
			var new_tag = $('');
			var new_link = $('');
			new_tag.html(""+text+"");
			new_tag.attr({ title: title }).addClass('a-tag a-popular');
			new_tag.prepend(new_link);
			new_link.text(title);
			new_link.attr(attributes);
			return new_tag;
		}

		function makeRemoveLink(attributes, title, text)
		{
			title = $.trim(title);

			var new_tag = $('');
			var new_link = $('');
			var tagTitle = title;

			if (typeof(allTags) != 'undefined')
			{
				if (typeof(allTags[title]) != 'undefined')
				{
					title = title + ' - ' + allTags[title];
				}
				else
				{
					title = title + ' - 0';
				}
			}

			new_tag.html(""+title+"");
			new_tag.attr({ title: tagTitle }).addClass('a-tag');
			new_link.text('Remove Tag');
			new_link.attr(attributes);
			new_link.attr({ title: 'Remove' }).addClass('a-btn icon a-close-small no-label no-bg');
			new_link.prepend('');
			new_tag.append(new_link);
			return new_tag;
		}

		function trimExcessCommas(string)
		{
			string = string.replace(/(^,)|(, ?$)/g, '');
			string = string.replace(/(,,)|(, ,)/, ',');
			string = $.trim(string);

			return string;
		}

		// For multiple word typeahead we need some helpers

		function split( val ) {
			return val.split( /,\s*/ );
		}

		function extractLast( term ) {
			return split( term ).pop();
		}

		function multipleSelect(event, ui)
		{
			var terms = split( this.value );
			// remove the current input
			terms.pop();
			// add the selected item
			terms.push( ui.item.value );
			// add placeholder to get the comma-and-space at the end
			terms.push( "" );
			this.value = terms.join( ", " );
			return false;
		}

		function multipleFocus()
		{
			// prevent value inserted on focus
			return false;
		}

		function multipleSearch()
		{
			// custom minLength
			var term = extractLast( this.value );
			if ( term.length < 2 ) {
				return false;
			}
		}

		// We don't want to display popular tags that we're already using
		var unusedPopulars = {};
		for (x in popularTags)
		{
			if (typeof(existingTags[x]) == 'undefined')
			{
				unusedPopulars[x] = popularTags[x];
			}
		}

		var popularsAttributes = {};
		var existingTagsAttributes = {};
		var existingDiv = $('
');
		var popularsDiv = $('
');

		// Establish the quick enhancement
		var tagInput = $(this);
		var typeAheadContainer = $('
');
		var typeAheadBox = $('<input />');
		var typeAheadBoxId = 'inline-tag-ahead-box-' + Math.floor(Math.random() * 2000);
		typeAheadBox.attr('type', 'text');
		typeAheadBox.attr('id', typeAheadBoxId);
		if ((typeof(allTags) == 'undefined') && (typeof(typeaheadUrl) != 'undefined'))
		{
			typeAheadBox.autocomplete({
			  source: function (request, response) {
					$.getJSON(typeaheadUrl, {
						  term: extractLast(request.term)
					  }, response);
				},
				search: multipleSearch,
				focus: multipleFocus,
				select: multipleSelect
			});
		}
		else if (typeof(allTags) != 'undefined')
		{
			var allTagsReformat = new Array();
			for (x in allTags)
			{
				allTagsReformat.push(x);
			}
			typeAheadBox.autocomplete({
				source: function (request, response) {
					response( $.ui.autocomplete.filter(
										allTagsReformat, extractLast( request.term ) ) );
				},
				search: multipleSearch,
				focus: multipleFocus,
				select: multipleSelect
			});
		}

		var addButton = $('');
		addButton.html('Add');
		addButton.attr({'href' : '#', 'class' : 'a-btn icon a-add add-tags-link', 'title' : 'Add these tags'});

		typeAheadContainer.addClass('a-inline-taggable-widget').append(typeAheadBox).append(addButton);

		tagInput.hide();
		tagInput.parent().append(typeAheadContainer);

		// Add a list of popular tags to be added
		function addTagsToForm(link)
		{
			var tag = link.attr('title');
			var value = tagInput.val() + ', ' + tag;

			value = trimExcessCommas(value);
			tagInput.val(value);

			link.remove();

			var new_link = makeRemoveLink(existingTagsAttributes, tag, tag + ' x');
			new_link.children('a').bind('click', function() { removeTagsFromForm($(this).parent()); return false; });
			existingDiv.append(new_link);
		}

		// Add a list of tags that may be removed
		function removeTagsFromForm(link)
		{
			var tag = link.attr('title');
			var value = tagInput.val();

			value = value.replace(tag, '');
			value = trimExcessCommas(value);
			tagInput.val(value);

			link.remove();

			// As we have just removed it from the list, we want the real deal populars.
			if (typeof(popularTags[tag]) != 'undefined')
			{
				var linkLabel = popularTags[tag];
				var new_link = makePopularLink(existingTagsAttributes, tag, linkLabel);
				new_link.children('a').bind('click', function() { addTagsToForm($(this).parent()); return false; });
				popularsDiv.append(new_link);
			}
		}

		// a maker function for tag containers
		function makeTagContainer(containerLabel, tagArray, linkAttributes, linkLabelType)
		{
			// Add a list of tags that may be removed
			var tagContainer = $('
');
			tagContainer.addClass('a-inline-taggable-widget-tag-container');
			var header = $('
');
			header.text(containerLabel);
			tagContainer.append(header);

			var attributes = {};
			for (x in tagArray)
			{
				var linkLabel = '';
				if (linkLabelType == 'add')
				{
					tagContainer.addClass('a-popular-tags');

					linkLabel = tagArray[x];
					var new_link = makePopularLink(linkAttributes, x, linkLabel);
					new_link.children('a').bind('click', function() { addTagsToForm($(this).parent());  return false; });
				}
				else if (linkLabelType == 'remove')
				{
					tagContainer.addClass('a-existing-tags');

					linkLabel = 'x ' + x;
					var new_link = makeRemoveLink(linkAttributes, x, linkLabel);
					new_link.children('a').bind('click', function() { removeTagsFromForm($(this).parent());  return false; });
				}
				tagContainer.append(new_link);
			}
			return tagContainer;
		}

		// Add the new tags to the existing form input.
		// If the user doesn't click Save changes or add... tough?
		function commitTagsToForm()
		{
			if (typeAheadBox.val() != '')
			{
				var value = tagInput.val() + ',' + typeAheadBox.val();
				value = trimExcessCommas(value);
				tagInput.val(value);
				typeAheadBox.val('');

				existingTags = {};
				var lp = value.split(',');
				for (x in lp)
				{
					existingTags[lp[x]] = lp[x];
				}

				existingDiv.html(makeTagContainer(tagsLabel, existingTags, existingTagsAttributes, 'remove').html());
				existingDiv.find('a').each(function() {
					$(this).bind('click', function() {
						removeTagsFromForm($(this).parent());
						return false;
					});
				});
			}
			return false;
		}
		addButton.bind('click', function() { commitTagsToForm(); return false; });
		if (commitSelector != 'undefined')
		{
			$(commitSelector).bind(commitEvent, function() {
				commitTagsToForm();
				return true;
			});
		}

		existingDiv = makeTagContainer(tagsLabel, existingTags, existingTagsAttributes, 'remove');
		existingDiv.children('a').bind('click', function() { removeTagsFromForm($(this));  return false; });
		tagInput.parent().prepend(existingDiv);

		popularsDiv = makeTagContainer('Popular Tags', unusedPopulars, popularsAttributes, 'add');
		popularsDiv.children('a').bind('click', function() { addTagsToForm($(this));  return false; });
		tagInput.parent().append(popularsDiv);

		// Catch that enter key, baby!
		$(document).keyup(function(e)
			{
				if (e.keyCode == 13)
				{
					e.preventDefault();
				}
			});

		$(document).keypress(function(e)
			{
				if (e.keyCode == 13)
				{
					e.preventDefault();
				}
			});

		typeAheadBox.keyup(function(e)
			{
		 		if (e.keyCode == 13)
		 		{
		 			commitTagsToForm();
		 		}
		 	});
	});

}

// DEPRECATED. Requires the old sfJqueryReloadedPlugin and generally is not as cool.

// You need to bring in jQuery first in order for this to work
//
// Call it like this:
// pkTagahead(<?php echo json_encode(url_for("taggableComplete/complete")) ?>);
//
// Or similar. Now all of your input elements with the input-tag class
// automatically gain the typeahead suggestion feature.
//
// If you're not using Symfony and sfDoctrineActAsTaggablePlugin,
// pass your own URL that returns a
 containing
s with the
// FULL TEXT of what the ENTIRE tag string will be if the user takes
// that suggestion, with the new tag you're suggesting an  link
// to #. Then use CSS to hide (visibility: none) the part of the
//
 that is not in the . Don't introduce any extra whitespace.

function pkTagahead(tagaheadUrl)
{
  $(function() {
    function getKey(event)
    {
      // Portable keycodes sigh
      return event.keyCode ? event.keyCode : event.which;
    }
    function setClick(target)
    {
      $(target).find('a').click(function(event)
      {
        // span contains ul contains li contains a
        var span = this.parentNode.parentNode.parentNode;
        var input = $(span).data("tag-peer");
        // Get text from the li
        var parent = this.parentNode;
        $(input).val($(parent).text());
        $(input).focus();
        return false;
      });
    }
    // Add suggestions span (you'll need to style that)
    $('input.tag-input').after("

");
    // Each tag field remembers its suggestions span...
    $('input.tag-input').each(function()
    {
      $(this).data("tag-peer", $(this).next()[0]);
    });
    // And vice versa
    $('div.tag-suggestions').each(function()
    {
      $(this).data("tag-peer", $(this).prev()[0]);
    });
    // Now we can really throw down
    $('input.tag-input').keyup(function(event)
    {
      var key = getKey(event);
      // Tab key
      if (key == 9)
      {
        var peer = $(this).data("tag-peer");
        var suggestions = $(peer).find("li");
        if (suggestions.length)
        {
          $(this).val($(suggestions[0]).text());
          $(this).focus();
        }
        // In any case don't insert the tab
        return false;
      }
      else
      {
        // Trigger ajax update of suggestions
      }
    });
    $('input.tag-input').keypress(function(event)
    {
      // Firefox 2.0 mac is stubborn and only allows cancel here
      // (we will still get the keyup and do the real work there)
      var key = getKey(event);
      if (key == 9)
      {
        // Don't insert tabs, ever
        return false;
      }
    });
    var lastValues = {};
    setInterval(function()
    {
      // AJAX query for suggestions only when changes have taken place
      $('input.tag-input').each(function()
      {
        var last = $(this).data('tag-last');
        var value = $(this).val();
        var peer = $(this).data('tag-peer');
        if (last !== value)
        {
          $(this).data('tag-last', value);
          $.post(
            tagaheadUrl,
            {
              current: $(this).val()
            },
            function(data, textStatus)
            {
              $(peer).html(data);
              setClick(peer);
            }
          );
        }
      });
    }, 200);
  });
}
<commit_msg>added spans around the tag count<commit_after>// Great new taggable widget that requires jQuery UI to be loaded.
// Enhances an existing text field (progressive enhancement). See
// the README

function pkInlineTaggableWidget(selector, options)
{
	$(selector).each(function()
	{
		// Semi-global
		var typeaheadUrl = options['typeahead-url'];
		var tagsLabel = options['tags-label'];
		var popularTags = options['popular-tags'];
		var existingTags = {};
		var allTags = options['all-tags'];
		var commitSelector = options['commit-selector'];
		var commitEvent = options['commit-event'];

		if (typeof(tagsLabel) == 'undefined')
		{
			tagsLabel = "Existing Tags";
		};
		if (typeof(popularTags) == 'undefined')
		{
			popularTags = {};
		};
		if (typeof(allTags) == 'undefined')
		{
			allTags == {};
		};
		if (typeof(commitEvent) == 'undefined')
		{
			commitEvent = 'click';
		}

		// populate existingTags
		if ($(this).val().trim() != '')
		{
			var lp = $(this).val().split(',');
			for (x in lp)
			{
				existingTags[lp[x].trim()] = lp[x].trim();
			}
		}

		function makePopularLink(attributes, title, text)
		{
			var new_tag = $('');
			var new_link = $('');
			new_tag.html(""+text+"");
			new_tag.attr({ title: title }).addClass('a-tag a-popular');
			new_tag.prepend(new_link);
			new_link.text(title);
			new_link.attr(attributes);
			return new_tag;
		}

		function makeRemoveLink(attributes, title, text)
		{
			title = $.trim(title);

			var new_tag = $('');
			var new_link = $('');
			var tagTitle = title;

			if (typeof(allTags) != 'undefined')
			{
				if (typeof(allTags[title]) != 'undefined')
				{
					title = title + '(' + allTags[title] + ')';
				}
				else
				{
					title = title + '(0)';
				}
			}

			new_tag.html(""+title+"");
			new_tag.attr({ title: tagTitle }).addClass('a-tag');
			new_link.text('Remove Tag');
			new_link.attr(attributes);
			new_link.attr({ title: 'Remove' }).addClass('a-btn icon a-close-small no-label no-bg');
			new_link.prepend('');
			new_tag.append(new_link);
			return new_tag;
		}

		function trimExcessCommas(string)
		{
			string = string.replace(/(^,)|(, ?$)/g, '');
			string = string.replace(/(,,)|(, ,)/, ',');
			string = $.trim(string);

			return string;
		}

		// For multiple word typeahead we need some helpers

		function split( val ) {
			return val.split( /,\s*/ );
		}

		function extractLast( term ) {
			return split( term ).pop();
		}

		function multipleSelect(event, ui)
		{
			var terms = split( this.value );
			// remove the current input
			terms.pop();
			// add the selected item
			terms.push( ui.item.value );
			// add placeholder to get the comma-and-space at the end
			terms.push( "" );
			this.value = terms.join( ", " );
			return false;
		}

		function multipleFocus()
		{
			// prevent value inserted on focus
			return false;
		}

		function multipleSearch()
		{
			// custom minLength
			var term = extractLast( this.value );
			if ( term.length < 2 ) {
				return false;
			}
		}

		// We don't want to display popular tags that we're already using
		var unusedPopulars = {};
		for (x in popularTags)
		{
			if (typeof(existingTags[x]) == 'undefined')
			{
				unusedPopulars[x] = popularTags[x];
			}
		}

		var popularsAttributes = {};
		var existingTagsAttributes = {};
		var existingDiv = $('
');
		var popularsDiv = $('
');

		// Establish the quick enhancement
		var tagInput = $(this);
		var typeAheadContainer = $('
');
		var typeAheadBox = $('<input />');
		var typeAheadBoxId = 'inline-tag-ahead-box-' + Math.floor(Math.random() * 2000);
		typeAheadBox.attr('type', 'text');
		typeAheadBox.attr('id', typeAheadBoxId);
		if ((typeof(allTags) == 'undefined') && (typeof(typeaheadUrl) != 'undefined'))
		{
			typeAheadBox.autocomplete({
			  source: function (request, response) {
					$.getJSON(typeaheadUrl, {
						  term: extractLast(request.term)
					  }, response);
				},
				search: multipleSearch,
				focus: multipleFocus,
				select: multipleSelect
			});
		}
		else if (typeof(allTags) != 'undefined')
		{
			var allTagsReformat = new Array();
			for (x in allTags)
			{
				allTagsReformat.push(x);
			}
			typeAheadBox.autocomplete({
				source: function (request, response) {
					response( $.ui.autocomplete.filter(
										allTagsReformat, extractLast( request.term ) ) );
				},
				search: multipleSearch,
				focus: multipleFocus,
				select: multipleSelect
			});
		}

		var addButton = $('');
		addButton.html('Add');
		addButton.attr({'href' : '#', 'class' : 'a-btn icon a-add add-tags-link', 'title' : 'Add these tags'});

		typeAheadContainer.addClass('a-inline-taggable-widget').append(typeAheadBox).append(addButton);

		tagInput.hide();
		tagInput.parent().append(typeAheadContainer);

		// Add a list of popular tags to be added
		function addTagsToForm(link)
		{
			var tag = link.attr('title');
			var value = tagInput.val() + ', ' + tag;

			value = trimExcessCommas(value);
			tagInput.val(value);

			link.remove();

			var new_link = makeRemoveLink(existingTagsAttributes, tag, tag + ' x');
			new_link.children('a').bind('click', function() { removeTagsFromForm($(this).parent()); return false; });
			existingDiv.append(new_link);
		}

		// Add a list of tags that may be removed
		function removeTagsFromForm(link)
		{
			var tag = link.attr('title');
			var value = tagInput.val();

			value = value.replace(tag, '');
			value = trimExcessCommas(value);
			tagInput.val(value);

			link.remove();

			// As we have just removed it from the list, we want the real deal populars.
			if (typeof(popularTags[tag]) != 'undefined')
			{
				var linkLabel = popularTags[tag];
				var new_link = makePopularLink(existingTagsAttributes, tag, linkLabel);
				new_link.children('a').bind('click', function() { addTagsToForm($(this).parent()); return false; });
				popularsDiv.append(new_link);
			}
		}

		// a maker function for tag containers
		function makeTagContainer(containerLabel, tagArray, linkAttributes, linkLabelType)
		{
			// Add a list of tags that may be removed
			var tagContainer = $('
');
			tagContainer.addClass('a-inline-taggable-widget-tag-container');
			var header = $('
');
			header.text(containerLabel);
			tagContainer.append(header);

			var attributes = {};
			for (x in tagArray)
			{
				var linkLabel = '';
				if (linkLabelType == 'add')
				{
					tagContainer.addClass('a-popular-tags');

					linkLabel = tagArray[x];
					var new_link = makePopularLink(linkAttributes, x, linkLabel);
					new_link.children('a').bind('click', function() { addTagsToForm($(this).parent());  return false; });
				}
				else if (linkLabelType == 'remove')
				{
					tagContainer.addClass('a-existing-tags');

					linkLabel = 'x ' + x;
					var new_link = makeRemoveLink(linkAttributes, x, linkLabel);
					new_link.children('a').bind('click', function() { removeTagsFromForm($(this).parent());  return false; });
				}
				tagContainer.append(new_link);
			}
			return tagContainer;
		}

		// Add the new tags to the existing form input.
		// If the user doesn't click Save changes or add... tough?
		function commitTagsToForm()
		{
			if (typeAheadBox.val() != '')
			{
				var value = tagInput.val() + ',' + typeAheadBox.val();
				value = trimExcessCommas(value);
				tagInput.val(value);
				typeAheadBox.val('');

				existingTags = {};
				var lp = value.split(',');
				for (x in lp)
				{
					existingTags[lp[x]] = lp[x];
				}

				existingDiv.html(makeTagContainer(tagsLabel, existingTags, existingTagsAttributes, 'remove').html());
				existingDiv.find('a').each(function() {
					$(this).bind('click', function() {
						removeTagsFromForm($(this).parent());
						return false;
					});
				});
			}
			return false;
		}
		addButton.bind('click', function() { commitTagsToForm(); return false; });
		if (commitSelector != 'undefined')
		{
			$(commitSelector).bind(commitEvent, function() {
				commitTagsToForm();
				return true;
			});
		}

		existingDiv = makeTagContainer(tagsLabel, existingTags, existingTagsAttributes, 'remove');
		existingDiv.children('a').bind('click', function() { removeTagsFromForm($(this));  return false; });
		tagInput.parent().prepend(existingDiv);

		popularsDiv = makeTagContainer('Popular Tags', unusedPopulars, popularsAttributes, 'add');
		popularsDiv.children('a').bind('click', function() { addTagsToForm($(this));  return false; });
		tagInput.parent().append(popularsDiv);

		// Catch that enter key, baby!
		$(document).keyup(function(e)
			{
				if (e.keyCode == 13)
				{
					e.preventDefault();
				}
			});

		$(document).keypress(function(e)
			{
				if (e.keyCode == 13)
				{
					e.preventDefault();
				}
			});

		typeAheadBox.keyup(function(e)
			{
		 		if (e.keyCode == 13)
		 		{
		 			commitTagsToForm();
		 		}
		 	});
	});

}

// DEPRECATED. Requires the old sfJqueryReloadedPlugin and generally is not as cool.

// You need to bring in jQuery first in order for this to work
//
// Call it like this:
// pkTagahead(<?php echo json_encode(url_for("taggableComplete/complete")) ?>);
//
// Or similar. Now all of your input elements with the input-tag class
// automatically gain the typeahead suggestion feature.
//
// If you're not using Symfony and sfDoctrineActAsTaggablePlugin,
// pass your own URL that returns a
 containing
s with the
// FULL TEXT of what the ENTIRE tag string will be if the user takes
// that suggestion, with the new tag you're suggesting an  link
// to #. Then use CSS to hide (visibility: none) the part of the
//
 that is not in the . Don't introduce any extra whitespace.

function pkTagahead(tagaheadUrl)
{
  $(function() {
    function getKey(event)
    {
      // Portable keycodes sigh
      return event.keyCode ? event.keyCode : event.which;
    }
    function setClick(target)
    {
      $(target).find('a').click(function(event)
      {
        // span contains ul contains li contains a
        var span = this.parentNode.parentNode.parentNode;
        var input = $(span).data("tag-peer");
        // Get text from the li
        var parent = this.parentNode;
        $(input).val($(parent).text());
        $(input).focus();
        return false;
      });
    }
    // Add suggestions span (you'll need to style that)
    $('input.tag-input').after("

");
    // Each tag field remembers its suggestions span...
    $('input.tag-input').each(function()
    {
      $(this).data("tag-peer", $(this).next()[0]);
    });
    // And vice versa
    $('div.tag-suggestions').each(function()
    {
      $(this).data("tag-peer", $(this).prev()[0]);
    });
    // Now we can really throw down
    $('input.tag-input').keyup(function(event)
    {
      var key = getKey(event);
      // Tab key
      if (key == 9)
      {
        var peer = $(this).data("tag-peer");
        var suggestions = $(peer).find("li");
        if (suggestions.length)
        {
          $(this).val($(suggestions[0]).text());
          $(this).focus();
        }
        // In any case don't insert the tab
        return false;
      }
      else
      {
        // Trigger ajax update of suggestions
      }
    });
    $('input.tag-input').keypress(function(event)
    {
      // Firefox 2.0 mac is stubborn and only allows cancel here
      // (we will still get the keyup and do the real work there)
      var key = getKey(event);
      if (key == 9)
      {
        // Don't insert tabs, ever
        return false;
      }
    });
    var lastValues = {};
    setInterval(function()
    {
      // AJAX query for suggestions only when changes have taken place
      $('input.tag-input').each(function()
      {
        var last = $(this).data('tag-last');
        var value = $(this).val();
        var peer = $(this).data('tag-peer');
        if (last !== value)
        {
          $(this).data('tag-last', value);
          $.post(
            tagaheadUrl,
            {
              current: $(this).val()
            },
            function(data, textStatus)
            {
              $(peer).html(data);
              setClick(peer);
            }
          );
        }
      });
    }, 200);
  });
}
<|endoftext|>


<commit_before>/*!
 * Amplify Request @VERSION
 *
 * Copyright 2011 appendTo LLC. (http://appendto.com/team)
 * Dual licensed under the MIT or GPL licenses.
 * http://appendto.com/open-source-licenses
 *
 * http://amplifyjs.com
 */
(function( amplify, undefined ) {

function noop() {}
function isFunction( obj ) {
	return ({}).toString.call( obj ) === "[object Function]";
}

amplify.request = function( resourceId, data, callback ) {
	// default to an empty hash just so we can handle a missing resourceId
	// in one place
	var settings = resourceId || {};

	if ( typeof settings === "string" ) {
		if ( isFunction( data ) ) {
			callback = data;
			data = {};
		}
		settings = {
			resourceId: resourceId,
			data: data || {},
			success: callback
		};
	}

	var request = { abort: noop },
		resource = amplify.request.resources[ settings.resourceId ],
		success = settings.success || noop,
		error = settings.error || noop;
	settings.success = function( data, extra, status ) {
		amplify.publish( "request.success", settings, data, extra, status );
		amplify.publish( "request.complete", settings, data, extra, status );
		success.apply( this, arguments );
	};
	settings.error = function( data, extra, status ) {
		amplify.publish( "request.error", settings, data, extra, status );
		amplify.publish( "request.complete", settings, data, extra, status );
		error.apply( this, arguments );
	};

	if ( !resource ) {
		if ( !settings.resourceId ) {
			throw "amplify.request: no resourceId provided";
		}
		throw "amplify.request: unknown resourceId: " + settings.resourceId;
	}

	if ( amplify.publish( "request.before", settings ) ) {
		amplify.request.resources[ settings.resourceId ]( settings, request );
	}

	return request;
};

amplify.request.types = {};
amplify.request.resources = {};
amplify.request.define = function( resourceId, type, settings ) {
	if ( typeof type === "string" ) {
		if ( !( type in amplify.request.types ) ) {
			throw "amplify.request.define: unknown type: " + type;
		}

		settings.resourceId = resourceId;
		amplify.request.resources[ resourceId ] =
			amplify.request.types[ type ]( settings );
	} else {
		// no pre-processor or settings for one-off types (don't invoke)
		amplify.request.resources[ resourceId ] = type;
	}
};

}( amplify ) );

(function( amplify, $, undefined ) {

amplify.request.types.ajax = function( defnSettings ) {
	defnSettings = $.extend({
		type: "GET"
	}, defnSettings );

	return function( settings, request ) {
		var url = defnSettings.url,
			data = settings.data,
			abort = request.abort,
			ajaxSettings,
			regex,
			xhr;

		if ( typeof data !== "string" ) {
			data = $.extend( true, {}, defnSettings.data, data );
			$.each( data, function( key, value ) {
				regex = new RegExp( "{" + key + "}", "g");
				if ( regex.test( url ) ) {
					url = url.replace( regex, value );
					delete data[ key ];
				}
			});
		}

		xhr = $.ajax($.extend( {}, defnSettings, {
			url: url,
			type: defnSettings.type,
			data: data,
			dataType: defnSettings.dataType,
			success: function( data, status, xhr ) {
				settings.success( data, xhr, status );
			},
			// data parameter is for custom overrides that proxy this function
			error: function( xhr, status, error, data ) {
				if ( data === undefined ) {
					// TODO: add support for ajax errors with data
					data = null;
				}
				settings.error( data, xhr, status );
			},
			beforeSend: function( xhr, ajaxSettings ) {
				var ret = defnSettings.beforeSend ?
					defnSettings.beforeSend.apply( this, arguments ) : true;
				return ret && amplify.publish( "request.before.ajax",
					defnSettings, settings, ajaxSettings, xhr );
			}
		}) );

		request.abort = function() {
			xhr.abort();
			abort.call( this );
		};
	};
};

var cache = amplify.request.cache = {
	_key: function( resourceId, data ) {
		var length = data.length,
			i = 0,
			checksum = chunk();

		while ( i < length ) {
			checksum ^= chunk();
		}

		function chunk() {
			return data.charCodeAt( i++ ) << 24 |
				data.charCodeAt( i++ ) << 16 |
				data.charCodeAt( i++ ) << 8 |
				data.charCodeAt( i++ ) << 0;
		}

		return "request-" + resourceId + "-" + checksum;
	},

	_default: (function() {
		var memoryStore = {};
		return function( resource, settings, ajaxSettings ) {
			// data is already converted to a string by the time we get here
			var cacheKey = cache._key( resource.resourceId, ajaxSettings.data ),
				duration = resource.cache;

			if ( cacheKey in memoryStore ) {
				ajaxSettings.success( memoryStore[ cacheKey ] );
				return false;
			}
			var success = ajaxSettings.success;
			ajaxSettings.success = function( data ) {
				memoryStore[ cacheKey ] = data;
				if ( typeof duration === "number" ) {
					setTimeout(function() {
						delete memoryStore[ cacheKey ];
					}, duration );
				}
				success.apply( this, arguments );
			};
		};
	}())
};

if ( amplify.store ) {
	$.each( amplify.store.types, function( type ) {
		cache[ type ] = function( resource, settings, ajaxSettings ) {
			var cacheKey = cache._key( resource.resourceId, ajaxSettings.data ),
				cached = amplify.store[ type ]( cacheKey );

			if ( cached ) {
				ajaxSettings.success( cached );
				return false;
			}
			var success = ajaxSettings.success;
			ajaxSettings.success = function( data ) {
				amplify.store[ type]( cacheKey, data , { expires: resource.expires } );
				success.apply( this, arguments );
			};
		};
	});
	cache.persist = cache[ amplify.store.type ];
}

amplify.subscribe( "request.before.ajax", function( resource ) {
	var cacheType = resource.cache;
	if ( cacheType ) {
		return cache[ cacheType in cache ? cacheType : "_default" ]
			.apply( this, arguments );
	}
});

amplify.request.decoders = {
	// http://labs.omniti.com/labs/jsend
	jsend: function( data, status, xhr, success, error ) {
		if ( data.status === "success" ) {
			success( data.data );
		} else if ( data.status === "fail" ) {
			error( data.data, "fail" );
		} else if ( data.status === "error" ) {
			delete data.status;
			error( data, "error" );
		}
	}
};

amplify.subscribe( "request.before.ajax", function( resource, settings, ajaxSettings ) {
	var _success = ajaxSettings.success,
		_error = ajaxSettings.error,
		decoder = $.isFunction( resource.decoder )
			? resource.decoder
			: resource.decoder in amplify.request.decoders
				? amplify.request.decoders[ resource.decoder ]
				: amplify.request.decoders._default;

	if ( !decoder ) {
		return;
	}

	function success( xhr ) {
		return function( data, status ) {
			_success( data, status || "success", xhr );
		};
	}
	function error( xhr, _status ) {
		return function( data, status ) {
			_error( xhr, status || _status, null, data );
		};
	}
	ajaxSettings.success = function( data, status, xhr ) {
		decoder( data, status, xhr, success( xhr ), error( xhr, "error" ) );
	};
	ajaxSettings.error = function( xhr, status ) {
		decoder( null, status, xhr, success( xhr ), error( xhr, status ) );
	};
});

}( amplify, jQuery ) );
<commit_msg>Request: allow object values for the cache option for setting type and expires<commit_after>/*!
 * Amplify Request @VERSION
 *
 * Copyright 2011 appendTo LLC. (http://appendto.com/team)
 * Dual licensed under the MIT or GPL licenses.
 * http://appendto.com/open-source-licenses
 *
 * http://amplifyjs.com
 */
(function( amplify, undefined ) {

function noop() {}
function isFunction( obj ) {
	return ({}).toString.call( obj ) === "[object Function]";
}

amplify.request = function( resourceId, data, callback ) {
	// default to an empty hash just so we can handle a missing resourceId
	// in one place
	var settings = resourceId || {};

	if ( typeof settings === "string" ) {
		if ( isFunction( data ) ) {
			callback = data;
			data = {};
		}
		settings = {
			resourceId: resourceId,
			data: data || {},
			success: callback
		};
	}

	var request = { abort: noop },
		resource = amplify.request.resources[ settings.resourceId ],
		success = settings.success || noop,
		error = settings.error || noop;
	settings.success = function( data, extra, status ) {
		amplify.publish( "request.success", settings, data, extra, status );
		amplify.publish( "request.complete", settings, data, extra, status );
		success.apply( this, arguments );
	};
	settings.error = function( data, extra, status ) {
		amplify.publish( "request.error", settings, data, extra, status );
		amplify.publish( "request.complete", settings, data, extra, status );
		error.apply( this, arguments );
	};

	if ( !resource ) {
		if ( !settings.resourceId ) {
			throw "amplify.request: no resourceId provided";
		}
		throw "amplify.request: unknown resourceId: " + settings.resourceId;
	}

	if ( amplify.publish( "request.before", settings ) ) {
		amplify.request.resources[ settings.resourceId ]( settings, request );
	}

	return request;
};

amplify.request.types = {};
amplify.request.resources = {};
amplify.request.define = function( resourceId, type, settings ) {
	if ( typeof type === "string" ) {
		if ( !( type in amplify.request.types ) ) {
			throw "amplify.request.define: unknown type: " + type;
		}

		settings.resourceId = resourceId;
		amplify.request.resources[ resourceId ] =
			amplify.request.types[ type ]( settings );
	} else {
		// no pre-processor or settings for one-off types (don't invoke)
		amplify.request.resources[ resourceId ] = type;
	}
};

}( amplify ) );

(function( amplify, $, undefined ) {

amplify.request.types.ajax = function( defnSettings ) {
	defnSettings = $.extend({
		type: "GET"
	}, defnSettings );

	return function( settings, request ) {
		var url = defnSettings.url,
			data = settings.data,
			abort = request.abort,
			ajaxSettings,
			regex,
			xhr;

		if ( typeof data !== "string" ) {
			data = $.extend( true, {}, defnSettings.data, data );
			$.each( data, function( key, value ) {
				regex = new RegExp( "{" + key + "}", "g");
				if ( regex.test( url ) ) {
					url = url.replace( regex, value );
					delete data[ key ];
				}
			});
		}

		xhr = $.ajax($.extend( {}, defnSettings, {
			url: url,
			type: defnSettings.type,
			data: data,
			dataType: defnSettings.dataType,
			success: function( data, status, xhr ) {
				settings.success( data, xhr, status );
			},
			// data parameter is for custom overrides that proxy this function
			error: function( xhr, status, error, data ) {
				if ( data === undefined ) {
					// TODO: add support for ajax errors with data
					data = null;
				}
				settings.error( data, xhr, status );
			},
			beforeSend: function( xhr, ajaxSettings ) {
				var ret = defnSettings.beforeSend ?
					defnSettings.beforeSend.apply( this, arguments ) : true;
				return ret && amplify.publish( "request.before.ajax",
					defnSettings, settings, ajaxSettings, xhr );
			}
		}) );

		request.abort = function() {
			xhr.abort();
			abort.call( this );
		};
	};
};

var cache = amplify.request.cache = {
	_key: function( resourceId, data ) {
		var length = data.length,
			i = 0,
			checksum = chunk();

		while ( i < length ) {
			checksum ^= chunk();
		}

		function chunk() {
			return data.charCodeAt( i++ ) << 24 |
				data.charCodeAt( i++ ) << 16 |
				data.charCodeAt( i++ ) << 8 |
				data.charCodeAt( i++ ) << 0;
		}

		return "request-" + resourceId + "-" + checksum;
	},

	_default: (function() {
		var memoryStore = {};
		return function( resource, settings, ajaxSettings ) {
			// data is already converted to a string by the time we get here
			var cacheKey = cache._key( resource.resourceId, ajaxSettings.data ),
				duration = resource.cache;

			if ( cacheKey in memoryStore ) {
				ajaxSettings.success( memoryStore[ cacheKey ] );
				return false;
			}
			var success = ajaxSettings.success;
			ajaxSettings.success = function( data ) {
				memoryStore[ cacheKey ] = data;
				if ( typeof duration === "number" ) {
					setTimeout(function() {
						delete memoryStore[ cacheKey ];
					}, duration );
				}
				success.apply( this, arguments );
			};
		};
	}())
};

if ( amplify.store ) {
	$.each( amplify.store.types, function( type ) {
		cache[ type ] = function( resource, settings, ajaxSettings ) {
			var cacheKey = cache._key( resource.resourceId, ajaxSettings.data ),
				cached = amplify.store[ type ]( cacheKey );

			if ( cached ) {
				ajaxSettings.success( cached );
				return false;
			}
			var success = ajaxSettings.success;
			ajaxSettings.success = function( data ) {
				amplify.store[ type]( cacheKey, data , { expires: resource.cache.expires } );
				success.apply( this, arguments );
			};
		};
	});
	cache.persist = cache[ amplify.store.type ];
}

amplify.subscribe( "request.before.ajax", function( resource ) {
	var cacheType = resource.cache;
	if ( cacheType ) {
		if ( typeof cacheType === 'object' ) {
			cacheType = cacheType.type;
		}
		return cache[ cacheType in cache ? cacheType : "_default" ]
			.apply( this, arguments );
	}
});

amplify.request.decoders = {
	// http://labs.omniti.com/labs/jsend
	jsend: function( data, status, xhr, success, error ) {
		if ( data.status === "success" ) {
			success( data.data );
		} else if ( data.status === "fail" ) {
			error( data.data, "fail" );
		} else if ( data.status === "error" ) {
			delete data.status;
			error( data, "error" );
		}
	}
};

amplify.subscribe( "request.before.ajax", function( resource, settings, ajaxSettings ) {
	var _success = ajaxSettings.success,
		_error = ajaxSettings.error,
		decoder = $.isFunction( resource.decoder )
			? resource.decoder
			: resource.decoder in amplify.request.decoders
				? amplify.request.decoders[ resource.decoder ]
				: amplify.request.decoders._default;

	if ( !decoder ) {
		return;
	}

	function success( xhr ) {
		return function( data, status ) {
			_success( data, status || "success", xhr );
		};
	}
	function error( xhr, _status ) {
		return function( data, status ) {
			_error( xhr, status || _status, null, data );
		};
	}
	ajaxSettings.success = function( data, status, xhr ) {
		decoder( data, status, xhr, success( xhr ), error( xhr, "error" ) );
	};
	ajaxSettings.error = function( xhr, status ) {
		decoder( null, status, xhr, success( xhr ), error( xhr, status ) );
	};
});

}( amplify, jQuery ) );
<|endoftext|>


<commit_before>App = Ember.Application.create();

App.Router.map(function() {
  this.route('index', { path: '/' });
});

App.LtiExtension = Ember.Object.extend({});

App.IndexController = Ember.ArrayController.extend({
  appName: 'my_lti_app',

  buildUrl: function() {
    var extNames = this.get('selectedExtensions').mapBy('optName');
    var host = Ember.ENV.host;
    return host + "/template/?ext=" + extNames;
  }.property('selectedExtensions', 'appName'),

  installCommand: function() {
    return "rails plugin new " + this.get('appName') + " -T \/\n\
    --mountable \/\n\
    --dummy-path=spec/test_app \/\n\
    -m " + this.get('buildUrl');
  }.property('buildUrl'),

  selectedExtensions: function() {
    return this.get('content').filterBy('isSelected', true);
  }.property('content.@each.isSelected')
});

App.IndexRoute = Ember.Route.extend({
  model: function() {
    return [
      App.LtiExtension.create({
        isSelected: false,
        name: "Rich Editor Button (WYSIWYG)",
        optName: "editor_button",
        description: "This shows adding an editor button to the rich editor in course/group content. Editor button extensions show up in Canvas as icons in the right editor, in this case as a fish icon. This simple demo app lets the user pick from a list of fish pictures. The picture they choose will be inserted into the rich editor.",
        imageUrl: "/images/editor_button.png",
        link: "http://www.eduappcenter.com/docs/extensions/canvas_wysiwyg"
      }),
      App.LtiExtension.create({
        isSelected: false,
        name: "Resource Selection",
        optName: "resource_selection",
        description: "In Canvas you can configure an LTI tool to select LTI launch URLs as module items. This app allows the user to pick and insert custom content as a link in course modules. When inserting content into a module, if the user picks \"External Tools\" they'll see the configured app with a \"find\" icon.",
        imageUrl: "/images/resource_selection.png",
        link: "http://www.eduappcenter.com/docs/extensions/canvas_link_selection"
      }),
      App.LtiExtension.create({
        isSelected: false,
        name: "Homework Submission",
        optName: "homework_submission",
        description: "In Canvas you can configure an LTI tool to turn in homework through an LTI launch.",
        imageUrl: null,
        link: "http://www.eduappcenter.com/docs/extensions/canvas_homework_submission"
      }),
      App.LtiExtension.create({
        isSelected: false,
        name: "Account Navigation",
        optName: "account_navigation",
        description: "This link is available to anyone with access to the account. The link will appear in the current account and any sub-accounts of that account.",
        imageUrl: null,
        link: "http://www.eduappcenter.com/docs/extensions/canvas_navigation"
      }),
      App.LtiExtension.create({
        isSelected: false,
        name: "Course Navigation",
        optName: "course_navigation",
        description: "This link is available to anyone with access to the course. If it's configured at the account level, the link will be added to all courses within that account. Permissions can be set to determine which user types have access to the app, and whether the app is enabled by default for all applicable courses, or if it needs to be configured manually for each course.",
        imageUrl: "/images/course_navigation.png",
        link: "http://www.eduappcenter.com/docs/extensions/canvas_navigation"
      }),
      App.LtiExtension.create({
        isSelected: false,
        name: "User Navigation",
        optName: "user_navigation",
        description: "This link is available to anyone with access to the institution, and will appear as a navigation item whey they click their profile or user information link.",
        imageUrl: "/images/user_navigation.png",
        link: "http://www.eduappcenter.com/docs/extensions/canvas_navigation"
      })
    ]
  }
});
<commit_msg>fixed console command<commit_after>App = Ember.Application.create();

App.Router.map(function() {
  this.route('index', { path: '/' });
});

App.LtiExtension = Ember.Object.extend({});

App.IndexController = Ember.ArrayController.extend({
  appName: 'my_lti_app',

  buildUrl: function() {
    var extNames = this.get('selectedExtensions').mapBy('optName');
    var host = Ember.ENV.host;
    return host + "/template/?ext=" + extNames;
  }.property('selectedExtensions', 'appName'),

  installCommand: function() {
    return "rails plugin new " + this.get('appName') + " -T \\\n\
    --mountable \\\n\
    --dummy-path=spec/test_app \\\n\
    -m " + this.get('buildUrl');
  }.property('buildUrl'),

  selectedExtensions: function() {
    return this.get('content').filterBy('isSelected', true);
  }.property('content.@each.isSelected')
});

App.IndexRoute = Ember.Route.extend({
  model: function() {
    return [
      App.LtiExtension.create({
        isSelected: false,
        name: "Rich Editor Button (WYSIWYG)",
        optName: "editor_button",
        description: "This shows adding an editor button to the rich editor in course/group content. Editor button extensions show up in Canvas as icons in the right editor, in this case as a fish icon. This simple demo app lets the user pick from a list of fish pictures. The picture they choose will be inserted into the rich editor.",
        imageUrl: "/images/editor_button.png",
        link: "http://www.eduappcenter.com/docs/extensions/canvas_wysiwyg"
      }),
      App.LtiExtension.create({
        isSelected: false,
        name: "Resource Selection",
        optName: "resource_selection",
        description: "In Canvas you can configure an LTI tool to select LTI launch URLs as module items. This app allows the user to pick and insert custom content as a link in course modules. When inserting content into a module, if the user picks \"External Tools\" they'll see the configured app with a \"find\" icon.",
        imageUrl: "/images/resource_selection.png",
        link: "http://www.eduappcenter.com/docs/extensions/canvas_link_selection"
      }),
      App.LtiExtension.create({
        isSelected: false,
        name: "Homework Submission",
        optName: "homework_submission",
        description: "In Canvas you can configure an LTI tool to turn in homework through an LTI launch.",
        imageUrl: null,
        link: "http://www.eduappcenter.com/docs/extensions/canvas_homework_submission"
      }),
      App.LtiExtension.create({
        isSelected: false,
        name: "Account Navigation",
        optName: "account_navigation",
        description: "This link is available to anyone with access to the account. The link will appear in the current account and any sub-accounts of that account.",
        imageUrl: null,
        link: "http://www.eduappcenter.com/docs/extensions/canvas_navigation"
      }),
      App.LtiExtension.create({
        isSelected: false,
        name: "Course Navigation",
        optName: "course_navigation",
        description: "This link is available to anyone with access to the course. If it's configured at the account level, the link will be added to all courses within that account. Permissions can be set to determine which user types have access to the app, and whether the app is enabled by default for all applicable courses, or if it needs to be configured manually for each course.",
        imageUrl: "/images/course_navigation.png",
        link: "http://www.eduappcenter.com/docs/extensions/canvas_navigation"
      }),
      App.LtiExtension.create({
        isSelected: false,
        name: "User Navigation",
        optName: "user_navigation",
        description: "This link is available to anyone with access to the institution, and will appear as a navigation item whey they click their profile or user information link.",
        imageUrl: "/images/user_navigation.png",
        link: "http://www.eduappcenter.com/docs/extensions/canvas_navigation"
      })
    ]
  }
});
<|endoftext|>