iText 7 pdhHtml keep table rows together - itext7

I am new in iText 7, i am developing a spa project (asp.net, c#, and angularjs), where i need to implement a report for existing html page.I found iText 7 (.Net) has a easy way to implement it. Using below code of line, that's return me a byte array and i can easily show in browser as pdf also can download.
var memStream = new MemoryStream();
ConverterProperties converterProperties = new ConverterProperties();
converterProperties.SetFontProvider(fontProvider); converterProperties.SetBaseUri(System.AppDomain.CurrentDomain.BaseDirectory);
HtmlConverter.ConvertToPdf(htmlText, memStream, converterProperties);
In my raw html there has some html tables (every table has some particular rows) and i want to keep them in a page (i mean if table rows not fit in a single page then start from next page). I got a solution like below
Paragraph p = new Paragraph("Test");
PdfPTable table = new PdfPTable(2);
for (int i = 1; i < 6; i++) {
table.addCell("key " + i);
table.addCell("value " + i);
}
for (int i = 0; i < 40; i++) {
document.add(p);
}
// Try to keep the table on 1 page
table.setKeepTogether(true);
document.add(table);
But in my case i cannot implement like that way because content already exist in html tables (in my existing html page).
Advance thanks, if anyone can help me.

This can easily be done using a custom TagWorkerFactory and TableTagWorker class.
Take a look at the code samples below.
The first thing we should do is create a custom TableTagWorker that tells iText to keep the table together. We do this using the code you've mentioned: table.setKeepTogether(true).
class CustomTableTagWorker extends TableTagWorker{
public CustomTableTagWorker(IElementNode element, ProcessorContext context) {
super(element, context);
}
#Override
public void processEnd(IElementNode element, ProcessorContext context) {
super.processEnd(element, context);
((com.itextpdf.layout.element.Table) getElementResult()).setKeepTogether(true);
}
}
As you can see the only thing we changed on our custom TableTagWorker is the fact that it has to keep the table together.
The next step would be to create a custom TagWorkerFactory that maps our CustomTableTagWorker to the table tag in HTML. We do this like so:
class CustomTagWorkerFactory extends DefaultTagWorkerFactory{
#Override
public ITagWorker getCustomTagWorker(IElementNode tag, ProcessorContext context) {
if (tag.name().equalsIgnoreCase("table")) {
return new CustomTableTagWorker(tag, context); // implements ITagWorker
}
return super.getCustomTagWorker(tag, context);
}
}
All we do here is tell iText that if it finds a table tag it should pass the element to the CustomTableTagWorker, in order to be converted to a PDF object (where setKeepTogether == true).
The last step is registering this CustomTagWorkerFactory on our ConverterProperties.
ConverterProperties converterProperties = new ConverterProperties();
converterProperties.setTagWorkerFactory(new CustomTagWorkerFactory());
HtmlConverter.convertToPdf(HTML, new FileOutputStream(DEST), converterProperties);
Using these code samples I was able to generate an output PDF where tables, if small enough to render on an entire page, will never be split across multiple pages.

I had a similar issue of trying to keep together content within a div. I applied the following css property and this kept everything together. This worked with itext7 pdfhtml.
page-break-inside: avoid;

Related

How to tag table header cells as TH instead of TD in iText7?

I need to create a PDF/UA compliant document in iText7. The most important requirement is tagging of all content. When tagging is enabled (by calling PdfDocument.SetTagged() method) most elements added to the document get correct tags.
The issue is with tagging of table header cells. According to ISO 32000-1:2008, table header cells must be tagged as TH and table data cells must be tagged as TD (14.8.4.2.4. Table elements, Table 337).
iText allows to distinguish between header cells and regular cells by using Table.AddHeaderCell() and Table.AddCell() methods. This mechanism properly creates THead and TBody tags for the groups of rows. Unfortunately, the cells themselves are always marked as TD.
Here is sample code for generating a table:
//var pdfDoc = new PdfDocument(...)
pdfDoc.SetTagged();
var doc = new Document(pdfDoc);
var table = new Table(2);
table.AddHeaderCell("Header 0");
table.AddHeaderCell("Header 1");
table.AddCell("Data 0");
table.AddCell("Data 1");
doc.Add(table);
doc.Close();
Here is an example of tagging structure we are getting:
<Table>
<THead>
<TR>
<TD> //must be TH!
<P>
"Header 0"
<TD>
<P>
"Header 1"
<TBody>
<TR>
<TD> //TD is correct here
<P>
"Data 0"
<TD>
<P>
"Data 1"
Is it possible to have iText generating TH tags when AddHeaderCell() method is used?
I am using iText 7.0.0 for .NET (Community edition)
EDIT: Initial answer was in mistakingly given in the context of pdfHTML and not iText7 proper.
The TH tags getting tagged as TD is a side-effect of the current implementation that treats a TH in the same way as a TD.
For iText7
Set the role of the header-cells to TH before adding them to the table:
cell.setRole(PdfName.TH);
For pdfHTML
While it's possible to access the elements after conversion and before adding them to the document, you'll need to traverse the tree of iText element to find and identify tables and their header -cells. It's easier to to overwrite the conversion behavior of tags with a CustomTagWorker. The following code is taken from the accessibility example. For a primer on custom tagworkers, have a look at the configuration blog-post.
Start by creating a custom tagworker that inherits from a TdTagWorker, but overwrites the role right before returning the element-result:
public class TableHeaderTagWorker extends TdTagWorker {
public TableHeaderTagWorker(IElementNode element, ProcessorContext context) {
super(element, context);
}
#Override
public IPropertyContainer getElementResult() {
Cell cell =(Cell) super.getElementResult();
cell.setRole(PdfName.TH);
return super.getElementResult();
}
}
Create a CustomTagWorkerFactory that maps this TagWorker to the TH-tag
public class AccessibilityTagWorkerFactory extends DefaultTagWorkerFactory {
#Override
public ITagWorker getCustomTagWorker(IElementNode tag, ProcessorContext context) {
//This can probably replaced with a regex or string pattern
if(tag.name().equals("h1")){
return new HeaderTagWorker(tag, context,1);
}
if(tag.name().equals("h2")){
return new HeaderTagWorker(tag, context,2);
}
if(tag.name().equals("h3")){
return new HeaderTagWorker(tag, context,3);
}
if(tag.name().equals("h4")){
return new HeaderTagWorker(tag, context,4);
}
if(tag.name().equals("h5")){
return new HeaderTagWorker(tag, context,5);
}
if(tag.name().equals("h6")){
return new HeaderTagWorker(tag, context,6);
}
if(tag.name().equals("th")){
return new TableHeaderTagWorker(tag,context);
}
return null;
}
}
And set the ConvertorProperties to use this custom factory:
ConverterProperties props = new ConverterProperties();
DefaultTagWorkerFactory tagWorkerFactory = new AccessibilityTagWorkerFactory();
props.setTagWorkerFactory(tagWorkerFactory);
HtmlConverter.convertToPdf(new FileInputStream(src), pdfDoc, props);
pdfDoc.close();
Please note that this has changed with iText 7.1. You can no longer call the setRole() function directly, you have to go through the Accessibility Properties. Furthermore, the setRole() function in the Accessibility Properties only accepts a string. So now, it would be:
cell.getAccessibilityProperties().setRole(PdfName.TH.toString());

How can I full text search response documents and have the parent show in the view/data table

I'm at a bit of an impasse. I am working in Domino with xPages and I am trying to allow full text searching through a view including response documents but including the parent document for any responses that match the query in the view or data table. Currently I'm just using the search term in a view datasource, and then using that datasource in a view control, but any workable solution would be welcome. There may be additional search criteria on the parent document.
Any ideas?
Richard,
you can't directly use the view as data source, so you won't use the view control. You can use the data table or (probably better, since it gives you full layout control) the repeat control.
Run the search against the view in code:
var v = database.getView("yourView")
//var result = database.FTSearch(...)
var result = v.FTSearchSorted(...) // or FTSearch
var datasource = [];
var parent;
for (var doc in result) {
addResult(doc, datasource);
if (doc.isResponseDoc()) {
parent = doc.getParentDocument();
addResult(parent, datasource);
// Careful here - if the parent is part of the resultset on its own
parent.recycle();
}
doc.recycle();
}
try {
result.recycle();
v.recycle();
} catch (e) {
// We suffer silently
}
return datasource;
function addResult(doc, datasource) {
var oneResult = {};
//Adjust that to your needs
oneResult.subject = doc.getItemValueString("Subject");
oneResult.unid = doc.getUniversalId();
datasource.push(oneResult);
}
See the FTSearchSorted documentation. I typed the code off my head, so there might be little syntax snafus, ut you get the idea Don't return documents or Notes objects to the XPage and use recycle() wisely.

GWT image click handler that changes size of image

I would like to add a click handler to an image that changes the size of the image. The BlobStore allows one to easily change the size of an image by changing the suffix of the URL, as "=s128" will change the image to 128 pixels in the line below:
im.setUrl(thing.get(i)+"=s128");
I want to use this feature to change the size of an image after it is clicked; in this example to change back to its original size, like:
//-- when clicked, do this:
im.setUrl(thing.get(i));
and I thought the following code would do it for a group of images:
for (int i=0; i<thing.size(); i++){
final Image im = new Image();
im.setUrl(thing.get(i)+"=s128");
im.addClickHandler(new ClickHandler(){
public void onClick(ClickEvent event){
im.setUrl(response.get(i));
}
});
htmlpanel.add(im,"imageGrid");
}
But the second "setUrl" line gives me this error:
Multiple markers at this line
- Cannot refer to a non-final variable response inside an inner class defined in a different method
- Cannot refer to a non-final variable i inside an inner class defined in a different method
How can I do this? Thanks.
response and i variables should be final. That why compiler can't compile this code. But you can't make ifinal because you are incrementing it in in for cycle. So you'll have to copy it into some final variable:
for (int i = 0; i < thing.size(); i++){
final Image im = new Image();
final int index = i; //here
im.setUrl(thing.get(i) + "=s128");
im.addClickHandler(new ClickHandler() {
public void onClick(ClickEvent event) {
im.setUrl(response.get(index ));
}
});
htmlpanel.add(im, "imageGrid");
}
But the best solution will be to create special click handler class, which will set a specific URL to specific Image.
UPDATE
This structure:
new ClickHandler() {
public void onClick(ClickEvent event) {
im.setUrl(response.get(index ));
}
}
is called an anonymous class. If it needs to access some variable/parameter in method which defines it, such variable/parameter have to be final.

Wicket: How can I rerender the current form without losing existing input?

I have a form with a combobox/drop down to select the user language. If the user changes the language, I'd like to update all the labels but leave the input elements alone.
In jQuery, I'd request a list of label IDs and the new texts via JSON and then use a loop like this:
var texts = {[ {id:'nameLabel', text:'First Name'}, {id:'familyLabel', text:'Family Name'} ]};
for( var i=0; i<texts.length; i++) {
var item = texts[i];
$('#'+item.id).text(item.text);
}
That would update all the labels without modifying anything else. How do I do this in Wicket?
[EDIT] What I tried:
DropDownChoice<Locale> ddc = new DropDownChoice<Locale>(...);
ddc.add( new AjaxFormComponentUpdatingBehavior("onchange") {
private static final long serialVersionUID = 1L;
#Override
protected void onUpdate( AjaxRequestTarget target ) {
getSession().setLocale( language );
for( MarkupContainer label : labels ) {
target.addComponent( label );
}
}
});
This does change the labels but it also renders all the input fields again. I found no way to access the current values of the input fields.
[EDIT2] The list of labels is created like so:
StringResourceModel usernameLabel = new StringResourceModel("usernameLabel", this, new Model<ValueMap>(map));
labels.add(add(new Label("usernameLabel", usernameLabel)));
This is wrong:
labels.add(add(new Label("usernameLabel", usernameLabel)));
You're not adding Label instances to 'labels', it's repeatedly adding the container you are adding it to (probably the Page instance). The method 'add()' doesn't return the component being added, it returns the container you are adding the components into.
Try changing it to:
Label label = new Label("usernameLabel", usernameLabel);
add(label);
labels.add(label);

Using DataObjectTypeName in DataObjectSource

The functionality I am trying to use is:
- Create a ObjectDataSource for selection and updating controls on a web page (User Control).
- Use the DataObjectTypeName to have an object created that would send the data to an UpdateMethod.
- Before the values are populated in the DataObjectTypeName’s object, I would like to pre-populate the object so the unused items in the class are not defaulted to zeros and empty strings without me knowing whether the zero or default string was set by the user or by the application.
I cannot find a way to pre-populate the values (this was an issue back in 2006 with framework 2.0). One might ask “Why would anyone need to pre-populate the object?”. The simple answer is: I want to be able to randomly place controls on different User Controls and not have to be concerned with which UpdateMethod needs to handle which fields of an object.
For Example, let’s say I have a class (that reflects a SQL Table) that includes the fields: FirstName, LastName, Address, City, State, Zip. I may want to give the user the option to change the FirstName and LastName and not even see the Address, City, State, Zip (or vice-versa). I do not want to create two UpdateMethods where one handled FirstName and LastName and the other method handles the other fields. I am working with a Class of some 40+ columns from multiple tables and I may want some fields on one screen and not another and decide later to change those fields from one screen to another (which breaks my UpdateMethods without me knowing).
I hope I explained my issue well enough.
Thanks
This is hardly a solution to the problem, but it's my best stab at it.
I have a GridView with its DataSourceID set to an ObjectDataSource.
Whenever a row is updated, I want the property values in the object to be selectively updated - that is - only updated if they appear as columns in the GridView.
I've created the following extension:
public static class GridViewExtensions
{
public static void EnableLimitUpdateToGridViewColumns(this GridView gridView)
{
_gridView = gridView;
if (_gridView.DataSourceObject != null)
{
((ObjectDataSource)_gridView.DataSourceObject)
.Updating += new ObjectDataSourceMethodEventHandler(objectDataSource_Updating);
}
}
private static GridView _gridView;
private static void objectDataSource_Updating(object sender, ObjectDataSourceMethodEventArgs e)
{
var newObject = ((object)e.InputParameters[0]);
var oldObjects = ((ObjectDataSource)_gridView.DataSourceObject).Select().Cast<object>();
Type type = oldObjects.First().GetType();
object oldObject = null;
foreach (var obj in oldObjects)
{
if (type.GetProperty(_gridView.DataKeyNames.First()).GetValue(obj, null).ToString() ==
type.GetProperty(_gridView.DataKeyNames.First()).GetValue(newObject, null).ToString())
{
oldObject = obj;
break;
}
}
if (oldObject == null) return;
var dynamicColumns = _gridView.Columns.OfType<DynamicField>();
foreach (var property in type.GetProperties())
{
if (dynamicColumns.Where(c => c.DataField == property.Name).Count() == 0)
{
property.SetValue(newObject, property.GetValue(oldObject, null), null);
}
}
}
}
And in the Page_Init event of my page, I apply it to the GridView, like so:
protected void Page_Init()
{
GridView1.EnableLimitUpdateToGridViewColumns();
}
This is working well for me at the moment.
You could probably apply similar logic to other controls, e.g. ListView or DetailsView.
I'm currently scratching my head to think of a way this can be done in a rendering-agnostic manner - i.e. without having to know about the rendering control being used.
I hope this ends up as a normal feature of the GridView or ObjectDataSource control rather than having to hack it.

Resources