Workaround to TCPDF css limitations

Collapse
X
 
  • Time
  • Show
Clear All
new posts

  • telecastg
    replied
    Hi Mato,

    The code samples that I posted were meant to illustrate how to implement the print to window functionality into your own scripts, not to be used as a step by step guide.

    Here is a tutorial to add the print to window functionality to the Contact entity:

    First we need to let EspoCRM know that we want to use a custom view to render the Contact detail display, which is where the "Print to Window" drop down menu option will appear and we do this by creating a custom clientDefs file for "Contact" as follows:

    custom\Espo\Custom\Resources\metadata\clientDefs\C ontact.json
    Code:
    {
        "recordViews": {
            "detail": "custom:views/contact/record/detail"
        }
    }
    Next we need to define the custom view specified in the custom clientDefs:

    client\custom\src\views\contact\record\detail.js
    Code:
    Espo.define('custom:views/contact/record/detail', 'crm:views/contact/record/detail', function (Dep) {
    
        return Dep.extend({
    
            setupActionItems: function () {
    
                Dep.prototype.setupActionItems.call(this);
    
                this.dropdownItemList.push({
                    name: 'printWindow',
                    label: 'Print to Window'
                });                            
            },
    
            // print a record to a new window using a template
            actionPrintWindow: function () {
                // find out if the model has a templateId attribute
                if(this.model.attributes.templateId) {
                    var templateId = this.model.attributes.templateId;
                    window.open('?entryPoint=printToWindow&entityType='+this.model.name+'&entityId='+this.model.id+'&templateId=' + templateId, '_blank');
                // if not, open the select template model and wait for the user to select a template
                } else {
                    this.createView('windowTemplate', 'views/modals/select-template', {
                        entityType: this.model.name
                    }, function (view) {
                        view.render();
                        this.listenToOnce(view, 'select', function (model) {
                            this.clearView('windowTemplate');
                            window.open('?entryPoint=printToWindow&entityType='+this.model.name+'&entityId='+this.model.id+'&templateId=' + model.id, '_blank');
                        }, this);
                    });                
                }
            }
    
        });
    });
    The view script above, will add the "Print to Window" menu option to the drop down menu next to the "Edit" button in a Contact's detail display, and when the option is selected, it will call the function "actionPrintWindow".

    Function "actionPrintWindow" will look for a Template object already linked to the Contact object or it will display a modal window to allow the user to select the template to be used to render the Contact object.

    Once a template is defined, the function will invoke a back end Entry Point:

    custom\Espo\Custom\EntryPoints\PrintToWindow.php
    Code:
    namespace Espo\Custom\EntryPoints;  
    
    use \Espo\Core\Exceptions\NotFound;
    use \Espo\Core\Exceptions\BadRequest;  
    
    class PrintToWindow extends \Espo\Core\EntryPoints\Base {    
        public static $authRequired = true;    
    
        public function run()    {        
            if (empty($_GET['entityId']) || empty($_GET['entityType']) || empty($_GET['templateId'])) {            
                throw new BadRequest();        
            }        
    
            $entityId = $_GET['entityId'];        
            $entityType = $_GET['entityType'];        
            $templateId = $_GET['templateId'];        
            $entity = $this->getEntityManager()->getEntity($entityType, $entityId);        
            $template = $this->getEntityManager()->getEntity('Template', $templateId);        
            if (!$entity || !$template) {            
                throw new NotFound();        
            }        
            $this->getContainer()->get('serviceFactory')->create('PrintToWindowService')->buildFromTemplate($entity, $template, true);        
            exit;    
        }
    }
    Then the entry point will request service "PrintToWindowService" which will generate and return the HTML code to actually render the document in a new window.

    custom\Espo\Custom\Services\PrintToWindowService.p hp
    Code:
    namespace Espo\Custom\Services;  
    
    use \Espo\Core\Exceptions\Forbidden;
    use \Espo\Core\Exceptions\NotFound;
    use \Espo\Core\Exceptions\Error;  
    use \Espo\ORM\Entity;  
    use \Espo\Core\Htmlizer\Htmlizer;  
    
    class PrintToWindowService extends \Espo\Core\Services\Base {    
    
        protected $fontFace = 'freesans';    
        protected $fontSize = 10;    
        protected $removeMassFilePeriod = '1 hour';    
    
        protected function init()   {      
            $this->addDependency('fileManager');      
            $this->addDependency('acl');      
            $this->addDependency('metadata');      
            $this->addDependency('serviceFactory');      
            $this->addDependency('dateTime');      
            $this->addDependency('number');      
            $this->addDependency('entityManager');      
            $this->addDependency('defaultLanguage');  
        }    
    
        protected function getAcl()   {      
            return $this->getInjection('acl');  
        }    
    
        protected function getMetadata()   {      
            return $this->getInjection('metadata');  
        }    
    
        protected function getServiceFactory()   {      
            return $this->getInjection('serviceFactory');  
        }    
    
        protected function getFileManager()   {      
            return $this->getInjection('fileManager');  
        }    
    
        protected function createHtmlizer()   {      
            return new Htmlizer(          
                $this->getFileManager(),          
                $this->getInjection('dateTime'),          
                $this->getInjection('number'),          
                $this->getAcl(),          
                $this->getInjection('entityManager'),          
                $this->getInjection('metadata'),          
                $this->getInjection('defaultLanguage')      
            );  
            }    
    
        protected function printEntity(Entity $entity, Entity $template, Htmlizer $htmlizer)   {      
            $htmlFooter='';      
            $pageOrientation = 'Portrait';      
            $pageFormat = 'A4';        
            if ($template->get('fontFace')) {          
                $fontFace = $template->get('fontFace');      
            }        
            if ($template->get('printFooter')) {          
                $htmlFooter = $htmlizer->render($entity, $template->get('footer'));      
            }        
            if ($template->get('pageOrientation')) {          
                $pageOrientation = $template->get('pageOrientation');      
            }      
            if ($template->get('pageFormat')) {          
                $pageFormat = $template->get('pageFormat');      
            }        
            $htmlHeader = $htmlizer->render($entity, $template->get('header'));      
            $htmlBody = $htmlizer->render($entity, $template->get('body'));        
            return $htmlHeader.$htmlBody.$htmlFooter;  
        }    
    
        public function buildFromTemplate(Entity $entity, Entity $template)   {      
            $entityType = $entity->getEntityType();      
            $service = $this->getServiceFactory()->create($entityType);      
            $service->loadAdditionalFields($entity);        
            if ($template->get('entityType') !== $entityType) {          
                throw new Forbidden();      
            }        
            if (!$this->getAcl()->check($entity, 'read') || !$this->getAcl()->check($template, 'read')) {          
                throw new Forbidden();      
            }        
            $htmlizer = $this->createHtmlizer();        
            $output = $this->printEntity($entity, $template, $htmlizer);        
            echo $output;  
        }  
    }
    UPDATE for Espo 6.0.8+

    Files PrintToWindow.php and PrintToWindowService.php can now be downloaded as a package here: https://github.com/telecastg/print-t...ow-for-espocrm

    Changes to client\custom\src\views\contact\record\detail.js and to custom\Espo\Custom\Resources\metadata\clientDefs\C ontact.json remain the same

    UPDATE for Espo 7.x +
    Unfortunately Espo Core has been heavily refactored and previous custom implementations are no longer compatible. The files above are no longer available and using the old scripts will trigger errors.
    Last edited by telecastg; 10-24-2021, 02:33 AM. Reason: Files no longer available for download because of incompatibility with Espo 7.x

    Leave a comment:


  • MATO
    replied
    Hi telecastg

    I have created all structures and files

    For simplicity and for testing we are using Contacts

    where should we being seeing the button at present I am on a treasure hunt.

    We have run a rebuild backend and cleared cache

    Thank you for your help

    MATO

    Leave a comment:


  • telecastg
    replied
    Hi Mato,

    All folders under the "Custom" namespace need to be created, this is where Espo looks for custom implementations and any changes made there will not be affected by upgrades.

    For front-end (js) scripts the custom path is: "client/custom"... and for back-end (php) is "custom/Espo/Custom"...

    The scripts that I described, including the first, are custom made so you will have to write and save them.

    "my-entity" is just a placeholder for the name of your custom or standard entity to which you want to apply the printing method.

    For example, if you wanted to do it with "Contact" the name of the first script would be "espocrm\client\custom\src\views\contact\record\de tail.js"

    Cheers

    Leave a comment:


  • MATO
    replied
    Hi telecastg

    Very interested in your process of printing to PDF

    When looking for the following entries I am struggling to find them in our folder structure.

    Version 5.8.4

    "espocrm\client\custom\src\views\my-entity\record\detail.js"

    and

    "espocrm\custom\Espo\Custom\EntryPoints\PrintToWin dow.php"

    Are we creating the folder paths or should they already be there?

    Thank you for your help

    MATO

    Leave a comment:


  • telecastg
    replied
    I got it, here's what I did in case other newbie members like myself that might want to understand how the process of integrating a custom action button with a custom entry point works:

    Goal: Create a button at an entity's detail view that when pressed prints the entity fields using a template as html output to a new window, so the browser's "Print to PDF" capabilities render a PDF without having to be limited by TDPDF css limits.
    1. Create button inside the entity's record detail view file ("espocrm\client\custom\src\views\my-entity\record\detail.js")

      Code:
      setupActionItems: function () {
      	   this.dropdownItemList.push({
      	       label: 'Print Lease (Window)',
      	       name: '[COLOR=#FF0000]printLeaseWindow[/COLOR]'
      	   });
      	}
      
      [COLOR=#FF0000]actionPrintLeaseWindow[/COLOR]: function () {
      	   var templateId = this.model.get("templateId");
      	   window.open('?entryPoint=[COLOR=#FF8C00]printToWindow[/COLOR]&entityType='+this.model.name+'&entityId='+this.model.id+'&templateId=' + templateId, '_blank');
      	}
    2. Create class ("Entry Point") containing the entry point logic in file ("espocrm\custom\Espo\Custom\EntryPoints\PrintToWindow.php")

      Code:
      namespace Espo\Custom\EntryPoints;
      
      	use \Espo\Core\Exceptions\NotFound;
      	use \Espo\Core\Exceptions\BadRequest;
      
      	class [COLOR=#FFA500]PrintToWindow[/COLOR] extends \Espo\Core\EntryPoints\Base
      	{
      	   public static $authRequired = true;
      
      	   public function run()
      	   {
      
      	       if (empty($_GET['entityId']) || empty($_GET['entityType']) || empty($_GET['templateId'])) {
      	           throw new BadRequest();
      	       }
      	       $entityId = $_GET['entityId'];
      	       $entityType = $_GET['entityType'];
      	       $templateId = $_GET['templateId'];
      
      	       $entity = $this->getEntityManager()->getEntity($entityType, $entityId);
      	       $template = $this->getEntityManager()->getEntity('Template', $templateId);
      
      	       if (!$entity || !$template) {
      	           throw new NotFound();
      	       }
      
      	       $this->getContainer()->get('serviceFactory')->create('[COLOR=#800080]PrintToWindowService[/COLOR]')->buildFromTemplate($entity, $template, true);
      
      	       exit;
      	   }
      	}
    3. Create class ("Service") providing the logic to print an entity's fields using a given template as HTML output to a new window, file ("espocrm\custom\Espo\Custom\Services\PrintToWindowService.php")

      Code:
      namespace Espo\Custom\Services;
      
      	use \Espo\Core\Exceptions\Forbidden;
      	use \Espo\Core\Exceptions\NotFound;
      	use \Espo\Core\Exceptions\Error;
      
      	use \Espo\ORM\Entity;
      
      	use \Espo\Core\Htmlizer\Htmlizer;
      
      	class [COLOR=#800080]PrintToWindowService[/COLOR] extends \Espo\Core\Services\Base
      	{
      
      	   protected $fontFace = 'freesans';
      
      	   protected $fontSize = 10;
      
      	   protected $removeMassFilePeriod = '1 hour';
      
      	   protected function init()
      	   {
      	       $this->addDependency('fileManager');
      	       $this->addDependency('acl');
      	       $this->addDependency('metadata');
      	       $this->addDependency('serviceFactory');
      	       $this->addDependency('dateTime');
      	       $this->addDependency('number');
      	       $this->addDependency('entityManager');
      	       $this->addDependency('defaultLanguage');
      	   }
      
      	   protected function getAcl()
      	   {
      	       return $this->getInjection('acl');
      	   }
      
      	   protected function getMetadata()
      	   {
      	       return $this->getInjection('metadata');
      	   }
      
      	   protected function getServiceFactory()
      	   {
      	       return $this->getInjection('serviceFactory');
      	   }
      
      	   protected function getFileManager()
      	   {
      	       return $this->getInjection('fileManager');
      	   }
      
      	   protected function createHtmlizer()
      	   {
      	       return new Htmlizer(
      	           $this->getFileManager(),
      	           $this->getInjection('dateTime'),
      	           $this->getInjection('number'),
      	           $this->getAcl(),
      	           $this->getInjection('entityManager'),
      	           $this->getInjection('metadata'),
      	           $this->getInjection('defaultLanguage')
      	       );
      	   }
      
      	   protected function printEntity(Entity $entity, Entity $template, Htmlizer $htmlizer)
      	   {
      	       $htmlFooter='';
      	       $pageOrientation = 'Portrait';
      	       $pageFormat = 'A4';
      
      	       if ($template->get('fontFace')) {
      	           $fontFace = $template->get('fontFace');
      	       }
      
      	       if ($template->get('printFooter')) {
      	           $htmlFooter = $htmlizer->render($entity, $template->get('footer'));
      	       }
      
      	       if ($template->get('pageOrientation')) {
      	           $pageOrientation = $template->get('pageOrientation');
      	       }
      	       if ($template->get('pageFormat')) {
      	           $pageFormat = $template->get('pageFormat');
      	       }
      
      	       $htmlHeader = $htmlizer->render($entity, $template->get('header'));
      	       $htmlBody = $htmlizer->render($entity, $template->get('body'));
      
      	       return $htmlHeader.$htmlBody.$htmlFooter;
      	   }
      
      	   public function buildFromTemplate(Entity $entity, Entity $template)
      	   {
      	       $entityType = $entity->getEntityType();
      	       $service = $this->getServiceFactory()->create($entityType);
      	       $service->loadAdditionalFields($entity);
      
      	       if ($template->get('entityType') !== $entityType) {
      	           throw new Forbidden();
      	       }
      
      	       if (!$this->getAcl()->check($entity, 'read') || !$this->getAcl()->check($template, 'read')) {
      	           throw new Forbidden();
      	       }
      
      	       $htmlizer = $this->createHtmlizer();
      
      	       $output = $this->printEntity($entity, $template, $htmlizer);
      
      	       echo $output;
      	   }
      
      	}
    Last edited by telecastg; 11-14-2018, 08:21 AM.

    Leave a comment:


  • telecastg
    replied
    Thank you so much for your reply Tanya.

    I understand how to create and action button in detail view and I also understand (thanks to the information in this forum) how to call for the pdf entry point and display the result in a new window.

    What I don't understand is where would a custom entry point functionality be defined (in which folder) and also if possible could you provide an example of a "build document body and open new window with this body" code ?


    Leave a comment:


  • tanya
    replied
    You can develop own action button, where you can build document body and open new window with this body
    I create form " receipt voucher " and create the template with the same name, to print it as PDF, As the image. How can I link them and Print the

    Leave a comment:


  • telecastg
    started a topic Workaround to TCPDF css limitations

    Workaround to TCPDF css limitations

    I am using entry point "pdf" to generate a a "lease" document based on a record view of my custom "Tenancy" entity but I would like to be able to use the full css capabilities for positioning, fonts, background images, etc instead of being very limited by TCPDF css capabilities.

    One possible workaround I believe would be to output the raw HTML to a new window and use the built in "save as PDF" browser capability to create a PDF document. Is this possible and if so how would I go to implement in espoCRM ?

    Any help will be highly appreciated, I am not an experienced developer.
Working...