Topic Results missing when exporting to HTML

Guy started the topic:
2017-02-07 17:20

Results missing when exporting to HTML

Hello, hoping someone can shed some light on an issue I running into.

I've created my own extension that analyzes the values in the the datastore and outputs 1 or 2 crosstabs depending on the options chosen before analyzing.

When viewing the results in the program I can see my crosstabs and their labels just fine but when I export to HTML nothing is there. Just the label for the extension.

I used BooleanAnalyzerResultSwingRenderer as my base and made my modifications off of it:

package cdwtester.analyzer;

import javax.inject.Inject;
import javax.swing.Box;
import javax.swing.JComponent;
import javax.swing.JLabel;

import org.datacleaner.api.RendererBean;
import org.datacleaner.bootstrap.WindowContext;
import org.datacleaner.panels.DCPanel;
import org.datacleaner.result.Crosstab;
import org.datacleaner.result.CrosstabResult;
import org.datacleaner.result.renderer.AbstractRenderer;
import org.datacleaner.result.renderer.RendererFactory;
import org.datacleaner.result.renderer.SwingRenderingFormat;
import org.datacleaner.util.WidgetUtils;
import org.datacleaner.widgets.result.DefaultCrosstabResultSwingRenderer;
import org.datacleaner.widgets.table.CrosstabPanel;
import org.jdesktop.swingx.VerticalLayout;

@RendererBean(SwingRenderingFormat.class)
public class CDWTableAnalyzerResultRenderer extends AbstractRenderer<CDWTableAnalyzerResult, JComponent>{
@Inject
WindowContext _windowContext;

@Inject
RendererFactory _rendererFactory;

@Override
public JComponent render(final CDWTableAnalyzerResult result) {
final DefaultCrosstabResultSwingRenderer crosstabResultSwingRenderer =
new DefaultCrosstabResultSwingRenderer(_windowContext, _rendererFactory);

final Crosstab<Number> tableCrosstab = result.getTableCrosstab();
final Crosstab<Number> phiCrosstab = result.getPHICrosstab();
CrosstabPanel tablePanel = null;

if (tableCrosstab != null){
tablePanel = (CrosstabPanel) crosstabResultSwingRenderer.render(new CrosstabResult(tableCrosstab));
}

if (phiCrosstab == null) {
return tablePanel;
}

CrosstabPanel phiPanel = (CrosstabPanel) crosstabResultSwingRenderer.render(new CrosstabResult(phiCrosstab));
if(tableCrosstab == null){
return phiPanel;
}

final DCPanel panel = new DCPanel();
panel.setLayout(new VerticalLayout(4));

JLabel label = new JLabel("Table Analyzer:");
label.setFont(WidgetUtils.FONT_HEADER1);
panel.add(label);
panel.add(tablePanel);

panel.add(Box.createVerticalStrut(4));

label = new JLabel("PHI Searcher:");
label.setFont(WidgetUtils.FONT_HEADER1);
panel.add(label);
panel.add(phiPanel);

return panel;
}
}
Dennis replied:
2017-02-08 08:03
Hi Guy,

To export a result as HTML, something needs to be able to render it as HTML, so you will also need to make a RendererBean for the HTML. There is a CrosstabHtmlRenderer that you can probably leverage more or less in the same way that you did with the DefaultCrosstabSwingRenderer.

Best regards,
Dennis
Dennis replied:
2017-02-08 08:12
An alternative, if you only need a summary, is to make a few getter methods in your AnalyzerResult (e.g. a getTotalRowCount() and a getPhiRowCount(), or whatever makes sense) and annotate them with @Metric, then the default renderer should output those.
Guy replied:
2017-02-08 17:04
So I've started looking into CrosstabHtmlRenderer and I'm trying to figure out how HtmlFragment works. At this point I can either output one table (not both like I need) or a couple lines meant to be the table labels (rendered via SimpleHtmlFragment).

Is it possible to create an htmlFragment that has multiple crosstabs in it? When I try to get the BodyElements out of the existing CrosstabHtmlFragment and add them to my SimpleHtmlFragment I end up with a NullPointerException.

Code so far:
package cdwtester.analyzer;

import javax.inject.Inject;

import org.datacleaner.api.RendererBean;
import org.datacleaner.result.Crosstab;
import org.datacleaner.result.html.BodyElement;
import org.datacleaner.result.html.HtmlFragment;
import org.datacleaner.result.html.SimpleHtmlFragment;
import org.datacleaner.result.renderer.AbstractRenderer;
import org.datacleaner.result.renderer.CrosstabHtmlFragment;
import org.datacleaner.result.renderer.RendererFactory;
import org.datacleaner.api.Provided;
import org.datacleaner.result.renderer.HtmlRenderingFormat;


@RendererBean(HtmlRenderingFormat.class)
public class CDWTableAnalyzerHtmlRenderer extends AbstractRenderer<CDWTableAnalyzerResult, HtmlFragment>{

@Inject
@Provided
RendererFactory rendererFactory;

public CDWTableAnalyzerHtmlRenderer() {
this(null);
}

public CDWTableAnalyzerHtmlRenderer(final RendererFactory rendererFactory) {
this.rendererFactory = rendererFactory;
}

@Override
public HtmlFragment render(final CDWTableAnalyzerResult result) {
final SimpleHtmlFragment htmlFragment = new SimpleHtmlFragment();

final Crosstab<Number> tableCrosstab = result.getTableCrosstab();
final Crosstab<Number> phiCrosstab = result.getPHICrosstab();
HtmlFragment tablePanel = render(tableCrosstab);
HtmlFragment phiPanel = render(phiCrosstab);


htmlFragment.addBodyElement("<p>Table Analyzer:</p>");

if(!tablePanel.equals(null)){
BodyElement[] tableElements = (BodyElement[]) tablePanel.getBodyElements().toArray();
for(int i = 0; i < tableElements.length; i++){
if(!tableElements[i].equals(null)){
htmlFragment.addBodyElement(tableElements[i]);
}
}
}

//htmlFragment.addBodyElement("<p>PHI Searcher:</p>");
//htmlFragment.addBodyElement(phiPanel.toString());

return htmlFragment;
}

public HtmlFragment render(final Crosstab<?> crosstab) {
return new CrosstabHtmlFragment(crosstab, rendererFactory);

}

}
Dennis replied:
2017-02-09 07:06
Hmmm, not entirely sure why you'd see an NPE when adding the BodyElements. How does the stack trace look?
Dennis replied:
2017-02-09 09:26
Hi Guy,

I tried making a quick test, and this seems to work for me:
 
Crosstab<Integer> regionCrosstab = new Crosstab<>(Integer.class, "Region");
regionCrosstab.where("Region", "EU").put(1, true);
regionCrosstab.where("Region", "USA").put(2, true);
regionCrosstab.where("Region", "Asia").put(3, true);

CrosstabHtmlFragment regionFragment = new CrosstabHtmlFragment(regionCrosstab, rendererFactory);

Crosstab<Integer> stuffCrosstab = new Crosstab<>(Integer.class, "Stuff");
stuffCrosstab.where("Stuff", "Foo").put(4, true);
stuffCrosstab.where("Stuff", "Bar").put(5, true);
stuffCrosstab.where("Stuff", "Baz").put(6, true);

CrosstabHtmlFragment stuffFragment = new CrosstabHtmlFragment(stuffCrosstab, rendererFactory);

HtmlFragment bothCrosstabs = new HtmlFragment() {
@Override
public void initialize(final HtmlRenderingContext context) {
regionFragment.initialize(context);
stuffFragment.initialize(context);
}

@Override
public List<HeadElement> getHeadElements() {
List<HeadElement> headElements = new ArrayList<>();
headElements.addAll(regionFragment.getHeadElements());
headElements.addAll(stuffFragment.getHeadElements());
return headElements;
}

@Override
public List<BodyElement> getBodyElements() {
List<BodyElement> bodyElements = new ArrayList<>();
bodyElements.add(new SimpleBodyElement("<p>Region crosstab:</p>"));
bodyElements.addAll(regionFragment.getBodyElements());
bodyElements.add(new SimpleBodyElement("<p>Stuff crosstab:</p>"));
bodyElements.addAll(stuffFragment.getBodyElements());
return bodyElements;
}
};


The NPEs was probably because the CrosstabHtmlFragments wasn't initialized. However, as you can see, it's not entirely simple: The renderer is not initialized with a rendering context (for some reason), but the fragments are. So by returning a new fragment implementation we can do the initialization when the rendering context is available (making the HtmlFragment implementation into a nested or top-level class would probably make my demo code a little less ugly :))

By the way, since we currently don't have any head elements and only a single body element in the CrosstabHtmlFragment, you _could_ make it a little simpler by relying on that, but it would mean that it might break if we change it later so I'd keep the full copy of head and body elements.

Best regards,
Dennis
Guy replied:
2017-02-09 15:02
Hi Dennis,

Thanks for all your help. I was able to get the export working the way I wanted and match the functionality of my ResultRenderer. The code needs some cleaning up but its working and that's all I care about for now.

The render function in my HtmlRenderer ended up looking like this:

public HtmlFragment render(final CDWTableAnalyzerResult result) {   	
final Crosstab<Number> tableCrosstab = result.getTableCrosstab();
final Crosstab<Number> phiCrosstab = result.getPHICrosstab();

final CrosstabHtmlFragment tableFragment = tableCrosstab != null? new CrosstabHtmlFragment(tableCrosstab, rendererFactory) : null;
final CrosstabHtmlFragment phiFragment = phiCrosstab != null? new CrosstabHtmlFragment(phiCrosstab, rendererFactory) : null;

HtmlFragment finalCrosstab = new HtmlFragment() {
@Override
public void initialize(final HtmlRenderingContext context) {
if(tableFragment != null){
tableFragment.initialize(context);
}
if(phiFragment != null){
phiFragment.initialize(context);
}
}

@Override
public List<HeadElement> getHeadElements() {
List<HeadElement> headElements = new ArrayList<>();

if(tableFragment != null){
headElements.addAll(tableFragment.getHeadElements());
}

if(phiFragment != null){
headElements.addAll(phiFragment.getHeadElements());
}

return headElements;
}

@Override
public List<BodyElement> getBodyElements() {
List<BodyElement> bodyElements = new ArrayList<>();

if(tableFragment != null){
bodyElements.add(new SimpleBodyElement("<p style='font-size:18px'><b>Table Analyzer:</b></p>"));
bodyElements.addAll(tableFragment.getBodyElements());
}

if(tableFragment != null && phiFragment != null){
bodyElements.add(new SimpleBodyElement("<br/><br/>"));
}

if(phiFragment != null){
bodyElements.add(new SimpleBodyElement("<p style='font-size:18px'><b>PHI Searcher:</b></p>"));
bodyElements.addAll(phiFragment.getBodyElements());
}

return bodyElements;
}
};

return finalCrosstab;
}

Thanks again for all your help!
Dennis replied:
2017-02-09 19:51
You're very welcome! :)
You are not signed in.
You need to be signed in to participate in the discussion.