diff --git a/etemplate/js/et2_core_interfaces.js b/etemplate/js/et2_core_interfaces.js index 15a5a98d22..70d2508de9 100644 --- a/etemplate/js/et2_core_interfaces.js +++ b/etemplate/js/et2_core_interfaces.js @@ -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() {} +}); \ No newline at end of file diff --git a/etemplate/js/et2_extension_nextmatch.js b/etemplate/js/et2_extension_nextmatch.js index 8cadd189da..a52450ea38 100644 --- a/etemplate/js/et2_extension_nextmatch.js +++ b/etemplate/js/et2_extension_nextmatch.js @@ -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"]); diff --git a/etemplate/js/etemplate2.js b/etemplate/js/etemplate2.js index a785bc846b..d5d9c0aec8 100644 --- a/etemplate/js/etemplate2.js +++ b/etemplate/js/etemplate2.js @@ -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 // /** diff --git a/etemplate/templates/default/etemplate2.css b/etemplate/templates/default/etemplate2.css index b1298b5171..80c5f726f7 100644 --- a/etemplate/templates/default/etemplate2.css +++ b/etemplate/templates/default/etemplate2.css @@ -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; diff --git a/jdots/print.css b/jdots/print.css index 02852e7b5e..02b8fd0f86 100644 --- a/jdots/print.css +++ b/jdots/print.css @@ -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; diff --git a/phpgwapi/js/framework/fw_base.js b/phpgwapi/js/framework/fw_base.js index 55d5268fe5..dd88d0d5d8 100644 --- a/phpgwapi/js/framework/fw_base.js +++ b/phpgwapi/js/framework/fw_base.js @@ -958,7 +958,17 @@ var fw_base = Class.extend({ if (appWindow) { appWindow.focus(); - appWindow.print(); + + // et2 available, let its widgets prepare + if(typeof etemplate2 == "function" && etemplate2.print) + { + etemplate2.print(this.activeApp.appName); + } + else + { + // Print + appWindow.print(); + } } } } diff --git a/pixelegg/print.css b/pixelegg/print.css index 1ac39f43db..27ae35c75c 100644 --- a/pixelegg/print.css +++ b/pixelegg/print.css @@ -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 */ \ No newline at end of file