Além do Console.log, Episódio III: Formatadores personalizados em Devtools

Este artigo faz parte de uma série de artigos sobre o Console do Chrome;
Anteriormente em “Além do console.log”
Você está cansado de ver seus objetos sempre sendo logados da mesma forma no console? Você gosta do Chrome Dev Tools, mas gostaria do nome das instâncias de Eleve é exibido em vermelho, laranja ou verde dependendo do seu valor? Fique nesta página, você não precisa ir mais longe.

Pré-requisito: habilitar Formatadores Personalizados

No DevTools, vá para configurações e marque a caixa “habilitar formatadores personalizados” na seção “Console”.

O que são Formatadores Personalizados?

Os Formatadores personalizados são uma matriz de objetos armazenados em object window. Eles nos permitem personalizar a exibição de um determinado objeto como desejarmos. Assim, eles podem melhorar muito a legibilidade de nossos logs. Ou melhor : fazer sangrar os olhos de nossos colegas (veja abaixo).
captura de tela de quando log() um objeto com os formatadores personalizados: aqui, as letras aparecem em chamas!
Apesar de sua utilidade, não me parece que este seja um tema muito conhecido, há um número muito pequeno de artigos sobre o assunto na rede, todos em inglês. Sem mais delongas, vamos consertar essa injustiça sujando as mãos.

o objetivo

  • Primeiro, garantiremos que as instâncias da nossa futura classe Student exibam seu nome no formato firstname + NAME, e esta deverá ser colorida de vermelho a verde de acordo com a média de suas notas.
  • Então vamos ter certeza de ter um código de fácil manutenção

Vamos codificar

Primeiro, vamos codificar a classe Student:

class Student {
    constructor(firstname, lastname, notations) {
        this.firstname = firstname;
        this.lastname = lastname;
        this.notations = notations;
    }
    getAverageNotation() {
        return this.notations.reduce((total, notation) => total += notation, 0) / this.notations.length;
    }
}

… Então vamos declarar nosso formatador e adicioná-lo ao array:

const studentFormatter = {
    header: (x) => {
        if (x instanceof Student) {
            const moyenne = x.getAverageNotation();
            const color = (moyenne > 15) ? 'limegreen' : (moyenne > 5) ? 'orangered' : 'firebrick';
            return [
                'span',
                {
                    style: 'color: ' + color
                },
                x.lastname.toUpperCase() + ' ' + x.firstname
            ];
        }
    },
    hasBody: x => x instanceof Student,
    body: (x) => {
        return [
            'table',
            {},
            ['tr', {},
                ['td', {style: 'color: burlywood'}, 'Prénom: '],
                ['td', {}, `${x.firstname}`],
            ],
            ['tr', {},
                ['td', {style: 'color: burlywood'}, 'Nom: '],
                ['td', {}, `${x.lastname}`],
            ],
            ['tr', {},
                ['td', {style: 'color: burlywood'}, 'Notes: '],
                ['object', {object: x.notes}],
            ],
        ];
    }
};
window.devtoolsFormatters = [ studentFormatter ];

Agora podemos registrar uma matriz de alunos:

const ducobu = new Student('Élève', 'Ducobu', [2, 5, 3]);
const willHunting = new Student('William', 'Hunt', [18, 20, 19]);
const nicolasLePetit = new Student('Nicolas', 'Le Petit', [10, 10, 10]);
console.info([ducobu, willHunting, nicolasLePetit]);

captura de tela de devtools ao registrar um objeto com formatadores personalizados

Análise breve de código

musgo animado
Nossa, é mágico, os nomes dos alunos aparecem em maiúsculas e é colorido de acordo com a média! Como eu poderia viver sem até agora? Minha vida como programador mudou para sempre!
Cada objeto na matriz de formatadores personalizados deve ter três métodos: cabeçalho, que deve renderizar um objeto JSONML que será usado para exibir o nome do objeto quando ele for recolhido, temCorpo, que indica se o objeto é desdobrável e finalmente corpo que deve renderizar o template HTML do corpo desdobrado do objeto, no formato JSONML. No entanto, os únicos elementos HTML permitidos são mesa, tr, ts, ol, li, div et palmo
Aqui eu verifico se x é uma instância de Student com instanceof. Observe que isso retornará true para qualquer subclasse de Student. Se quisermos habilitar o formatador apenas se o objeto for exatamente uma instância de Student, poderíamos ter escrito:
if (x.constructor === Student)

Ir além

Tudo o que realmente não é ruim. Mas em um projeto grande, não vejo pessoas adicionando a classe que acabaram de criar ao array window.devtoolsFormatters manualmente. Seria melhor se o CustomFormatter do aluno fosse definido na própria classe. Mas isso traria uma desvantagem: poluiria nossos alunos, poderia causar colisões. É com esse tipo de problema em mente que vamos aproveitar os Símbolos:

const CustomFormatterToken = Symbol('CustomFormatter');
// en ajoutant cette propriété à Symbol, ça permet à notre
// fonctionnalité d'être accessible partout :
Symbol.customFormatter = CustomFormatterToken;
// Voici la nouvelle version de la classe Eleve :
class Student {
    constructor(firstname, lastname, notations) {
        this.firstname = firstname;
        this.lastname = lastname;
        this.notations = notations;
    }
    get [CustomFormatterToken]() {
        return {
            header: () => {
                const averageNotation = this.getAverageNotation();
                const color = (moyenne > 15) ? 'limegreen' : (moyenne > 5) ? 'orangered' : 'firebrick';
                return [
                    'span',
                    {
                        style: 'color: '+ color + '}'
                    },
                    this.lastname.toUpperCase() + 'this.firstname'
                ];
            },
            hasBody: () => true,
            body: () => [
                'table',
                {},
                ['tr', {},
                    ['td', {style: 'color: burlywood'}, 'prenom: '],
                    ['td', {}, `${this.firstname}`],
                ],
                ['tr', {},
                    ['td', {style: 'color: burlywood'}, 'nom: '],
                    ['td', {}, `${this.lastname}`],
                ],
                ['tr', {},
                    ['td', {style: 'color: burlywood'}, 'notes: '],
                    ['object', {object: this.notations}],
                ],
            ]
        };
    }
    getAverageNotation() {
        return this.notations.reduce((total, notation) => total += notation, 0) / this.notations.length;
    }
}
// Dorénavant, devtoolsFormatters n'aura qu'une seule valeur : AllPurposeFormatter
const AllPurposeFormatter = {
    header: x => {
        const formatter = x[Symbol.customFormatter];
        if (formatter) {
            return formatter.header();
        }
    },
    hasBody: x => {
        const formatter = x[Symbol.customFormatter];
        if (formatter) {
            return formatter.hasBody();
        }
    },
    body: x => {
        const formatter = x[Symbol.customFormatter];
        if (formatter) {
            return formatter.body();
        }
    },
};
window.devtoolsFormatters = [ AllPurposeFormatter ];
const ducobu = new Student('Élève', 'Ducobu', [2, 5, 3]);
const willHunting = new Student('William', 'Hunt', [18, 20, 19]);
const nicolasLePetit = new Student('Nicolas', 'Le Petit', [10, 10, 10]);
console.info([ducobu, willHunting, nicolasLePetit]);
// les propriétés qui ont pour clé un symbole n'apparaissent pas...
// Notre objet n'est pas pollué \o/
console.log(Object.keys(ducobu)); // => ["firstname", "lastname", "notations"]

treinadores
Bem, isso é tudo por hoje. Até breve para novas aventuras com Chromes Dev Tools!