Workaround to TCPDF css limitations

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • telecastg
    Active Community Member
    • Jun 2018
    • 907

    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.
  • tanya
    Senior Member
    • Jun 2014
    • 4308

    #2
    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

    Comment

    • telecastg
      Active Community Member
      • Jun 2018
      • 907

      #3
      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 ?


      Comment

      • telecastg
        Active Community Member
        • Jun 2018
        • 907

        #4
        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.

        Comment

        • MATO
          Member
          • Jun 2019
          • 98

          #5
          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

          Comment

          • telecastg
            Active Community Member
            • Jun 2018
            • 907

            #6
            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

            Comment

            • MATO
              Member
              • Jun 2019
              • 98

              #7
              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

              Comment

              • telecastg
                Active Community Member
                • Jun 2018
                • 907

                #8
                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

                Comment

                • jc63
                  Junior Member
                  • Apr 2020
                  • 2

                  #9
                  Hi telecastg,

                  I just wanted to say thank you very much for this! I'm new to EspoCRM and your post has helped me a great deal in customizing the app for our use case.

                  Cheers!

                  Comment


                  • telecastg
                    telecastg commented
                    Editing a comment
                    You're very welcome :-)

                    As you do different things with Espo please share your findings and solutions in the forum so everybody benefits.

                    Cheers !
                • emillod
                  Active Community Member
                  • Apr 2017
                  • 1405

                  #10
                  We've created small workaround for that. We create PDF template in EspoCRM entity and after that we can create PDF as html template in FTP. Of course this html template can be connected with header and footer from templates entity. Also you can put all fields in this html template like in template entity, so for example {{name}}

                  Comment

                  • esforim
                    Active Community Member
                    • Jan 2020
                    • 2204

                    #11
                    telecastg


                    I'm getting a 404 Page Not Found after click "Print to Window", digging around a little and I notice that the URL is ?entryPoint but in some of the code is ?entryPoints (with a "s"), so I tried create a folder call entryPoint but that doesn't work either.

                    From the look of it, it can't find this file?: custom\Espo\Custom\Services\PrintToWindowService.p hp
                    But I'm sure the file exist and it in the right location with the correct filename. Also changed a couple of File Permission see if that help.
                    Also change filename to see if it case sensitive (e.g. printToWindow.php)

                    Not sure what the issue at this point.


                    My Old Post:
                    Never gotten around to this but seeing as I'm learning how to create button I decide to give it a try.

                    Unfortunately I'm stuck in the first step and need help on what to do.

                    For my Contact.json file there is already code in there so I'm getting a Syntax error, not sure how do I paste it so it will work and don't receive syntax error

                    I probably need to add a comma or semi-colon somewhere don't I?

                    Here is what I have at the moment:

                    edit: I think I figure it out. I added the code after the icon class and add a ", comma" at the end"
                    Code:
                    "iconClass": "fas fa-id-badge",
                    "recordViews": {
                    "detail": "custom:views/contact/record/detail"
                    },
                    Last edited by esforim; 07-02-2020, 12:49 AM.

                    Comment

                    • telecastg
                      Active Community Member
                      • Jun 2018
                      • 907

                      #12
                      Please post the whole code and the name with full path of the script that you are editing.

                      The first step is to create a custom Contact,json clientDefs metadata file in the "Custom" namespace so it will not interfere with the core Contact.json clientDef metadata file.

                      Comment

                      • esforim
                        Active Community Member
                        • Jan 2020
                        • 2204

                        #13
                        Hi telecastg,

                        Thank you. I decided to upload the whole customized file if you have the chance to look at it. It pretty much a copy/paste job, only change I made was to the: custom\Espo\Custom\Resources\metadata\clientDefs\C ontact.json
                        File, I also update my previous post so I think you post your message before you read my update.

                        Please check: https://www93.zippyshare.com/v/GgHXaVLr/file.html OR https://github.com/espcrm/EspoCRM-Customize/tree/master
                        I also made the folder tree/location as per guide, and also upload the file and extract it to my CRM incase I made a type with file or folder name.

                        But still no luck, not why where I went wrong at the moment.
                        Last edited by esforim; 07-02-2020, 03:34 AM.

                        Comment

                        • telecastg
                          Active Community Member
                          • Jun 2018
                          • 907

                          #14
                          Hi esforim

                          Originally posted by espcrm
                          I decided to upload the whole customized file if you have the chance to look at it.
                          Sorry but I can't help with code debugging.

                          This is NOT an extension or package that will self install so you should not compress and upload anything. What you need to do is to write 4 different scripts and save them to the correct file path.

                          The correct procedure is:

                          1) If you haven't done it yet, set up a local server (xampp for example) and install Espo in a local website

                          2) Use your code editor or IDE to create each script exactly as written and save it in the exact specified folder (path) within the Espo installation.

                          The correct names and paths for each script are: (I use forward slashes because otherwise the forum's markdown inserts unwanted spaces)

                          custom/Espo/Custom/Resources/metadata/clientDefs/Contact.json

                          client/custom/src/views/contact/record/detail.js

                          custom/Espo/Custom/EntryPoints/PrintToWindow.php

                          custom/Espo/Custom/Services/PrintToWindowService.php

                          If you are not familiar with Espo's file structure you can open the installation in your IDE or use windows explorer to get a better idea of where everything goes.

                          3) Clear cache and rebuild the local installation

                          4) Test

                          5) Make any modifications that you want

                          6) Test again

                          7) Upload each script individually using FTP to the correct folder in your online Espo installation.

                          8) Clear cache and rebuild the online installation

                          9) Test again.
                          Last edited by telecastg; 07-02-2020, 02:34 PM. Reason: Corrected typo in file name PrintToWindowService.php

                          Comment

                          • esforim
                            Active Community Member
                            • Jan 2020
                            • 2204

                            #15
                            Hi telecastg
                            Sorry if I didn't make it clear. The .zip file I upload only contain 4 files, in particular these 4 files:

                            custom/Espo/Custom/Resources/metadata/clientDefs/Contact.json
                            client/custom/src/views/contact/record/detail.js
                            custom/Espo/Custom/EntryPoints/PrintToWindow.php
                            custom/Espo/Custom/Services/PrintToWindow/Service.php

                            It does not contain any other file except for these 4 files in those 4 folders. All file is a copy/paste except for "/Contact.json' which have my icon/colors (looking at my code, I don't think this file affect the error)

                            I just notice one thing that may explain why it not working: your previous post the filename is like this:

                            Code:
                            [B]custom\Espo\Custom\Services\PrintToWindowService.php[/B]
                            But your new post it is this:

                            Code:
                            custom/Espo/Custom/Services/PrintToWindow/Service.php
                            This could explain why? The PrintToWindow is the folder, the Service.php is the file

                            ---

                            Noted, I will give a local server a try in the future but your step:
                            "7) Upload each script individually using FTP to the correct folder in your online Espo installation."

                            This step should not matter in relation to these 4 files I believe, as long as I can extract 4 files to those 4 folders location everything should work as intended?

                            For my first trial, All file was manually created in those folder. I only made the ZIP with the file because may help, maybe somewhere in those file or folder I create was wrong and can't see it. It looked at the filepath and filename and everything seem to be right. My copy/paste not missing starting and ending line either.
                            ---

                            Anyway I go see if my hunches is correct.

                            Update. My hunches is incorrect. Adding the PrintToWindow folder and creating a Service.php file does not change anything. Still getting Error 404.

                            I attached my screenshot, hopefully anyone with eagle eyes might be able to spot the issue. If my ZIP file work for anyone then it mean this is an isolated problem with my system.

                            Click image for larger version  Name:	PrintToWindow error.png Views:	0 Size:	1.32 MB ID:	60098
                            Last edited by esforim; 07-02-2020, 09:08 AM.

                            Comment

                            Working...