* All apps: printing of lists improved a lot, asks now how many lines to print

r51437: Work in progress of printing nextmatches, still needs some prettying up & edge case testing
r51453: Bug fixes on nextmatch printing
 - fix loaded rows check
 - fix hidden etemplate check
r51454: Printing CSS improvements
r51588: Attempt to get nextmatch printing always on the page (landscape)
r51589: Attempt to get nextmatch printing always on the page (landscape) - put things back if they cancel at nextmatch dialog
r51612: disable footer for print
This commit is contained in:
Ralf Becker 2015-02-06 13:36:25 +00:00
parent 4249dca91d
commit 6625ffdde4
7 changed files with 268 additions and 8 deletions

View File

@ -144,3 +144,20 @@ var et2_IDetachedDOM = new Interface({
});
/**
* Interface for widgets that need to do something special before printing
*/
var et2_IPrint = new Interface({
/**
* Set up for printing
*
* @return {undefined|Deferred} Return a jQuery Deferred object if not done setting up
* (waiting for data)
*/
beforePrint: function() {},
/**
* Reset after printing
*/
afterPrint: function() {}
});

View File

@ -68,7 +68,7 @@ var et2_INextmatchSortable = new Interface({
*
* @augments et2_DOMWidget
*/
var et2_nextmatch = et2_DOMWidget.extend([et2_IResizeable, et2_IInput],
var et2_nextmatch = et2_DOMWidget.extend([et2_IResizeable, et2_IInput, et2_IPrint],
{
attributes: {
// These normally set in settings, but broken out into attributes to allow run-time changes
@ -1836,6 +1836,141 @@ var et2_nextmatch = et2_DOMWidget.extend([et2_IResizeable, et2_IInput],
set_value: function(_value)
{
this.value = _value;
},
// Printing
/**
* Prepare for printing
*
* We check for un-loaded rows, and ask the user what they want to do about them.
* If they want to print them all, we ask the server and print when they're loaded.
*/
beforePrint: function() {
// Add the class, if needed
this.div.addClass('print');
// Trigger resize, so we can fit on a page
this.dynheight.outerNode.css('max-width',this.div.css('max-width'));
this.resize();
// Check for rows that aren't loaded yet, or lots of rows
var range = this.controller._grid.getIndexRange();
this.old_height = this.controller._grid._scrollHeight;
var loaded_count = range.bottom - range.top +1;
var total = this.controller._grid.getTotalCount();
if(loaded_count != total ||
this.controller._grid.getTotalCount() > 100)
{
// Defer the printing
var defer = jQuery.Deferred();
// Something not in the grid, lets ask
et2_dialog.show_prompt(jQuery.proxy(function(button, value) {
if(button == 'dialog[cancel]') {
// Give dialog a chance to close, or it will be in the print
window.setTimeout(function() {defer.reject();}, 0);
return;
}
value = parseInt(value);
if(value > total)
{
value = total;
}
// If they want the whole thing, treat it as all
if(button == 'dialog[ok]' && value == this.controller._grid.getTotalCount())
{
button = 'dialog[all]';
// Add the class, gives more reliable sizing
this.div.addClass('print');
}
// We need more rows
if(button == 'dialog[all]' || value > loaded_count)
{
var count = 0;
var fetchedCount = 0;
var cancel = false;
var nm = this;
var dialog = et2_dialog.show_dialog(
// Abort the long task if they canceled the data load
function() {count = total; cancel=true;window.setTimeout(function() {defer.reject();},0)},
egw.lang('Loading'), egw.lang('please wait...'),{},[
{"button_id": et2_dialog.CANCEL_BUTTON,"text": 'cancel',id: 'dialog[cancel]',image: 'cancel'}
]
);
// dataFetch() is asyncronous, so all these requests just get fired off...
// 200 rows chosen arbitrarily to reduce requests.
do {
var ctx = {
"self": this.controller,
"start": count,
"count": Math.min(value,200),
"lastModification": this.controller._lastModification
};
if(nm.controller.dataStorePrefix)
{
ctx.prefix = nm.controller.dataStorePrefix;
}
nm.controller.dataFetch({start:count, num_rows: Math.min(value,200)}, function(data) {
// Keep track
if(data && data.order)
{
fetchedCount += data.order.length;
}
nm.controller._fetchCallback.apply(this, arguments);
if(fetchedCount >= value)
{
if(cancel)
{
dialog.destroy();
defer.reject();
return;
}
nm.controller._grid.setScrollHeight(nm.controller._grid.getAverageHeight() * (value+1));
// Grid needs to redraw before it can be printed, so wait
window.setTimeout(jQuery.proxy(function() {
dialog.destroy();
// Should be OK to print now
defer.resolve();
},nm),ET2_GRID_INVALIDATE_TIMEOUT);
}
},ctx);
count += 200;
} while (count < value)
nm.controller._grid.setScrollHeight(nm.controller._grid.getAverageHeight() * (value+1));
}
else
{
// Don't need more rows, limit to requested and finish
this.controller._grid.setScrollHeight(this.controller._grid.getAverageHeight() * (value+1));
// Give dialog a chance to close, or it will be in the print
window.setTimeout(function() {defer.resolve();}, 0);
}
//this.controller._gridCallback(0, button == et2_dialog.OK_BUTTON ? value : this.controller._grid.getTotalCount());
},this),
egw.lang('How many rows to print'), egw.lang('Print'),
Math.min(100, total),
[
{"button_id": 1,"text": egw.lang('Ok'), id: 'dialog[ok]', image: 'check', "default":true},
// Nice for small lists, kills server for large lists
//{"button_id": 2,"text": egw.lang('All'), id: 'dialog[all]', image: ''},
{"button_id": 0,"text": egw.lang('Cancel'), id: 'dialog[cancel]', image: 'cancel'},
]
);
return defer;
}
// Don't return anything, just work normally
},
afterPrint: function() {
this.div.removeClass('print');
this.controller._grid.setScrollHeight(this.old_height);
delete this.old_height;
this.dynheight.outerNode.css('max-width','inherit');
this.resize();
}
});
et2_register_widget(et2_nextmatch, ["nextmatch"]);

View File

@ -904,6 +904,69 @@ etemplate2.app_refresh = function(_msg, _app, _id, _type)
return refresh_done;
};
/**
* "Intelligently" print a given app
*
* Mostly, we let the nextmatch change how many rows it's showing, so you don't
* get just one printed page.
*/
etemplate2.print = function(_app)
{
// Allow any widget to change for printing
var et2 = etemplate2.getByApplication(_app);
// Sometimes changes take time
var deferred = [];
for(var i = 0; i < et2.length; i++)
{
// Skip hidden templates
if(!jQuery(et2[i].DOMContainer).filter(':visible').length) continue;
et2[i].widgetContainer.iterateOver(function(_widget) {
// Skip widgets from a different etemplate (home)
if(_widget.getInstanceManager() != et2[i]) return;
var result = _widget.beforePrint();
if (typeof result == "object" && result.done)
{
deferred.push(result);
}
},et2,et2_IPrint);
}
// Try to clean up after - not guaranteed
var afterPrint = function() {
for(var i = 0; i < et2.length; i++)
{
// Skip hidden templates
if(!jQuery(et2[i].DOMContainer).filter(':visible')) continue;
et2[i].widgetContainer.iterateOver(function(_widget) {
_widget.afterPrint();
},et2,et2_IPrint);
}
var mediaQueryList = window.matchMedia('print');
mediaQueryList
};
if(egw.window.matchMedia) {
var mediaQueryList = window.matchMedia('print');
var listener = function(mql) {
if (!mql.matches) {
afterPrint();
mediaQueryList.removeListener(listener);
}
};
mediaQueryList.addListener(listener);
}
egw.window.onafterprint = afterPrint;
// Wait for everything to be loaded, then send it off
jQuery.when.apply(jQuery, deferred).done(function() {
egw.window.print();
}).fail(function() {
afterPrint();
});
}
// Some static things to make getting into widget context a little easier //
/**

View File

@ -462,6 +462,21 @@ which caused click on free space infront of a tag stops nm row selection*/
.et2_nextmatch .egwGridView_grid tr td div.et2_vbox a {
display: table-row;
}
/*
These are set via javascript before printing to help tame nextmatch's layout
for printing
*/
.et2_nextmatch.print .egwGridView_scrollarea {
height: auto !important;
width: auto !important;
}
.et2_nextmatch.print > div {
height: auto !important;
}
.et2_nextmatch.print {
/* This is fairly arbitrary, but makes it fit in Chrome and Firefox*/
max-width: 700pt !important;
}
/**
* Diff widget
*/
@ -1115,13 +1130,19 @@ div.message.floating {
.et2_nextmatch .egwGridView_grid > tbody > tr {
display: block;
}
.et2_nextmatch .egwGridView_spacer {
display:none;
}
.egwGridView_grid > tbody > tr {
page-break-inside: avoid;
-webkit-region-break-inside: avoid;
}
.et2_nextmatch > div {
width: 100% !important;
height: auto;
width: auto !important;
height: auto !important;
}
.et2_nextmatch table.egwGridView_outer thead tr th div.innerContainer {
max-height: inherit;
}
#cke_1_top.cke_top {
display: none;

View File

@ -18,21 +18,35 @@
@media print {
html, body {
width: 100% !important;
width: auto !important;
height: auto !important;
}
#egw_fw_sidebar, #egw_fw_topmenu, .egw_fw_ui_tabs_header, #egw_fw_topmenu_slide,#egw_fw_print,#egw_fw_logout {
display: none;
width: 0px;
}
#egw_fw_main, #egw_fw_tabs {
margin: 0px !important;
float: none !important;
width: auto !important;
}
#egw_fw_basecontainer {
position: inherit;
width: auto;
}
.egw_fw_ui_tab_content, .egw_fw_ui_tab_content > div {
height: auto;
.egw_fw_ui_tab_content {
position: relative;
float: none;
width: auto !important;
}
#egw_fw_main .egw_fw_ui_tab_content > div {
width: auto !important;
}
.egw_fw_ui_tab_content, .egw_fw_ui_tab_content div.egw_fw_content_browser_div {
height: auto !important;
overflow-y: hidden;
position: relative;
}
body {
background: white;

View File

@ -958,8 +958,18 @@ var fw_base = Class.extend({
if (appWindow)
{
appWindow.focus();
// et2 available, let its widgets prepare
if(typeof etemplate2 == "function" && etemplate2.print)
{
etemplate2.print(this.activeApp.appName);
}
else
{
// Print
appWindow.print();
}
}
}
}
});

View File

@ -16,7 +16,7 @@
*/
@media print {
#egw_fw_topmenu_addons, #egw_fw_header {
#egw_fw_topmenu_addons, #egw_fw_header, #egw_fw_footer {
display: none;
}
} /* @media print */