Beginner Tutorial - Dataset

Introduction

As we know, FineReport supports Class Dataset, which helps us define a customized dataset by some Java programming. If you are not familiar with Class Dataset, please check out our demo here: https://help.fanruan.com/finereport-en/doc-view-650.html. However, this functionality is limited because it does not support the front-end configuration. This tutorial is here to solve the problem, and it will teach you how to implement a plugin that defines your own dataset.

The first class we need to know is com.fr.data.AbstractTableData. It provides four important methods for us:

public abstract int getColumnCount() throws TableDataException;
public abstract String getColumnName( int colIndex ) throws TableDataException;
public abstract int getRowCount() throws TableDataException;
public abstract Object getValueAt( int rowIndex, int colIndex );

As the names of the methods suggest, FineReport uses these methods to create a table and fill in data. The Class Dataset simply extends this class and implements the four methods.

While for our dataset plugin, the class for us to extend is not com.fr.data.AbstractTableData, but com.fr.data.AbstractParameterTableData. The key method is this:

public DataModel createDataModel(Calculator calculator);

The DataModel is an interface, and it is implemented by com.fr.data.AbstractDataModel. If we check out AbstractDataModel, we will find that it has the same four methods as those of the AbstractTableData. This data model thing is just a wrapper for our data.

In most cases, we do not directly implement an interface in our plugin development. An interface X usually has an abstract class AbstractX, and we should extend that AbstractX.

For now, we have explained how FineReport gets our data, but we still need to grab data from the outside (e.g. user input). So how does FineReport grab data from our input? This relies on a very important thing called Calculator (com.fr.script.calculator). Almost every information we need during calculation can get from this object. We don't need to 100% understand how it works. Just keep in mind that it is the core of the whole calculation process.

Example

After reading the tedious definition, let's start with a simple example. This will teach you how to build a dataset plugin step by step. Join me if you are ready!

1) Create a new module

Create a new module called plugin-tabledata-demo. If you don't remember how, please check the last tutorial: First Plugin - Hello World!

create_module

2) Define how we get data from the outside

public class DemoTableData extends AbstractParameterTableData {
    public final static String TAG = "others";

    @Override
    public DataModel createDataModel(Calculator calculator) {
        // Get all parameters on the pane.
        ParameterProvider[] parameters = this.getParameters(calculator);
        // Sometimes the parameters need us to recalculate once, or you will get the default value.
        parameters = Calculator.processParameters(calculator,parameters);
        return DemoDataModel.create(parameters, getOthers());
    }

    // Suppose we have some configuration other than parameters
    @Identifier(TAG)
    private Conf<String> others = Holders.simple(StringUtils.EMPTY);

    // Database read and write for server dataset.
    public String getOthers(){
        return others.get();
    }

    public void setOthers(String others){
        this.others.set(others);
    }

    // XML read and write for template dataset.
    @Override
    public void readXML(XMLableReader reader) {
        super.readXML(reader);
        if (reader.isChildNode()) {
            if ("Attributes".equals(reader.getTagName())) {
                setOthers(reader.getAttrAsString(TAG,StringUtils.EMPTY));
            }
        }
    }

    @Override
    public void writeXML(XMLPrintWriter writer) {
        super.writeXML(writer);
        writer.startTAG("Attributes").attr(TAG,getOthers()).end();
    }
}

3) Define the DataModel

The DataModel tells FineReport how to get the provided data.

public class DemoDataModel extends AbstractDataModel {
    public final static DemoDataModel EMPTY = new DemoDataModel();

    // For demo use, just give the fixed column names here.
    private final static String [] COL_NAMES = new String[]{"KEY","VALUE","OTHERS"};

    private ParameterProvider[] parameters = new ParameterProvider[0];

    private String others = StringUtils.EMPTY;


    public DemoDataModel(){}

    public static DemoDataModel create(ParameterProvider[] parameters, String others){
        DemoDataModel result = new DemoDataModel();
        result.parameters = null == parameters ? new ParameterProvider[0] : parameters;
        result.others= null == others ? StringUtils.EMPTY : others;
        return result;
    }

    @Override
    public int getColumnCount() throws TableDataException {
        return COL_NAMES.length;
    }

    @Override
    public String getColumnName(int colIndex) throws TableDataException {
        return COL_NAMES[colIndex];
    }

    @Override
    public int getRowCount() throws TableDataException {
        return parameters.length;
    }

    @Override
    public Object getValueAt(int rowIndex, int colIndex) throws TableDataException {
        if( 2 == colIndex ){
            return others;
        }
        ParameterProvider p = parameters[rowIndex];
        return 0 == colIndex ? p.getName() : p.getValue() ;
    }
}

4) Define the UI

After creating DemoTableData and DemoDataModel, the next thing we need is to define the UI for a user to configure our dataset. Otherwise, FineReport has no way to know what the parameters and the "others" are. The class for this is com.fr.design.data.tabledata.tabledatapane.AbstractTableDataPane. It has three important methods:

// To populate data on the pane
public abstract void populateBean(T var1);
// To update data
public abstract T updateBean();
// Ttile for the window
protected abstract String title4PopupWindow();

Here, T is our DemoTableData. The UI class can be defined like this:

public class DemoTableDataPane extends AbstractTableDataPane<DemoTableData> {

    private final static double P = TableLayout.PREFERRED;
    private final static double F = TableLayout.FILL;

    private UITableEditorPane<ParameterProvider> parameterTableEditorPane;
    private UITextField tEditor;

    public DemoTableDataPane() {
        init();
    }

    private void init() {
        // Create a pane for entering parameters.
        UITableModelAdapter<ParameterProvider> model = new ParameterTableModel();
        parameterTableEditorPane = new UITableEditorPane<ParameterProvider>(model);

        // Create the text field for 'others'
        tEditor = new UITextField();

        // Create the 'preview' button
        UIButton preview = new UIButton(BaseUtils.readIcon("/com/fr/design/images/m_file/preview.png"));
        preview.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                SwingUtilities.invokeLater(new Runnable() {
                    @Override
                    public void run() {
                        PreviewTablePane.previewTableData(DemoTableDataPane.this.updateBean());
                    }
                });
            }
        });

        // Table layout
        JPanel topPane = TableLayoutHelper.createTableLayoutPane(
                new Component[][] {{
                    tEditor, preview
                }},
                new double[] {P},
                new double[] {F, P}
        );

        this.add(TableLayoutHelper.createTableLayoutPane(
                new Component[][] {{
                    topPane
                }, {
                    parameterTableEditorPane
                }},
                new double[] {P, F},
                new double[] {F}
        ));
    }

    @Override
    public void populateBean(DemoTableData demoTableData) {
        if (null == demoTableData) {
            return;
        }
        String others = demoTableData.getOthers();
        ParameterProvider[] parameters = demoTableData.getParameters(Calculator.createCalculator());
        tEditor.setText(others);
        parameterTableEditorPane.populate(parameters);
    }

    @Override
    public DemoTableData updateBean() {
        DemoTableData demoTableData = new DemoTableData();
        demoTableData.setOthers(tEditor.getText());
        List<ParameterProvider> parameterProviderList = parameterTableEditorPane.update();
        ParameterProvider[] parameters = parameterProviderList.toArray(new ParameterProvider[0]);
        demoTableData.setParameters(parameters);
        return demoTableData;
    }

    @Override
    protected String title4PopupWindow() {
        return "Demo";
    }
}

6) Register the plugin

Finally, write the plugin.xml for our project. The configuration should be like this:

<extra-designer>
    <TableDataDefineProvider class="com.fr.plugin.tabledata.demo.DemoTableDataBridge"/>
    <ServerTableDataDefineProvider class="com.fr.plugin.tabledata.demo.DemoServerTableDataBridge"/>
</extra-designer>

If you have forgotten how to write the plugin.xml, please refer to our last tutorial.

Build the project and install the plugin. If you see the demo dataset as follows, congrats!! result_1 result_2

The source code for this plugin is also available on Github: https://github.com/finereport-overseas/report-starter-10/tree/master/plugin-tabledata-demo

results matching ""

    No results matching ""