FastReport Online Designer permite adicionar seus próprios componentes de relatório — com o seu ícone na paleta, propriedades no painel Properties e a sua caixa de diálogo de configurações. Nada de mágica: você escreve um plugin em JavaScript, faz o upload dele para o builder e ele é compilado diretamente na build do designer.
Neste artigo criaremos um componente chamado «Avaliação» do zero, que exibe um valor em forma de estrelas, e percorreremos todo o caminho: do download do SDK até uma build pronta do designer com o plugin e a parte do servidor.
O componente tem duas metades:
.frx.O que as liga é o nome do tipo: o nome curto da classe .NET, o campo type no plugin e a tag XML no .frx devem ser uma e a mesma string.
As duas metades podem ser feitas uma após a outra. A classe .NET não é necessária para o designer, por isso primeiro construiremos e testaremos o plugin, e a parte do servidor será adicionada no final.
No builder, abra Build → Componentes personalizados e clique em Baixar SDK. O ZIP contém README.md com uma breve introdução e examples/DemoChartObject — um exemplo funcional.
A pasta do plugin também serve como pasta de trabalho para a sua IDE. Copie examples/DemoChartObject e renomeie a cópia para RatingObject: nela já está tudo pronto para o autocompletar — sdk.d.ts com os tipos do SDK, jsconfig.json e package.json com os tipos do React correspondentes à versão do React do designer. Execute npm install nesta pasta e abra-a na sua IDE.
Um plugin — uma pasta com o arquivo index.jsx (ou index.js). Substitua o conteúdo de RatingObject/index.jsx pela descrição do componente:
import React from 'react';
import {registerComponent, tr} from 'sdk';
import {Input, ColorInput, Button, FormField} from 'sdk/controls';
registerComponent({
apiVersion: 1,
type: 'RatingObject',
core: 'net',
palette: {
title: 'Rating',
info: 'Star rating',
icon: 'icon-chart',
},
defaultValues: {
'Value': 3,
'Maximum': 5,
'StarColor': '#f5a623',
},
size: {width: 160, height: 32},
properties: [
{prop: 'Data:Value', type: 'number', label: 'Value', attrs: {min: 0}},
{prop: 'Data:Maximum', type: 'number', label: 'Maximum', attrs: {min: 1}},
{prop: 'Appearance:StarColor', type: 'color', label: 'Star color'},
],
});
Isso já é suficiente para o componente aparecer na paleta e receber suas propriedades:
palette define o cartão na paleta. O ícone aqui é o padrão, mas você também pode indicar o seu próprio SVG ou PNG como um data:-URI.defaultValues armazena os valores padrão. Somente os valores diferentes deles vão para o .frx — funciona da mesma forma que o FastReport .NET.properties descreve os campos do painel de propriedades no formato Categoria:Nome. Os campos Design:Name and Layout:Left/Top/Width/Height são adicionados pelo próprio SDK.Não é preciso salvar e ler .frx manualmente: a implementação herdada já sabe trabalhar com todas as propriedades declaradas.
Para que o componente pareça uma avaliação na página, adicione o draw à descrição. A moldura, a movimentação e o redimensionamento são fornecidos ao componente pela classe base Component, portanto draw é responsável apenas pelo conteúdo. O desenho deve ser feito no contêiner this.$content: no momento da chamada, ele já foi criado.
draw: function () {
if (!this.$stars) {
this.$stars = document.createElementNS('http://www.w3.org/2000/svg', 'g');
this.$content.append(this.$stars);
}
const maximum = Number(this.prop('Maximum')) || 1;
const value = Number(this.prop('Value')) || 0;
const color = String(this.prop('StarColor'));
const height = this.prop('Height');
const step = this.prop('Width') / maximum;
const stars = [];
for (let i = 0; i < maximum; i++) {
const fill = i < value ? color : '#d9d9d9';
stars.push(`<text x="${step * (i + 0.5)}" y="${height * 0.75}" text-anchor="middle" font-size="${height * 0.8}" fill="${fill}">★</text>`);
}
this.$stars.innerHTML = stars.join('');
},
A caixa de diálogo de configurações abre com um duplo clique no componente. Vamos montá-la com os controles padrão de sdk/controls, para que fique com a aparência dos editores integrados:
function RatingDialog ({component, popup}) {
const ok = () => {
component.model.submit();
popup.submitAndClose();
component.render();
};
const cancel = () => {
component.model.clear();
popup.close();
};
return (
<div>
<FormField label={`${tr('Value')}:`}>
<Input half type="number" min={0} model={component.model} prop="Value"/>
</FormField>
<FormField label={`${tr('Maximum')}:`}>
<Input half type="number" min={1} model={component.model} prop="Maximum"/>
</FormField>
<FormField label={`${tr('Star color')}:`}>
<ColorInput half model={component.model} prop="StarColor"/>
</FormField>
<div style={{textAlign: 'right'}}>
<Button variant="primary" text="OK" minWidth={60} style={{marginRight: 6}} onClick={ok}/>
<Button minWidth={60} text={tr('Cancel')} onClick={cancel}/>
</div>
</div>
);
}
Os controles gravam as alterações em component.model. Isso é um rascunho: as propriedades reais do componente só são alteradas após submit(), portanto o botão Cancelar não estraga nada.
Conecte a caixa de diálogo na descrição do componente:
editor: (component, popup) => <RatingDialog component={component} popup={popup}/>,
O plugin deve conter suas próprias traduções. É conveniente usar o texto em inglês como chave: se não houver tradução, o designer mostrará a própria chave.
const TRANSLATIONS = {
en: {'Rating': 'Rating', 'Star rating': 'Star rating', 'Value': 'Value', 'Maximum': 'Maximum', 'Star color': 'Star color', 'Cancel': 'Cancel'},
'pt-BR': {'Rating': 'Avaliação', 'Star rating': 'Avaliação em estrelas', 'Value': 'Valor', 'Maximum': 'Máximo', 'Star color': 'Cor das estrelas', 'Cancel': 'Cancelar'},
};
Adicione a linha translations: TRANSLATIONS à descrição do componente. As legendas na paleta e no painel de propriedades o designer traduzirá por conta própria; em seu próprio código use tr('Value').
RatingObject. Não é preciso excluir node_modules antes de empacotar — o builder levará em conta apenas os pacotes de dependencies, enquanto devDependencies (incluindo os tipos do React) não entrarão na build.Abra o designer: Avaliação apareceu na paleta. Adicione-o à página, mude o valor no painel de propriedades e abra a caixa de diálogo de configurações com um duplo clique.
Se você constrói o designer via Builder API, faça o upload do ZIP com uma requisição POST /builderAPI/extensions e liste os plugins na requisição de build: "extensions": ["RatingObject"].
Para que o servidor possa construir um relatório com o novo objeto, é necessária uma classe .NET com o mesmo nome em uma assembly separada:
public class RatingObject : ReportComponentBase
{
// Draw, Serialize, Deserialize, Assign
}
public class RatingAssemblyInitializer : AssemblyInitializerBase
{
public RatingAssemblyInitializer()
{
RegisteredObjects.Add(typeof(RatingObject), "ReportPage", myBitmap, "Rating");
}
}
A assembly é conectada pela seção <Plugins> em FastReport.config ou por uma chamada direta na inicialização do aplicativo. Um exemplo pronto de tal plugin — Extras/Misc/TeeChartObjectPlugin no FastReport .NET.
Enquanto a classe não estiver implantada, o servidor não conseguirá ler o relatório com esse objeto e lançará ClassException — inclusive ao gerar a visualização. A build do designer sem o plugin, no entanto, abrirá o relatório e salvará o objeto desconhecido com sua tag, seus atributos e os elementos XML aninhados.
O plugin sabe fazer mais do que o mostrado no artigo: herdar o comportamento dos componentes padrão via extends, conectar editores de propriedades próprios via registerPropertyEditor e usar bibliotecas de terceiros — elas são instaladas via npm install <pacote> e empacotadas junto com node_modules. Tudo isso está descrito no capítulo «Componentes personalizados» do manual do usuário do Online Designer, e o exemplo DemoChartObject mostra essas técnicas em código funcional.
Sim, para trabalhar no designer basta o plugin JS. Mas, sem a classe no servidor, não será possível construir um relatório com esse componente: o servidor lançará ClassException.
Indique em palette.icon o seu próprio SVG ou PNG como um data:-URI. Sem ele, o SDK usará o ícone padrão.
Na raiz do ZIP deve haver uma única pasta de plugin, e nela — o arquivo index.jsx ou index.js. O builder mostrará a lista de problemas já no upload.
Um componente personalizado para o FastReport Online Designer — duas metades amarradas pelo mesmo nome de tipo: um plugin JS para o editor e uma classe .NET para o servidor. Para montar tudo hoje, baixe o SDK, copie o exemplo DemoChartObject e substitua suas próprias propriedades, visualização e caixa de diálogo de configurações — o designer será reconstruído junto com o plugin. Conecte a parte do servidor em paralelo: mesmo antes da implantação da classe, um relatório com o novo componente pode ser aberto e salvo sem perdas.