YetiForceCompany/YetiForceCRM

View on GitHub
public_html/layouts/resources/debugbar/logs.js

Summary

Maintainability
F
1 wk
Test Coverage
(function ($) {
    var csscls = PhpDebugBar.utils.makecsscls('phpdebugbar-widgets-');

    /**
     * Replaces spaces with &nbsp; and line breaks with <br>
     *
     * @param {String} text
     * @return {String}
     */
    var htmlize = (PhpDebugBar.Widgets.htmlize = function (text) {
        return text.replace(/\n/g, '<br>').replace(/\s/g, '&nbsp;');
    });

    /**
     * Returns a string representation of value, using JSON.stringify
     * if it's an object.
     *
     * @param {Object} value
     * @param {Boolean} prettify Uses htmlize() if true
     * @return {String}
     */
    var renderValue = (PhpDebugBar.Widgets.renderValue = function (value, prettify) {
        if (typeof value !== 'string') {
            if (prettify) {
                return htmlize(JSON.stringify(value, undefined, 2));
            }
            return JSON.stringify(value);
        }
        return value;
    });

    /**
     * Highlights a block of code
     *
     * @param  {String} code
     * @param  {String} lang
     * @return {String}
     */
    var highlight = (PhpDebugBar.Widgets.highlight = function (code, lang) {
        if (typeof code === 'string') {
            if (typeof hljs === 'undefined') {
                return htmlize(code);
            }
            if (lang) {
                return hljs.highlight(lang, code).value;
            }
            return hljs.highlightAuto(code).value;
        }

        if (typeof hljs === 'object') {
            code.each(function (i, e) {
                hljs.highlightBlock(e);
            });
        }
        return code;
    });

    /**
     * Creates a <pre> element with a block of code
     *
     * @param  {String} code
     * @param  {String} lang
     * @param  {Number} [firstLineNumber] If provided, shows line numbers beginning with the given value.
     * @param  {Number} [highlightedLine] If provided, the given line number will be highlighted.
     * @return {String}
     */
    var createCodeBlock = (PhpDebugBar.Widgets.createCodeBlock = function (code, lang, firstLineNumber, highlightedLine) {
        var pre = $('<pre />').addClass(csscls('code-block'));
        // Add a newline to prevent <code> element from vertically collapsing too far if the last
        // code line was empty: that creates problems with the horizontal scrollbar being
        // incorrectly positioned - most noticeable when line numbers are shown.
        var codeElement = $('<code />')
            .text(code + '\n')
            .appendTo(pre);

        // Add a span with a special class if we are supposed to highlight a line.  highlight.js will
        // still correctly format code even with existing markup in it.
        if ($.isNumeric(highlightedLine)) {
            if ($.isNumeric(firstLineNumber)) {
                highlightedLine = highlightedLine - firstLineNumber + 1;
            }
            codeElement.html(function (index, html) {
                var currentLine = 1;
                return html.replace(/^.*$/gm, function (line) {
                    if (currentLine++ == highlightedLine) {
                        return '<span class="' + csscls('highlighted-line') + '">' + line + '</span>';
                    } else {
                        return line;
                    }
                });
            });
        }

        // Format the code
        if (lang) {
            pre.addClass('language-' + lang);
        }
        highlight(pre);

        // Show line numbers in a list
        if ($.isNumeric(firstLineNumber)) {
            var lineCount = code.split('\n').length;
            var $lineNumbers = $('<ul />').prependTo(pre);
            pre.children().addClass(csscls('numbered-code'));
            for (var i = firstLineNumber; i < firstLineNumber + lineCount; i++) {
                $('<li />').text(i).appendTo($lineNumbers);
            }
        }

        return pre;
    });

    // ------------------------------------------------------------------
    // Generic widgets
    // ------------------------------------------------------------------

    /**
     * Displays array element in a <ul> list
     *
     * Options:
     *  - data
     *  - itemRenderer: a function used to render list items (optional)
     */
    var ListWidget = (PhpDebugBar.Widgets.ListWidget = PhpDebugBar.Widget.extend({
        tagName: 'ul',

        className: csscls('list'),

        initialize: function (options) {
            if (!options['itemRenderer']) {
                options['itemRenderer'] = this.itemRenderer;
            }
            this.set(options);
        },

        render: function () {
            this.bindAttr(['itemRenderer', 'data'], function () {
                this.$el.empty();
                if (!this.has('data')) {
                    return;
                }

                var data = this.get('data');
                for (var i = 0; i < data.length; i++) {
                    var li = $('<li />').addClass(csscls('list-item')).appendTo(this.$el);
                    this.get('itemRenderer')(li, data[i]);
                }
            });
        },

        /**
         * Renders the content of a <li> element
         *
         * @param {jQuery} li The <li> element as a jQuery Object
         * @param {Object} value An item from the data array
         */
        itemRenderer: function (li, value) {
            li.html(renderValue(value));
        }
    }));

    // ------------------------------------------------------------------

    /**
     * Displays object property/value paris in a <dl> list
     *
     * Options:
     *  - data
     *  - itemRenderer: a function used to render list items (optional)
     */
    var KVListWidget = (PhpDebugBar.Widgets.KVListWidget = ListWidget.extend({
        tagName: 'dl',

        className: csscls('kvlist'),

        render: function () {
            this.bindAttr(['itemRenderer', 'data'], function () {
                this.$el.empty();
                if (!this.has('data')) {
                    return;
                }

                var self = this;
                $.each(this.get('data'), function (key, value) {
                    var dt = $('<dt />').addClass(csscls('key')).appendTo(self.$el);
                    var dd = $('<dd />').addClass(csscls('value')).appendTo(self.$el);
                    self.get('itemRenderer')(dt, dd, key, value);
                });
            });
        },

        /**
         * Renders the content of the <dt> and <dd> elements
         *
         * @param {jQuery} dt The <dt> element as a jQuery Object
         * @param {jQuery} dd The <dd> element as a jQuery Object
         * @param {String} key Property name
         * @param {Object} value Property value
         */
        itemRenderer: function (dt, dd, key, value) {
            dt.text(key);
            dd.html(htmlize(value));
        }
    }));

    // ------------------------------------------------------------------

    /**
     * An extension of KVListWidget where the data represents a list
     * of variables
     *
     * Options:
     *  - data
     */
    var VariableListWidget = (PhpDebugBar.Widgets.VariableListWidget = KVListWidget.extend({
        className: csscls('kvlist varlist'),

        itemRenderer: function (dt, dd, key, value) {
            $('<span />').attr('title', key).text(key).appendTo(dt);

            var v = value;
            if (v && v.length > 100) {
                v = v.substr(0, 100) + '...';
            }
            var prettyVal = null;
            dd.text(v).click(function () {
                if (dd.hasClass(csscls('pretty'))) {
                    dd.text(v).removeClass(csscls('pretty'));
                } else {
                    prettyVal = prettyVal || createCodeBlock(value);
                    dd.addClass(csscls('pretty')).empty().append(prettyVal);
                }
            });
        }
    }));

    // ------------------------------------------------------------------

    /**
     * An extension of KVListWidget where the data represents a list
     * of variables whose contents are HTML; this is useful for showing
     * variable output from VarDumper's HtmlDumper.
     *
     * Options:
     *  - data
     */
    var HtmlVariableListWidget = (PhpDebugBar.Widgets.HtmlVariableListWidget = KVListWidget.extend({
        className: csscls('kvlist htmlvarlist'),

        itemRenderer: function (dt, dd, key, value) {
            $('<span />').attr('title', key).text(key).appendTo(dt);
            dd.html(value);
        }
    }));

    // ------------------------------------------------------------------

    /**
     * Iframe widget
     *
     * Options:
     *  - data
     */
    var IFrameWidget = (PhpDebugBar.Widgets.IFrameWidget = PhpDebugBar.Widget.extend({
        tagName: 'iframe',

        className: csscls('iframe'),

        render: function () {
            this.$el.attr({
                seamless: 'seamless',
                border: '0',
                width: '100%',
                height: '100%'
            });
            this.bindAttr('data', function (url) {
                this.$el.attr('src', url);
            });
        }
    }));

    // ------------------------------------------------------------------
    // Collector specific widgets
    // ------------------------------------------------------------------

    /**
     * Widget for the MessagesCollector
     *
     * Uses ListWidget under the hood
     *
     * Options:
     *  - data
     */
    var MessagesWidget = (PhpDebugBar.Widgets.MessagesWidget = PhpDebugBar.Widget.extend({
        className: csscls('messages'),

        render: function () {
            var self = this;

            this.$list = new ListWidget({
                itemRenderer: function (li, value) {
                    if (value.message_html) {
                        var val = $('<span />').addClass(csscls('value')).html(value.message_html).appendTo(li);
                    } else {
                        var m = value.message;
                        if (m.length > 100) {
                            m = m.substr(0, 100) + '...';
                        }

                        var val = $('<span />').addClass(csscls('value')).text(m).appendTo(li);
                        if (!value.is_string || value.message.length > 100) {
                            var prettyVal = value.message;
                            if (!value.is_string) {
                                prettyVal = null;
                            }
                            li.css('cursor', 'pointer').click(function () {
                                if (val.hasClass(csscls('pretty'))) {
                                    val.text(m).removeClass(csscls('pretty'));
                                } else {
                                    prettyVal = prettyVal || createCodeBlock(value.message, 'php');
                                    val.addClass(csscls('pretty')).empty().append(prettyVal);
                                }
                            });
                        }
                    }

                    if (value.collector) {
                        $('<span />').addClass(csscls('collector')).text(value.collector).prependTo(li);
                    }
                    if (value.label) {
                        val.addClass(csscls(value.label));
                        $('<span />').addClass(csscls('label')).text(value.label).prependTo(li);
                    }
                }
            });

            this.$list.$el.appendTo(this.$el);
            this.$toolbar = $('<div><i class="phpdebugbar-fa phpdebugbar-fa-search"></i></div>')
                .addClass(csscls('toolbar'))
                .appendTo(this.$el);

            $('<input type="text" />')
                .on('change', function () {
                    self.set('search', this.value);
                })
                .appendTo(this.$toolbar);

            this.bindAttr('data', function (data) {
                this.set({ exclude: [], search: '' });
                this.$toolbar.find(csscls('.filter')).remove();

                var filters = [],
                    self = this;
                for (var i = 0; i < data.length; i++) {
                    if (!data[i].label || $.inArray(data[i].label, filters) > -1) {
                        continue;
                    }
                    filters.push(data[i].label);
                    $('<a />')
                        .addClass(csscls('filter'))
                        .text(data[i].label)
                        .attr('rel', data[i].label)
                        .on('click', function () {
                            self.onFilterClick(this);
                        })
                        .appendTo(this.$toolbar);
                }
            });

            this.bindAttr(['exclude', 'search'], function () {
                var data = this.get('data'),
                    exclude = this.get('exclude'),
                    search = this.get('search'),
                    caseless = false,
                    fdata = [];

                if (search && search === search.toLowerCase()) {
                    caseless = true;
                }

                for (var i = 0; i < data.length; i++) {
                    var message = caseless ? data[i].message.toLowerCase() : data[i].message;

                    if (
                        (!data[i].label || $.inArray(data[i].label, exclude) === -1) &&
                        (!search || message.indexOf(search) > -1)
                    ) {
                        fdata.push(data[i]);
                    }
                }

                this.$list.set('data', fdata);
            });
        },

        onFilterClick: function (el) {
            $(el).toggleClass(csscls('excluded'));

            var excludedLabels = [];
            this.$toolbar.find(csscls('.filter') + csscls('.excluded')).each(function () {
                excludedLabels.push(this.rel);
            });

            this.set('exclude', excludedLabels);
        }
    }));

    // ------------------------------------------------------------------

    /**
     * Widget for the TimeDataCollector
     *
     * Options:
     *  - data
     */
    var TimelineWidget = (PhpDebugBar.Widgets.TimelineWidget = PhpDebugBar.Widget.extend({
        tagName: 'ul',

        className: csscls('timeline'),

        render: function () {
            this.bindAttr('data', function (data) {
                // ported from php DataFormatter
                var formatDuration = function (seconds) {
                    if (seconds < 0.001) return (seconds * 1000000).toFixed() + 'μs';
                    else if (seconds < 1) return (seconds * 1000).toFixed(2) + 'ms';
                    return seconds.toFixed(2) + 's';
                };

                this.$el.empty();
                if (data.measures) {
                    var aggregate = {};

                    for (var i = 0; i < data.measures.length; i++) {
                        var measure = data.measures[i];

                        if (!aggregate[measure.label]) aggregate[measure.label] = { count: 0, duration: 0 };

                        aggregate[measure.label]['count'] += 1;
                        aggregate[measure.label]['duration'] += measure.duration;

                        var m = $('<div />').addClass(csscls('measure')),
                            li = $('<li />'),
                            left = ((measure.relative_start * 100) / data.duration).toFixed(2),
                            width = Math.min(((measure.duration * 100) / data.duration).toFixed(2), 100 - left);

                        m.append(
                            $('<span />')
                                .addClass(csscls('value'))
                                .css({
                                    left: left + '%',
                                    width: width + '%'
                                })
                        );
                        m.append(
                            $('<span />')
                                .addClass(csscls('label'))
                                .text(measure.label + ' (' + measure.duration_str + ')')
                        );

                        if (measure.collector) {
                            $('<span />').addClass(csscls('collector')).text(measure.collector).appendTo(m);
                        }

                        m.appendTo(li);
                        this.$el.append(li);

                        if (measure.params && !$.isEmptyObject(measure.params)) {
                            var table = $('<table><tr><th colspan="2">Params</th></tr></table>')
                                .addClass(csscls('params'))
                                .appendTo(li);
                            for (var key in measure.params) {
                                if (typeof measure.params[key] !== 'function') {
                                    table.append(
                                        '<tr><td class="' +
                                            csscls('name') +
                                            '">' +
                                            key +
                                            '</td><td class="' +
                                            csscls('value') +
                                            '"><pre><code>' +
                                            measure.params[key] +
                                            '</code></pre></td></tr>'
                                    );
                                }
                            }
                            li.css('cursor', 'pointer').click(function () {
                                var table = $(this).find('table');
                                if (table.is(':visible')) {
                                    table.hide();
                                } else {
                                    table.show();
                                }
                            });
                        }
                    }

                    // convert to array and sort by duration
                    aggregate = $.map(aggregate, function (data, label) {
                        return {
                            label: label,
                            data: data
                        };
                    }).sort(function (a, b) {
                        return b.data.duration - a.data.duration;
                    });

                    // build table and add
                    var aggregateTable = $('<table style="display: table; border: 0; width: 99%"></table>').addClass(
                        csscls('params')
                    );
                    $.each(aggregate, function (i, aggregate) {
                        width = Math.min(((aggregate.data.duration * 100) / data.duration).toFixed(2), 100);

                        aggregateTable.append(
                            '<tr><td class="' +
                                csscls('name') +
                                '">' +
                                aggregate.data.count +
                                ' x ' +
                                aggregate.label +
                                ' (' +
                                width +
                                '%)</td><td class="' +
                                csscls('value') +
                                '">' +
                                '<div class="' +
                                csscls('measure') +
                                '">' +
                                '<span class="' +
                                csscls('value') +
                                '" style="width:' +
                                width +
                                '%"></span>' +
                                '<span class="' +
                                csscls('label') +
                                '">' +
                                formatDuration(aggregate.data.duration) +
                                '</span>' +
                                '</div></td></tr>'
                        );
                    });

                    this.$el.append('<li/>').find('li:last').append(aggregateTable);
                }
            });
        }
    }));

    // ------------------------------------------------------------------

    /**
     * Widget for the displaying exceptions
     *
     * Options:
     *  - data
     */
    var ExceptionsWidget = (PhpDebugBar.Widgets.ExceptionsWidget = PhpDebugBar.Widget.extend({
        className: csscls('exceptions'),

        render: function () {
            this.$list = new ListWidget({
                itemRenderer: function (li, e) {
                    $('<span />').addClass(csscls('message')).text(e.message).appendTo(li);
                    if (e.file) {
                        var header = $('<span />')
                            .addClass(csscls('filename'))
                            .text(e.file + '#' + e.line);
                        if (e.xdebug_link) {
                            if (e.xdebug_link.ajax) {
                                $('<a title="' + e.xdebug_link.url + '"></a>')
                                    .on('click', function () {
                                        $.ajax(e.xdebug_link.url);
                                    })
                                    .addClass(csscls('editor-link'))
                                    .appendTo(header);
                            } else {
                                $('<a href="' + e.xdebug_link.url + '"></a>')
                                    .addClass(csscls('editor-link'))
                                    .appendTo(header);
                            }
                        }
                        header.appendTo(li);
                    }
                    if (e.type) {
                        $('<span />').addClass(csscls('type')).text(e.type).appendTo(li);
                    }
                    if (e.surrounding_lines) {
                        var pre = createCodeBlock(e.surrounding_lines.join(''), 'php').addClass(csscls('file')).appendTo(li);
                        li.click(function () {
                            if (pre.is(':visible')) {
                                pre.hide();
                            } else {
                                pre.show();
                            }
                        });
                    }
                    if (e.stack_trace) {
                        e.stack_trace.split('\n').forEach(function (trace) {
                            var $traceLine = $('<div />');
                            $('<span />').addClass(csscls('filename')).text(trace).appendTo($traceLine);
                            $traceLine.appendTo(li);
                        });
                    }
                }
            });
            this.$list.$el.appendTo(this.$el);

            this.bindAttr('data', function (data) {
                this.$list.set('data', data);
                if (data.length == 1) {
                    this.$list.$el.children().first().find(csscls('.file')).show();
                }
            });
        }
    }));
    var DebugLogsWidget = (PhpDebugBar.Widgets.DebugLogsWidget = PhpDebugBar.Widget.extend({
        className: csscls('messages'),
        render: function () {
            var self = this;

            this.$list = new ListWidget({
                itemRenderer: function (li, value) {
                    var m = value.message;
                    var val = $('<span />').addClass(csscls('value')).text(m).appendTo(li);
                    var prettyVal = value.message;
                    if (!value.is_string) {
                        prettyVal = null;
                    }
                    li.css('cursor', 'pointer').click(function () {
                        if (val.hasClass(csscls('pretty'))) {
                            val.text(m).removeClass(csscls('pretty'));
                        } else {
                            prettyVal = prettyVal || createCodeBlock(value.trace, 'php');
                            val.addClass(csscls('pretty')).append(prettyVal);
                        }
                    });
                    if (value.label) {
                        val.addClass(csscls(value.label));
                        $('<span />').addClass(csscls('label')).text(value.label).appendTo(li);
                    }
                    if (value.collector) {
                        $('<span />').addClass(csscls('collector')).text(value.collector).appendTo(li);
                    }
                }
            });

            this.$list.$el.appendTo(this.$el);
            this.$toolbar = $('<div><i class="phpdebugbar-fa phpdebugbar-fa-search"></i></div>')
                .addClass(csscls('toolbar'))
                .appendTo(this.$el);
            $('<input type="text" />')
                .on('change', function () {
                    self.set('search', this.value);
                })
                .css('border', 'solid 1px #000')
                .appendTo(this.$toolbar);

            this.bindAttr('data', function (data) {
                this.set({ exclude: [], search: '' });
                this.$toolbar.find(csscls('.filter')).remove();

                var filters = [],
                    self = this;
                for (var i = 0; i < data.length; i++) {
                    if (!data[i].label || $.inArray(data[i].label, filters) > -1) {
                        continue;
                    }
                    filters.push(data[i].label);
                    $('<a />')
                        .addClass(csscls('filter'))
                        .text(data[i].label)
                        .attr('rel', data[i].label)
                        .on('click', function () {
                            self.onFilterClick(this);
                        })
                        .appendTo(this.$toolbar);
                }
            });

            this.bindAttr(['exclude', 'search'], function () {
                var data = this.get('data'),
                    exclude = this.get('exclude'),
                    search = this.get('search'),
                    caseless = false,
                    fdata = [];

                if (search && search === search.toLowerCase()) {
                    caseless = true;
                }

                for (var i = 0; i < data.length; i++) {
                    var message = caseless ? data[i].message.toLowerCase() : data[i].message;

                    if (
                        (!data[i].label || $.inArray(data[i].label, exclude) === -1) &&
                        (!search || message.indexOf(search) > -1)
                    ) {
                        fdata.push(data[i]);
                    }
                }

                this.$list.set('data', fdata);
            });
        },
        onFilterClick: function (el) {
            $(el).toggleClass(csscls('excluded'));

            var excludedLabels = [];
            this.$toolbar.find(csscls('.filter') + csscls('.excluded')).each(function () {
                excludedLabels.push(this.rel);
            });

            this.set('exclude', excludedLabels);
        }
    }));
})(PhpDebugBar.$);