Wednesday, October 12, 2022

PDF Generation in Lightning Web Component (LWC) Salesforce

Hello Guys in this post I'll explain you how to generate PDF using LWC.

So to achieve this I am creating one LWC and putting that LWC on the Contact Record Detail page, In LWC I'm showing some contacts fields using the HTML file of LWC. Please make sure you're not using any slds class for styling because those classes wont create any styling in PDF. You have to use normal CSS for styling. In LWC, I have one button and on the click of that button PDF. would generate. I also need one Visualforce Page that will be used to render the PDF.

Check below example with Code

Lightning Web Component Code

pdfGenerationLWC.html

<template>
    <div class="container" style="background: white; margin: 1px; border:1px solid black;">
        <h1>Contact Detail</h1>
        <div style="margin-bottom: 10px;">
            Title : {contact.Title}            
        </div>
        <div style="margin-bottom: 10px;">
            <div>Phone : {contact.Phone}</div>
        </div> 
        <div style="margin-bottom: 10px;">
            Name : {contact.FirstName} {contact.LastName}
        </div>
        <div style="margin-bottom: 10px;">
            <div>Home Phone: {contact.HomePhone}</div>  
        </div>                       
    </div>
    <lightning-button variant="brand" label="Download PDF" onclick={downloadPDF}></lightning-button>
</template>

In JS part of LWC I have created one public property recordId, that will receive contact Id and using this Id I'm making a server call to fetch the contact details, also there is one method downloadPDF that will be called on the click of the button and in that first I am getting all html code using queryselector and passing as string to the visulforce page and visualforce page will show all the html code as pdf

pdfGenerationLWC.js

import { LightningElement, api } from 'lwc';
import getContact from '@salesforce/apex/PDFGenerationLWCController.getContact';

export default class PdfGenerationLWC extends LightningElement {
    @api recordId;
    contact = {}
    connectedCallback(){
       getContact({
           recordId : this.recordId
       })
       .then(result=>{
            this.contact = result;
       })
       .catch(error=>{
           console.error(error);
       })
   }
    downloadPDF(){
        let htmlContent = this.template.querySelector('.container')    
        window.location = "/apex/PDFGenerationPage?pdfHTML="+htmlContent.outerHTML;  
    }
}
pdfGenerationLWC.js-meta.xml

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>55.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__RecordPage</target>
    </targets>
</LightningComponentBundle>

Apex controller to fetch the contact details


public with sharing class PDFGenerationLWCController {
    @AuraEnabled
    public static Contact getContact(String recordId){
        try {
            return [SELECT Title, FirstName, LastName, Phone, Owner.FirstName, HomePhone FROM CONTACT WHERE Id=:recordId];
        } catch (Exception e) {
            throw new AuraHandledException(e.getMessage());
        }
    }    
}

Visualforce page

<apex:page controller="pdfPageController" renderAs="pdf" applyHtmlTag="false" showHeader="false" cache="true" readOnly="true">
    <html>
        <head>
            <meta http-equiv="Content-Type" content="text/html;charset=UTF-8"/>
            <style>
                @page {
                    size: a4 portrait;    
                    padding-left: 2px;    
                    padding-right: 2px;
                }            
            </style>
        </head>
        <apex:outputText value="{!pdfHTML}" escape="false"/>
    </html>

</apex:page>
Visualforce Page Controller

public with sharing class PDFPageController {
    public String pdfHTML{get;set;}
    public pdfPageController() {
        pdfHTML = ApexPages.currentPage().getParameters().get('pdfHTML');
    }
}

Output Screen shots

If you guys have any question, please comment below. I'll be happy to help.

Saturday, June 11, 2022

Lightning Modal/Popup Lightning Web Component(LWC)

Hello Guys, In this post I'll create lightning Modal/Popup in Lightning Web component Salesforce(LWC).

Modals are used to display content in a layer above the app. This paradigm is used in cases such as the creation or editing of a record, as well as various types of messaging and wizards.

Check below example

lightningModalLWC.html

<template>
    <!--Lightning Button which will open Modal on the click of it -->
    <lightning-button variant="brand" label="Open Modal" onclick={openModal}></lightning-button>

    <!--isModalOpen property will be used to hide/show for modal -->
    <template if:true={isModalOpen}>
        <!-- lightning modal Starts here -->
        <section role="dialog" tabindex="-1" aria-modal="true" aria-labelledby="modal-heading-01" class="slds-modal slds-fade-in-open">
            <div class="slds-modal__container">
                <lightning-button-icon icon-name="utility:close"
                                              onclick={closeModal}                                              
                                              variant="bare-inverse"
                                              class="slds-modal__close">
                </lightning-button-icon>
                <div class="slds-modal__header">
                    <h1 id="modal-heading-01" class="slds-modal__title slds-hyphenate">Modal/Popup header LWC</h1>
                </div>
                <div class="slds-modal__content slds-p-around_medium" id="modal-content-id-1">
                    <p>Modal Content would come here</p>
                </div>
                <div class="slds-modal__footer">
                    <button class="slds-button slds-button_neutral" aria-label="Cancel and close" onclick={closeModal}>Cancel</button>
                    <button class="slds-button slds-button_brand" onclick={submit}>Submit</button>
                </div>
            </div>
        </section>
        <div class="slds-backdrop slds-backdrop_open" role="presentation"></div>
        <!-- lightning modal Ends here -->
    </template>
</template>
lightningModalLWC.js

import { LightningElement } from 'lwc';

export default class LightningModalLWC extends LightningElement {
    isModalOpen = false;//property that will be use to hide/show of Lightning Modal

    openModal(){
        this.isModalOpen = true;
    }
    closeModal(){
        this.isModalOpen = false;
    }
    submit(){
        //write some logic
        this.isModalOpen = false;
    }
}
lightningModalLWC.js-meta.xml

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>54.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__HomePage</target>
        <target>lightning__AppPage</target>
        <target>lightning__RecordPage</target>
    </targets>
</LightningComponentBundle>
output

Related Post : Lightning Modal/Popup Lightning Aura Component Salesforce

Lightning Modal/Popup Lightning Aura Component Salesforce

Hello Guys, In this post I'll create lightning Modal in Lightning Aura component Salesforce.

Modals are used to display content in a layer above the app. This paradigm is used in cases such as the creation or editing of a record, as well as various types of messaging and wizards.

Check below example

LightningModalAura.cmp

<aura:component implements="flexipage:availableForAllPageTypes" access="global" >
    <!--Attributes -->
    <aura:attribute name="isModalOpen" type="boolean" default="false"></aura:attribute> 
    
    <!--Lightning Button which will open Modal on the click of it -->
    <lightning:button variant="brand" label="Open Modal" onclick="{!c.openModal}"></lightning:button>
    
    <!--isModalOpen Attribute will be used to hide/show for modal -->
    <aura:if isTrue="{!v.isModalOpen}">
        <!-- lightning modal Starts here -->
        <section role="dialog" tabindex="-1" aria-modal="true" aria-labelledby="modal-heading-01" class="slds-modal slds-fade-in-open">
            <div class="slds-modal__container">
                <lightning:buttonIcon iconName="utility:close"
                                              onclick="{!c.closeModal}"
                                              alternativeText="close"
                                              variant="bare-inverse"
                                              class="slds-modal__close"/>
                <div class="slds-modal__header">
                    <h1 id="modal-heading-01" class="slds-modal__title slds-hyphenate">Modal header</h1>
                </div>
                <div class="slds-modal__content slds-p-around_medium" id="modal-content-id-1">
                    <p>Modal Content would come here</p>
                </div>
                <div class="slds-modal__footer">
                    <button class="slds-button slds-button_neutral" aria-label="Cancel and close" onclick="{!c.closeModal}">Cancel</button>
                    <button class="slds-button slds-button_brand" onclick="{!c.submit}">Submit</button>
                </div>
            </div>
        </section>
        <div class="slds-backdrop slds-backdrop_open" role="presentation"></div>
        <!-- lightning modal Ends here -->
    </aura:if>
</aura:component>
LightningModalAuraController.js

({
	openModal : function(component, event, helper) {
		component.set("v.isModalOpen", true);
	},
    closeModal : function(component, event, helper) {
        component.set("v.isModalOpen", false);		
	},
    submit : function(component, event, helper) {
        //write some logic
		component.set("v.isModalOpen", false);	
	},
})
output

Related Post : Lightning Modal/Popup Lightning Web Component(LWC)

Wednesday, June 8, 2022

Lightning combobox to display State and City in LWC also load with Default value

Hello Guys, In this post we would learn how can we populate State values in Lightning Combobox and on the selection of State, Cities should be populated in another combobox.

To Understand this scenario, I have created two master objects in Salesforce 1. State ( having States records) 2. City which has master detail to State object and having cities records. I have created two text field State and City on Contact object to store the values.

Our stateCity component has been placed on Contact Detail page it has one update button so whenever user select States, automatically cities belong to that state would come up in City Combobox and after the selection of City, once user clicks on Update button both State and City values will be saved in Contact object in State and City fields respectivily.

If user comes on the detail page later and State and City fields on the Contact object are already has values so on load of the component the default values would be shown to the user.

See the full code below

Apex Class


public class StateCityController {
    @AuraEnabled
    public static ContactStateCityWrapper getStateCities(String recordId){  
        ContactStateCityWrapper obj = new ContactStateCityWrapper();
        List<State__c> stateList = [select Name, (select Name from cities__r order by Name) from state__c order by Name ];
        Contact con = [SELECT State__c, City__c FROM Contact WHERE Id =: recordId];
        
        obj.stateCityList = stateList;
        obj.contact = con;
        return obj;
    }
    public Class ContactStateCityWrapper{
        @AuraEnabled public Contact contact;
        @AuraEnabled public List<State__c> stateCityList;
    }
    @AuraEnabled
    public static String handleUpdate(String contactDetails){
        Contact conObj = (Contact) JSON.deserialize(contactDetails, Contact.class);
        
        update conObj;
        return conObj.Id;
    }
}

stateCity.html


<template>
    <lightning-card title="Update State and City">
        <div class="slds-grid slds-no-flex slds-var-p-around_medium	">
            <div class="slds-col slds-size_2-of-12">State</div>
            <div class="slds-col slds-size_2-of-12">
                <lightning-combobox variant="label-hidden"
                                name="state"                                                                                
                                placeholder="Select State"
                                options={states}
                                onchange={onStateChange} 
                                data-id="state">
                </lightning-combobox>                   
            </div>
            <div class="slds-col slds-size_1-of-12"></div>
            <div class="slds-col slds-size_2-of-12">City</div>
            <div class="slds-col slds-size_2-of-12">
                <lightning-combobox variant="label-hidden"
                            name="city"                                                                        
                            placeholder="Select City"
                            options={cities}
                            onchange={onCityChange}
                            data-id="city" >
                </lightning-combobox>
            </div>
        </div> 
    
    <div class="slds-grid slds-no-flex slds-var-p-around_medium	">
        <div class="slds-col slds-size_12-of-12 slds-align_absolute-center">
            <lightning-button variant="brand" style="width: 6rem;" label="Update" title="Update" onclick={handleUpdate} class=""></lightning-button>            
        </div>
    </div>
</lightning-card>
</template>

stateCity.js


import { LightningElement, api } from 'lwc';
import getStateCities from '@salesforce/apex/StateCityController.getStateCities';
import handleUpdate from '@salesforce/apex/StateCityController.handleUpdate';

export default class StateCity extends LightningElement {
    @api recordId;
    error;
    states;
    cities;
    stateCityMap = {};
    stateIdNameMap = {};
    cityIdNameMap = {};
    selectedStateName
    selectedCityName
    connectedCallback(){
        console.log('## '+this.recordId)
        getStateCities({
            recordId : this.recordId
        })
        .then(result =>{
            let stateOptions = [];                        
            let cityMapLocal = {}; 
            let stateIdNameMapLocal = {};
            let cityIdNameMapLocal = {};
            
            let stateNameIdMapLocal = {};
            let cityNameIdMapLocal = {};

            console.log('## '+JSON.stringify(result))
            //Population of State and City List
            result.stateCityList.forEach(function(index,value){
                if(index.Cities__r){
                    let cities = index.Cities__r.map(function(data){  
                        cityIdNameMapLocal[data.Id] = data.Name;
                        cityNameIdMapLocal[data.Name] = data.Id;                                              
                        return data;
                    });
                    cityMapLocal[index.Id] = cities;
                }else{
                    cityMapLocal[index.Id] = [];
                } 
                stateIdNameMapLocal[index.Id] = index.Name.toUpperCase(); 
                stateNameIdMapLocal[index.Name.toUpperCase()] = index.Id;              
                stateOptions.push({label:index.Name.toUpperCase(), value:index.Id});                
            }) 
            this.stateIdNameMap = stateIdNameMapLocal;
            this.cityIdNameMap = cityIdNameMapLocal;
            
            this.states = stateOptions;
            this.stateCityMap = cityMapLocal;
            console.log('## 2')
            if(result.contact.State__c != null && result.contact.City__c != null){
                this.template.querySelector('lightning-combobox[data-id="state"]').value = stateNameIdMapLocal[result.contact.State__c];
                this.template.querySelector('lightning-combobox[data-id="city"]').value = cityNameIdMapLocal[result.contact.City__c];
            
                //to populate the City Dropdown based on the State
                let cityOptions = [];
                let stateCityMap = JSON.parse(JSON.stringify(this.stateCityMap));
                let cityList = stateCityMap[stateNameIdMapLocal[result.contact.State__c]];
                if(cityList.length > 0){
                    cityList.forEach(function(index,value){
                        cityOptions.push({label: index.Name, value: index.Id});
                    })
                }
                this.cities = cityOptions;
            }
            console.log('##22')
        })
        .catch(error =>{
            this.error = error;
        })
    }
    onStateChange(event){
        let cityOptions = [];        
        let selectedStateId = event.target.value;
        let stateCityMap = JSON.parse(JSON.stringify(this.stateCityMap));
        let cityList = stateCityMap[selectedStateId];
        if(cityList.length > 0){
            cityList.forEach(function(index,value){
                cityOptions.push({label: index.Name, value: index.Id});
            })
        }
        this.cities = cityOptions;        
        this.selectedStateName = this.stateIdNameMap[selectedStateId];
    }
    onCityChange(event){        
        this.selectedCityName = this.cityIdNameMap[event.target.value];
    }
    handleUpdate(){
        let contact = {
            Id : this.recordId,
            State__c: this.selectedStateName,
            City__c : this.selectedCityName
        }

        handleUpdate({
            contactDetails : JSON.stringify(contact)
        })
        .then(result =>{
            alert('Successfully Updated!')
        })
        .catch(error =>{
            this.error = error
        })
    }
}

stateCity.js-meta.xml


<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>54.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__AppPage</target>
        <target>lightning__HomePage</target>
        <target>lightning__RecordPage</target>
    </targets>
</LightningComponentBundle>

Output

Below image shows the State and City values populating as default on page load from Contact record.

Please comment below, if you guys have any quesiton. I'll be happy to answer. Thanks!

Monday, May 16, 2022

Lightning Data Table to Show Parent object data along with Count of the child in LWC Salesforce

Hello Guys, in this post we would learn how can we show Parent object data along with no of child in one column in Lightning data table in Lightning Web Component(LWC).

To understand this scenario, I have taken Account as a parent object and contact as a child object and in the Lightning Data table we would display few Account fields along with No of Contacts

Please check the full code below

Apex Class

public class LightningDataExampleController {
	@AuraEnabled
    public static List getAccounts(){
        return [SELECT Id, Name, AccountNumber, Site,
                (SELECT Id FROM Contacts) 
                FROM Account 
                LIMIT 5];
    }
}
lightningDataTableExample.html

<template>
    <lightning-card title="Lightning Data Table">        
        <div class="slds-grid slds-no-flex slds-var-p-around_medium	">
            <div class="slds-col slds-size_12-of-12">
                <lightning-datatable key-field="Id"
                                    data={accounts}
                                    columns={columns}                                    
                                    hide-checkbox-column
                                    >
                </lightning-datatable>                            
            </div>
        </div>
    </lightning-card>    
</template>
lightningDataTableExample.js

import { LightningElement } from 'lwc';
import getAccounts from '@salesforce/apex/LightningDataExampleController.getAccounts';

export default class LightningDataTableExample extends LightningElement {
    columns = [
        {label: 'Name', fieldName: 'Name', type: 'text', sortable: false},
        {label: 'Account Number', fieldName: 'AccountNumber', type: 'text', sortable: false},
        {label: 'Site', fieldName: 'Site', type: 'text', sortable: false},
        {label: 'No of Contacts', fieldName: 'noOfContacts', type: 'text', sortable: false},
    ];

    accounts = [];
    error;

    connectedCallback(){
        this.getAccounts();
    }
    getAccounts(){
        getAccounts()
        .then(result =>{            
            result.forEach(element => {
                let noOfContacts = 0;
                if(element.Contacts){
                    element.Contacts.forEach(con =>{
                        noOfContacts++;
                    })
                }
                element.noOfContacts = noOfContacts;
            });
            this.accounts = result;            
        })
        .catch(error =>{
            this.error = error;
        })
    }
}
lightningDataTableExample.js-meta.xml

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>54.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__AppPage</target>
        <target>lightning__RecordPage</target>
        <target>lightning__HomePage</target>
    </targets>
</LightningComponentBundle>

Please comment below, if you guys have any quesiton. I'll be happy to answer. Thanks!

Related Posts
Add Delete rows dynamically in Lightning Data Table in LWC Salesforce

Sunday, May 15, 2022

Add Delete rows dynamically in Lightning Data Table in LWC Salesforce

Hello Guys, in this post we'll learn how to add delete rows dynamically in Lightning Data table in Lightning Web Components(LWC) in Salesforce with the proper Example.

Apex Class

public class AddDeleteRowLWCController {

    @AuraEnabled
    public static List getAccounts(){
        return [SELECT Id, Name, AccountNumber, Site FROM Account LIMIT 5];
    }
    @AuraEnabled
    public static Account addAccount(String account){
        Account accObj = (Account) System.JSON.deserialize(account, Account.class);
        insert accObj;
        return accObj;
    }
}
addDeleteRowLWC.html

<template>
    <lightning-card title="">
        <lightning-button variant="brand" label="Add Account" onclick={openAddAccountModal} ></lightning-button>
        <div class="slds-grid slds-no-flex slds-var-p-around_medium	">
            <div class="slds-col slds-size_12-of-12">
                <lightning-datatable key-field="Id"
                                    data={accountData}
                                    columns={columns}
                                    onrowaction={handleRowAction}
                                    hide-checkbox-column
                                    >
                </lightning-datatable>                            
            </div>
        </div>
    </lightning-card>

    <template if:true={isModalOpen}>
        <!-- Modal/Popup Box LWC starts here -->
        <section role="dialog" tabindex="-1" aria-labelledby="modal-heading-01" aria-modal="true" aria-describedby="modal-content-id-1" class="slds-modal slds-fade-in-open">
            <div class="slds-modal__container">
                <!-- Modal/Popup Box LWC header here -->
                <header class="slds-modal__header">
                    <button class="slds-button slds-button_icon slds-modal__close slds-button_icon-inverse" title="Close" onclick={closeModal}>
                        <lightning-icon icon-name="utility:close"
                            alternative-text="close"
                            variant="inverse"
                            size="small" ></lightning-icon>
                        <span class="slds-assistive-text">Close</span>
                    </button>
                    <h2 id="modal-heading-01" class="slds-text-heading_medium slds-hyphenate">Add Account</h2>
                </header>
                <!-- Modal/Popup Box LWC body starts here -->
                <div class="slds-modal__content slds-p-around_medium" id="modal-content-id-1">
                    <lightning-input name="accountName" type="text"  label="Name" value={accountName} onchange={handleChange}></lightning-input>                    
                    <lightning-input name="accountNumber" type="Integer" label="AccountNumber" value={accountNumber} onchange={handleChange}></lightning-input>                    
                    <lightning-input name="accountSite" type="text" label="Site" value={accountSite} onchange={handleChange}></lightning-input>                    

                </div>
                <!-- Modal/Popup Box LWC footer starts here -->
                <footer class="slds-modal__footer">
                    <button class="slds-button slds-button_neutral" onclick={closeModal} title="Cancel">Cancel</button>
                    <button class="slds-button slds-button_brand" onclick={addAccount} title="Add Account">Add Account</button>
                </footer>
            </div>
        </section>
        <div class="slds-backdrop slds-backdrop_open"></div>
    </template>

</template>
addDeleteRowLWC.js

import { LightningElement } from 'lwc';
import getAccounts from '@salesforce/apex/AddDeleteRowLWCController.getAccounts';
import addAccount from '@salesforce/apex/AddDeleteRowLWCController.addAccount';

export default class AddDeleteRowLWC extends LightningElement {
    columns = [
        {label: 'Name', fieldName: 'Name', type: 'text', sortable: true},
        {label: 'Account Number', fieldName: 'AccountNumber', type: 'text', sortable: false},
        {label: 'Site', fieldName: 'Site', type: 'text', sortable: true},
        {type: "button-icon", typeAttributes: {iconName: "utility:delete", name: "delete", iconClass: "slds-icon-text-error"},fixedWidth: 50}     
    ];

    accountData = [];
    isModalOpen = false;
    accountName= '';
    accountNumber = '';
    accountSite = '';

    connectedCallback(){
        this.getAccounts();
    }
    getAccounts(){
        getAccounts()
        .then(result =>{
            this.accountData = result;
        })
        .catch(error =>{

        })
    }

    handleRowAction(event) {
		if (event.detail.action.name === "delete") {
			this.deleteSelectedRow(event.detail.row);
		}
	}
    deleteSelectedRow(deleteRow) {
		let newData = JSON.parse(JSON.stringify(this.accountData));
		newData = newData.filter((row) => row.Id !== deleteRow.Id);
        this.accountData = newData;		
	}
    openAddAccountModal(){
        this.isModalOpen = true;
    }
    closeModal() {        
        this.isModalOpen = false;
    }
    handleChange(event){
        if(event.target.name == 'accountName'){
            this.accountName = event.target.value;
        }else if(event.target.name == 'accountNumber'){
            this.accountNumber = event.target.value;
        }else if(event.target.name == 'accountSite'){
            this.accountSite = event.target.value;
        }
    }
    addAccount(){
        let accountToInsert = {
            Name : this.accountName,
            AccountNumber: this.accountNumber,
            Site : this.accountSite
        }
        addAccount({
            account: JSON.stringify(accountToInsert)
        })
        .then(result =>{            
            this.addAccountToList(result)
        })
        .catch(error =>{
            this.error = error;
        })        
    }
    addAccountToList(account){
        let localList;            
        if(this.accountData.length > 0){                        
            localList =JSON.parse(JSON.stringify(this.accountData));                
        }else{
            localList = [];                
        }
         
        localList.push({
            Id: account.Id,
            Name : account.Name,
            AccountNumber : account.AccountNumber,
            Site : account.Site,            
        })                                        
        this.accountData = localList;
        this.isModalOpen = false;    
    }
}
addDeleteRowLWC.js-meta.xml

<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
    <apiVersion>54.0</apiVersion>
    <isExposed>true</isExposed>
    <targets>
        <target>lightning__AppPage</target>
        <target>lightning__RecordPage</target>
        <target>lightning__HomePage</target>
    </targets>
</LightningComponentBundle>
Output

If you guys have any question, please comment below, I'll be happy to answer your question.
#LWC, #LightningDataTable, #adddeleterowinlwc #salesforce #salesforcelightning

Related Posts
Lightning Data Table to Show Parent object data along with Count of the child in LWC Salesforce

Friday, May 13, 2022

Subscript is invalid because list is empty | Error in VF Page | Salesforce

Subscript is invalid because list is empty this error is comes on VF page if we are trying to access the list element on VF Page and in the apex the list has no rows.

Below is the example 

Here is my Apex Class

public Class myClass {

    public Account account {get;set;} 

public myClass(){

        recordId = System.currentPageReference().getParameters().get('recordId');

        this.account = [SELECT Id, Name,(select id,LastName FROM Contacts) FROM Account WHERE Id =:recordId];                

    }

}


VF Page

<apex:page renderAs="pdf" applyBodyTag="false" controller="myClass" >

<div class="header">

        <table>         

            <tr>

                <td style="background: #D6EEEE"><b>Account Name</b></td>   

                <td>{!account.Name}</td> 

                <td style="background: #D6EEEE"><b>Contact Last Name</b></td>  

                <td>{!account.Contacts[0].LastName}</td> 

        </tr>

    </table>



In order to solve this error we'll have to modify our code as below

public Class myClass {

    public Account account {get;set;} 

    public String contactLastName {get;set;}

public myClass(){

        recordId = System.currentPageReference().getParameters().get('recordId');

        contactLastName = '';

        this.account = [SELECT Id, Name,(select id,LastName FROM Contacts) FROM Account WHERE Id =:recordId];           

    if(this.account.contacts.size() >0){

        contactLastName = this.account.contacts[0].LastName;

    }     

    }

}


VF Page

<apex:page renderAs="pdf" applyBodyTag="false" controller="myClass" >

<div class="header">

        <table>         

            <tr>

                <td style="background: #D6EEEE"><b>Account Name</b></td>   

                <td>{!account.Name}</td> 

                <td style="background: #D6EEEE"><b>Contact Last Name</b></td>  

                <td><!--{!account.Contacts[0].Name}-->{!contactLastName}</td> 

        </tr>

    </table>


Hope this will solve your problem, Please comment below if you have any question. I'll be happy to help.