├── .forceignore ├── force.json ├── .DS_Store ├── src ├── .DS_Store ├── lwc │ ├── .eslintrc.json │ ├── upload │ │ ├── upload.js-meta.xml │ │ ├── upload.html │ │ └── upload.js │ ├── datatable │ │ ├── datatable.js-meta.xml │ │ ├── datatable.html │ │ └── datatable.js │ └── jsconfig.json ├── classes │ ├── datatableController.cls-meta.xml │ ├── uploadController.cls-meta.xml │ ├── uploadController.cls │ └── datatableController.cls └── package.xml ├── .sfdx ├── .forceCode └── sasank.github@gmail.com │ ├── .sfdx │ ├── sfdx-config.json │ ├── tools │ │ └── apex.db │ └── typings │ │ └── lwc │ │ ├── user.d.ts │ │ ├── apex │ │ ├── datatableController.d.ts │ │ └── uploadController.d.ts │ │ ├── schema.d.ts │ │ ├── apex.d.ts │ │ ├── engine.d.ts │ │ └── lds.d.ts │ ├── sfdx-project.json │ └── settings.json ├── .vscode └── settings.json ├── sfdx-project.json └── README.md /.forceignore: -------------------------------------------------------------------------------- 1 | **/jsconfig.json 2 | 3 | **/.eslintrc.json 4 | -------------------------------------------------------------------------------- /force.json: -------------------------------------------------------------------------------- 1 | { 2 | "lastUsername": "sasank.github@gmail.com" 3 | } -------------------------------------------------------------------------------- /.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sasank-sfdcdev/public/HEAD/.DS_Store -------------------------------------------------------------------------------- /src/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sasank-sfdcdev/public/HEAD/src/.DS_Store -------------------------------------------------------------------------------- /src/lwc/.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "@salesforce/eslint-config-lwc/recommended" 3 | } 4 | -------------------------------------------------------------------------------- /.sfdx: -------------------------------------------------------------------------------- 1 | /Users/sasanksubrahmanyamvaranasi/Documents/GitHub/public/.forceCode/sasank.github@gmail.com/.sfdx -------------------------------------------------------------------------------- /.forceCode/sasank.github@gmail.com/.sfdx/sfdx-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultusername": "sasank.github@gmail.com" 3 | } -------------------------------------------------------------------------------- /.forceCode/sasank.github@gmail.com/.sfdx/tools/apex.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sasank-sfdcdev/public/HEAD/.forceCode/sasank.github@gmail.com/.sfdx/tools/apex.db -------------------------------------------------------------------------------- /.forceCode/sasank.github@gmail.com/.sfdx/typings/lwc/user.d.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * User's Id. 3 | */ 4 | declare module "@salesforce/user/Id" { 5 | const id: string; 6 | export default id; 7 | } 8 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.nodePath": "/Users/sasanksubrahmanyamvaranasi/.vscode/extensions/salesforce.salesforcedx-vscode-lwc-46.7.0/node_modules", 3 | "workbench.colorCustomizations": {} 4 | } -------------------------------------------------------------------------------- /src/classes/datatableController.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 46.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/classes/uploadController.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 45.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/lwc/upload/upload.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 45.0 4 | false 5 | -------------------------------------------------------------------------------- /src/lwc/datatable/datatable.js-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 45.0 4 | false 5 | -------------------------------------------------------------------------------- /sfdx-project.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespace": "", 3 | "packageDirectories": [ 4 | { 5 | "path": "src", 6 | "default": true 7 | } 8 | ], 9 | "sfdcLoginUrl": "https://login.salesforce.com", 10 | "sourceApiVersion": "46.0" 11 | } -------------------------------------------------------------------------------- /.forceCode/sasank.github@gmail.com/sfdx-project.json: -------------------------------------------------------------------------------- 1 | { 2 | "namespace": "", 3 | "packageDirectories": [ 4 | { 5 | "path": "src", 6 | "default": true 7 | } 8 | ], 9 | "sfdcLoginUrl": "https://login.salesforce.com", 10 | "sourceApiVersion": "46.0" 11 | } -------------------------------------------------------------------------------- /src/lwc/jsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "paths": {}, 5 | "experimentalDecorators": true 6 | }, 7 | "include": [ 8 | "**/*", 9 | "../../.sfdx/typings/lwc/**/*.d.ts" 10 | ], 11 | "typeAcquisition": { 12 | "include": [ 13 | "jest" 14 | ] 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /.forceCode/sasank.github@gmail.com/.sfdx/typings/lwc/apex/datatableController.d.ts: -------------------------------------------------------------------------------- 1 | declare module "@salesforce/apex/datatableController.fetchDataMapCached" { 2 | export default function fetchDataMapCached(param: {params: any}): Promise; 3 | } 4 | declare module "@salesforce/apex/datatableController.fetchDataMap" { 5 | export default function fetchDataMap(param: {params: any}): Promise; 6 | } 7 | -------------------------------------------------------------------------------- /src/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | upload 5 | datatable 6 | LightningComponentBundle 7 | 8 | 9 | datatableController 10 | uploadController 11 | ApexClass 12 | 13 | 46.0 14 | -------------------------------------------------------------------------------- /.forceCode/sasank.github@gmail.com/.sfdx/typings/lwc/apex/uploadController.d.ts: -------------------------------------------------------------------------------- 1 | declare module "@salesforce/apex/uploadController.getDocumentLinks" { 2 | export default function getDocumentLinks(param: {docId: any}): Promise; 3 | } 4 | declare module "@salesforce/apex/uploadController.deleteDocuments" { 5 | export default function deleteDocuments(param: {docIds: any}): Promise; 6 | } 7 | declare module "@salesforce/apex/uploadController.createDocumentLink" { 8 | export default function createDocumentLink(param: {params: any}): Promise; 9 | } 10 | -------------------------------------------------------------------------------- /.forceCode/sasank.github@gmail.com/.sfdx/typings/lwc/schema.d.ts: -------------------------------------------------------------------------------- 1 | declare module "@salesforce/schema" { 2 | /** 3 | * Identifier for an object. 4 | */ 5 | export interface ObjectId { 6 | /** The object's API name. */ 7 | objectApiName: string; 8 | } 9 | 10 | /** 11 | * Identifier for an object's field. 12 | */ 13 | export interface FieldId { 14 | /** The field's API name. */ 15 | fieldApiName: string; 16 | /** The object's API name. */ 17 | objectApiName: string; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /.forceCode/sasank.github@gmail.com/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "alias": "", 3 | "apiVersion": "46.0", 4 | "deployOptions": { 5 | "allowMissingFiles": true, 6 | "checkOnly": false, 7 | "ignoreWarnings": true, 8 | "purgeOnDelete": false, 9 | "rollbackOnError": true, 10 | "runTests": [], 11 | "singlePackage": true, 12 | "testLevel": "NoTestRun" 13 | }, 14 | "overwritePackageXML": false, 15 | "poll": 2000, 16 | "pollTimeout": 1200, 17 | "prefix": "", 18 | "showTestCoverage": true, 19 | "spaDist": "", 20 | "src": "src", 21 | "staticResourceCacheControl": "Private", 22 | "url": "https://login.salesforce.com", 23 | "autoCompile": false, 24 | "username": "sasank.github@gmail.com" 25 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # public 2 | Generic components 3 | 4 | Currently there are 2 generic components (PFB). Very soon I will add DEMO components which will show how to use them. 5 | 6 | 1. datatable: Generic datatable which uses standard lightning-datatable for showing records in table
7 | 1. Gets data from database automatically. Can use relationship fields also.
8 | 2. Sort functionality
9 | 3. Pagination - first, previous, next and last pages
10 | 4. Persistant selection of records across pages. getSelectedRows public method to get selected data.
11 | 5. All events of lightning-datatable plus event while loading data
12 | 6. Cacheable data
13 | 7. Sosl search
14 | 8. Dynamically change data filters
15 | 16 | 2. upload: Currently implemented only for admins (without shareable)
17 | 1. Use component in create form wherein, the file has to be uploaded before record (like account or case etc) is created.
18 | 2. Share records with other users
19 | 3. Search for files
20 | 4. Delete files
21 | 5. Used datatable for showing records which gives all the functionality like sort, pagination etc
22 | 23 | 24 | -------------------------------------------------------------------------------- /.forceCode/sasank.github@gmail.com/.sfdx/typings/lwc/apex.d.ts: -------------------------------------------------------------------------------- 1 | declare module "@salesforce/apex" { 2 | /** 3 | * Identifier for an object's field. 4 | */ 5 | export interface FieldId { 6 | /** The field's API name. */ 7 | fieldApiName: string; 8 | /** The object's API name. */ 9 | objectApiName: string; 10 | } 11 | 12 | /** 13 | * Services for Apex. 14 | */ 15 | export interface ApexServices { 16 | /** 17 | * Refreshes a property annotated with @wire. Queries the server for updated data and refreshes the cache. 18 | * @param wiredTargetValue A property annotated with @wire. 19 | * @returns Promise that resolves to the refreshed value. If an error occurs, the promise is rejected. 20 | */ 21 | refreshApex: (wiredTargetValue: any) => Promise; 22 | 23 | /** 24 | * Gets a field value from an Apex sObject. 25 | * @param sObject The sObject holding the field. 26 | * @param field The field to return. 27 | * @returns The field's value. If it doesn't exist, undefined is returned. 28 | */ 29 | getSObjectValue: (sObject: object, field: string | FieldId) => any; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/lwc/upload/upload.html: -------------------------------------------------------------------------------- 1 | 14 | -------------------------------------------------------------------------------- /src/classes/uploadController.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * @File Name : uploadController.cls 3 | * @Description : 4 | * @Author : Sasank Subrahmanyam V 5 | * @Group : 6 | * @Last Modified By : Sasank Subrahmanyam V 7 | * @Last Modified On : 8/3/2019, 4:15:11 PM 8 | * @Modification Log : 9 | *============================================================================== 10 | * Ver Date Author Modification 11 | *============================================================================== 12 | * 1.0 5/22/2019, 2:16:19 PM Sasank Subrahmanyam V Initial Version 13 | **/ 14 | public without sharing class uploadController { 15 | 16 | @AuraEnabled 17 | public static List getDocumentLinks(String docId){ 18 | system.debug('docId => '+docId); 19 | List rlinks = new List(); 20 | List links = [ 21 | SELECT LinkedEntityId, LinkedEntity.Name FROM ContentDocumentLink WHERE ContentDocumentId=:docId 22 | ]; 23 | system.debug('links => '+links); 24 | for(ContentDocumentLink cdl: links){ 25 | rlinks.add(new Link(cdl, String.valueOf(cdl.LinkedEntityId.getSObjectType()))); 26 | } 27 | 28 | return rlinks; 29 | } 30 | 31 | @AuraEnabled 32 | public static String deleteDocuments(List docIds){ 33 | try{ 34 | delete [SELECT Id FROM ContentDocument WHERE Id=:docIds]; 35 | return 'SUCCESS'; 36 | } 37 | catch(Exception ex){ 38 | throw new AuraHandledException(ex.getMessage()); 39 | } 40 | } 41 | 42 | class Link{ 43 | @AuraEnabled 44 | public ContentDocumentLink link; 45 | @AuraEnabled 46 | public String objectType; 47 | Link(ContentDocumentLink cdl, String ot){ 48 | this.Link = cdl; 49 | this.ObjectType = ot; 50 | } 51 | } 52 | 53 | @AuraEnabled 54 | public static List createDocumentLink(Map params) { 55 | system.debug('params => '+params); 56 | List files = (List)params.get('files'); 57 | String recordId = (String)params.get('recordId'); 58 | List cdLinks = new List(); 59 | 60 | for(Object file : files) { 61 | Map fileMap = (Map)file; 62 | cdLinks.add(new ContentDocumentLink( 63 | LinkedEntityId=recordId, 64 | ContentDocumentId=(String)fileMap.get('documentId'), 65 | ShareType='V', 66 | Visibility='InternalUsers' 67 | )); 68 | } 69 | 70 | insert cdLinks; 71 | return cdLinks; 72 | } 73 | 74 | } -------------------------------------------------------------------------------- /src/classes/datatableController.cls: -------------------------------------------------------------------------------- 1 | /** 2 | * @File Name : datatableController.cls 3 | * @Description : 4 | * @Author : Sasank Subrahmanyam V 5 | * @Group : 6 | * @Last Modified By : Sasank Subrahmanyam V 7 | * @Last Modified On : 8/4/2019, 10:17:06 AM 8 | * @Modification Log : 9 | *============================================================================== 10 | * Ver Date Author Modification 11 | *============================================================================== 12 | * 1.0 8/1/2019, 1:50:15 PM Sasank Subrahmanyam V Initial Version 13 | **/ 14 | public with sharing class datatableController { 15 | 16 | @AuraEnabled(Cacheable=true) 17 | public static Map fetchDataMapCached(Map params) { 18 | return fetchDataMap(params); 19 | } 20 | 21 | @AuraEnabled 22 | public static Map fetchDataMap(Map params) { 23 | 24 | String objectName = params.containsKey('objectName') ? (String)params.get('objectName') : null; 25 | String fields = params.containsKey('fields') ? (String)params.get('fields') : null; 26 | String queryFilters = params.containsKey('queryFilters') ? (String)params.get('queryFilters') : null; 27 | String sortBy = params.containsKey('sortBy') ? (String)params.get('sortBy') : null; 28 | String queryType = params.containsKey('queryType') ? (String)params.get('queryType') : null; 29 | String soslSearchTerm = params.containsKey('soslSearchTerm') ? (String)params.get('soslSearchTerm') : null; 30 | Boolean sortAsc = params.containsKey('sortAsc') ? (Boolean)params.get('sortAsc') : false; 31 | Integer limitRecords = params.containsKey('limitRecords') ? Integer.valueOf(params.get('limitRecords')) : null; 32 | 33 | try{ 34 | //Initial checks 35 | String limitRecordsStr = String.valueOf(Integer.valueOf(limitRecords)); 36 | 37 | //Declare query string 38 | String query; 39 | 40 | //Query initialization for Soql and Sosl 41 | if(queryType == 'SOQL'){ 42 | query = 'SELECT Id, ' + fields + ' FROM ' + objectName; 43 | } 44 | else if(queryType == 'SOSL') { 45 | query = 'Id, ' + fields; 46 | } 47 | 48 | //Adding filters 49 | if(String.isNotBlank(queryFilters)){ 50 | query += ' WHERE ' + queryFilters; 51 | } 52 | 53 | //Adding order by and limit records 54 | if(String.isNotBlank(sortBy) && queryType == 'SOQL'){ 55 | query += ' ORDER BY ' + sortBy + (sortAsc?' ASC ':' DESC '); 56 | } 57 | 58 | if(String.isNotBlank(limitRecordsStr)) { 59 | query += ' LIMIT ' + limitRecordsStr; 60 | } 61 | 62 | //Log the query before getting query results from database 63 | Map returnMap = new Map(); 64 | List sObjectsList = new List(); 65 | if(queryType == 'SOQL'){ 66 | system.debug('query => '+query); 67 | sObjectsList = Database.query(query); 68 | } 69 | else if(queryType == 'SOSL') { 70 | query = 'FIND \'' + String.escapeSingleQuotes(soslSearchTerm) + '\' IN ALL FIELDS RETURNING ' + objectName + '(' + query + ')'; 71 | system.debug('query => '+query); 72 | sObjectsList = Search.query(query)[0]; 73 | } 74 | 75 | returnMap.put('records', sObjectsList); 76 | 77 | //Log the result 78 | system.debug('returnMap => '+returnMap); 79 | 80 | return returnMap; 81 | } 82 | catch(Exception ex) { 83 | system.debug('Error => '+ex.getMessage()); 84 | throw new AuraHandledException(ex.getMessage()); 85 | } 86 | } 87 | 88 | } -------------------------------------------------------------------------------- /src/lwc/upload/upload.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @File Name : upload.js 3 | * @Description : 4 | * @Author : Sasank Subrahmanyam V 5 | * @Group : 6 | * @Last Modified By : Sasank Subrahmanyam V 7 | * @Last Modified On : 8/3/2019, 4:10:04 PM 8 | * @Modification Log : 9 | *============================================================================== 10 | * Ver Date Author Modification 11 | *============================================================================== 12 | * 1.0 5/18/2019, 12:31:12 AM Sasank Subrahmanyam V Initial Version 13 | **/ 14 | import { LightningElement, api, track } from 'lwc'; 15 | import userId from '@salesforce/user/Id'; 16 | import { ShowToastEvent } from 'lightning/platformShowToastEvent'; 17 | import createDocumentLinks from '@salesforce/apex/uploadController.createDocumentLink'; 18 | import getDocumentLinks from '@salesforce/apex/uploadController.getDocumentLinks'; 19 | import deleteDocuments from '@salesforce/apex/uploadController.deleteDocuments'; 20 | 21 | export default class Upload extends LightningElement { 22 | @api label; 23 | @api multiple; 24 | @api accept; 25 | @api parentId; 26 | 27 | @track queryFilters = ""; 28 | 29 | files = []; 30 | 31 | @track documentsConfig = { 32 | objectName: "ContentDocument", 33 | tableConfig: { 34 | columns: [ 35 | { api: 'Title', label: 'Title', fieldName: 'Title', sortable: true }, 36 | { api: 'ContentSize', label: 'Size (bytes)', fieldName: 'ContentSize', type: 'number', sortable: true, callAttributes: { alignment: 'left' } }, 37 | { api: 'FileType', label: 'File Type', fieldName: 'FileType', sortable: true }, 38 | { api: 'Owner.Name', label: 'Owner', fieldName: 'OwnerName', sortable: true }, 39 | { label: '#', type: 'button-icon', typeAttributes: { name: 'delete', iconName: 'utility:delete', variant: 'bare' } } 40 | ], 41 | hideCheckboxColumn: true 42 | }, 43 | sortBy: 'CreatedDate', 44 | queryFilters: (this.parentId ? (` ParentId='${this.parentId}' `) : ''), 45 | pageSize: '5', 46 | limit: '100' 47 | }; 48 | 49 | connectedCallback() { 50 | this.uploadToId = this.parentId || userId; 51 | } 52 | 53 | handleRowAction = event => { 54 | console.log(JSON.stringify(event.detail)); 55 | this.template.querySelector("[data-id=spinner]").classList.remove('slds-hide'); 56 | deleteDocuments({ docIds: [event.detail.row.Id] }) 57 | .then(response => { 58 | this.dispatchEvent( 59 | new ShowToastEvent({ 60 | title: 'Success', 61 | message: 'Successfully deleted!', 62 | variant: 'success' 63 | }) 64 | ); 65 | this.template.querySelector("[data-id=spinner]").classList.add('slds-hide'); 66 | this.template.querySelector('c-datatable[data-id=documents]').refresh(); 67 | }) 68 | .catch(error => { 69 | this.dispatchEvent( 70 | new ShowToastEvent({ 71 | title: 'Error', 72 | message: error, 73 | variant: 'error' 74 | }) 75 | ); 76 | this.template.querySelector("[data-id=spinner]").classList.add('slds-hide'); 77 | }); 78 | } 79 | 80 | @api 81 | uploadToRecord(recordId) { 82 | const params = { 83 | files: this.files, 84 | recordId: recordId 85 | }; 86 | 87 | createDocumentLinks({ 88 | params: params 89 | }) 90 | .then(() => { 91 | this.dispatchEvent( 92 | new ShowToastEvent({ 93 | title: 'Success', 94 | message: 'File(s) shared', 95 | variant: 'success' 96 | }) 97 | ); 98 | }) 99 | .catch((error) => { 100 | this.message = 'Error received: code' + error.errorCode + ', ' + 'message ' + error.body.message; 101 | this.dispatchEvent( 102 | new ShowToastEvent({ 103 | title: 'Error uploading file(s)', 104 | message: this.message, 105 | variant: 'error' 106 | }) 107 | ); 108 | }); 109 | } 110 | 111 | shareFiles() { 112 | this.files = []; 113 | let selectedDocsMap = this.template.querySelector("c-datatable[data-id=documents]").getSelectedData(); 114 | Object.values(selectedDocsMap).forEach(doc => { 115 | this.files.push({ documentId: doc.Id, name: doc.Title }); 116 | }); 117 | console.log("Files List => ", JSON.stringify(this.files)); 118 | this.uploadToRecord(this.template.querySelector(".id-share-record").value); 119 | } 120 | 121 | searchFiles(event) { 122 | if (event.keyCode === 13) { 123 | this.doSearch(); 124 | } 125 | } 126 | 127 | doSearch() { 128 | this.queryFilters = (this.parentId ? (` ParentId='${this.parentId}' AND `) : '') + 129 | "Title LIKE '%" + this.template.querySelector(".id-search-str").value + "%'"; 130 | } 131 | 132 | handleUploadFinished(event) { 133 | this.files = event.detail.files; 134 | this.refreshSearchData(); 135 | this.files.forEach((file) => { 136 | console.log('Uploaded => ', file.documentId, file.name); 137 | }); 138 | } 139 | 140 | refreshSearchData() { 141 | if (this.template.querySelector("c-datatable[data-id=documents]")) { 142 | this.template.querySelector("c-datatable[data-id=documents]").refresh(); 143 | } 144 | } 145 | } -------------------------------------------------------------------------------- /src/lwc/datatable/datatable.html: -------------------------------------------------------------------------------- 1 | 22 | -------------------------------------------------------------------------------- /.forceCode/sasank.github@gmail.com/.sfdx/typings/lwc/engine.d.ts: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (c) 2018, salesforce.com, inc. 3 | * All rights reserved. 4 | * SPDX-License-Identifier: MIT 5 | * For full license text, see the LICENSE file in the repo root or https://opensource.org/licenses/MIT 6 | */ 7 | /** 8 | * Lightning Web Components core module 9 | */ 10 | declare module 'lwc' { 11 | 12 | interface ComposableEvent extends Event { 13 | composed: boolean 14 | } 15 | 16 | class HTMLElementTheGoodPart { 17 | dispatchEvent(evt: ComposableEvent): boolean; 18 | addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; 19 | removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; 20 | getAttribute(name: string): string | null; 21 | getBoundingClientRect(): ClientRect; 22 | querySelector(selectors: string): HTMLElement | null 23 | querySelectorAll(selectors: string): NodeListOf 24 | readonly tagName: string 25 | readonly classList: DOMTokenList; 26 | 27 | // Default HTML Properties 28 | dir: string; 29 | id: string; 30 | accessKey: string; 31 | title: string; 32 | lang: string; 33 | hidden: boolean; 34 | draggable: boolean; 35 | tabIndex: number; 36 | 37 | // Aria Properties 38 | ariaAutoComplete: string | null; 39 | ariaChecked: string | null; 40 | ariaCurrent: string | null; 41 | ariaDisabled: string | null; 42 | ariaExpanded: string | null; 43 | ariaHasPopUp: string | null; 44 | ariaHidden: string | null; 45 | ariaInvalid: string | null; 46 | ariaLabel: string | null; 47 | ariaLevel: string | null; 48 | ariaMultiLine: string | null; 49 | ariaMultiSelectable: string | null; 50 | ariaOrientation: string | null; 51 | ariaPressed: string | null; 52 | ariaReadOnly: string | null; 53 | ariaRequired: string | null; 54 | ariaSelected: string | null; 55 | ariaSort: string | null; 56 | ariaValueMax: string | null; 57 | ariaValueMin: string | null; 58 | ariaValueNow: string | null; 59 | ariaValueText: string | null; 60 | ariaLive: string | null; 61 | ariaRelevant: string | null; 62 | ariaAtomic: string | null; 63 | ariaBusy: string | null; 64 | ariaActiveDescendant: string | null; 65 | ariaControls: string | null; 66 | ariaDescribedBy: string | null; 67 | ariaFlowTo: string | null; 68 | ariaLabelledBy: string | null; 69 | ariaOwns: string | null; 70 | ariaPosInSet: string | null; 71 | ariaSetSize: string | null; 72 | ariaColCount: string | null; 73 | ariaColIndex: string | null; 74 | ariaDetails: string | null; 75 | ariaErrorMessage: string | null; 76 | ariaKeyShortcuts: string | null; 77 | ariaModal: string | null; 78 | ariaPlaceholder: string | null; 79 | ariaRoleDescription: string | null; 80 | ariaRowCount: string | null; 81 | ariaRowIndex: string | null; 82 | ariaRowSpan: string | null; 83 | role: string | null; 84 | } 85 | 86 | interface ShadowRootTheGoodPart extends NodeSelector { 87 | mode: string; 88 | readonly host: null; 89 | readonly firstChild: Node | null, 90 | readonly lastChild: Node | null, 91 | readonly innerHTML: string, 92 | readonly textContent: string, 93 | readonly childNodes: Node[], 94 | readonly delegatesFocus: boolean, 95 | addEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | AddEventListenerOptions): void; 96 | removeEventListener(type: string, listener: EventListenerOrEventListenerObject, options?: boolean | EventListenerOptions): void; 97 | hasChildNodes(): boolean; 98 | compareDocumentPosition(otherNode: Node): number; 99 | contains(otherNode: Node): boolean; 100 | 101 | // Aria Properties 102 | ariaAutoComplete: string | null; 103 | ariaChecked: string | null; 104 | ariaCurrent: string | null; 105 | ariaDisabled: string | null; 106 | ariaExpanded: string | null; 107 | ariaHasPopUp: string | null; 108 | ariaHidden: string | null; 109 | ariaInvalid: string | null; 110 | ariaLabel: string | null; 111 | ariaLevel: string | null; 112 | ariaMultiLine: string | null; 113 | ariaMultiSelectable: string | null; 114 | ariaOrientation: string | null; 115 | ariaPressed: string | null; 116 | ariaReadOnly: string | null; 117 | ariaRequired: string | null; 118 | ariaSelected: string | null; 119 | ariaSort: string | null; 120 | ariaValueMax: string | null; 121 | ariaValueMin: string | null; 122 | ariaValueNow: string | null; 123 | ariaValueText: string | null; 124 | ariaLive: string | null; 125 | ariaRelevant: string | null; 126 | ariaAtomic: string | null; 127 | ariaBusy: string | null; 128 | ariaActiveDescendant: string | null; 129 | ariaControls: string | null; 130 | ariaDescribedBy: string | null; 131 | ariaFlowTo: string | null; 132 | ariaLabelledBy: string | null; 133 | ariaOwns: string | null; 134 | ariaPosInSet: string | null; 135 | ariaSetSize: string | null; 136 | ariaColCount: string | null; 137 | ariaColIndex: string | null; 138 | ariaDetails: string | null; 139 | ariaErrorMessage: string | null; 140 | ariaKeyShortcuts: string | null; 141 | ariaModal: string | null; 142 | ariaPlaceholder: string | null; 143 | ariaRoleDescription: string | null; 144 | ariaRowCount: string | null; 145 | ariaRowIndex: string | null; 146 | ariaRowSpan: string | null; 147 | role: string | null; 148 | } 149 | 150 | /** 151 | * Base class for the Lightning Web Component JavaScript class 152 | */ 153 | export class LightningElement extends HTMLElementTheGoodPart { 154 | /** 155 | * Called when the component is created 156 | */ 157 | constructor(); 158 | /** 159 | * Called when the element is inserted in a document 160 | */ 161 | connectedCallback(): void; 162 | /** 163 | * Called when the element is removed from a document 164 | */ 165 | disconnectedCallback(): void; 166 | /** 167 | * Called after every render of the component 168 | */ 169 | renderedCallback(): void; 170 | /** 171 | * Called when a descendant component throws an error in one of its lifecycle hooks 172 | */ 173 | errorCallback(error: any, stack: string): void; 174 | 175 | readonly template: ShadowRootTheGoodPart; 176 | readonly shadowRoot: null; 177 | } 178 | 179 | /** 180 | * Decorator to mark public reactive properties 181 | */ 182 | export const api: PropertyDecorator; 183 | 184 | /** 185 | * Decorator to mark private reactive properties 186 | */ 187 | export const track: PropertyDecorator; 188 | 189 | /** 190 | * Decorator factory to wire a property or method to a wire adapter data source 191 | * @param getType imperative accessor for the data source 192 | * @param config configuration object for the accessor 193 | */ 194 | export function wire(getType: (config?: any) => any, config?: any): PropertyDecorator; 195 | } 196 | -------------------------------------------------------------------------------- /.forceCode/sasank.github@gmail.com/.sfdx/typings/lwc/lds.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'lightning/uiListApi' { 2 | /** 3 | * Identifier for an object. 4 | */ 5 | export interface ObjectId { 6 | /** The object's API name. */ 7 | objectApiName: string; 8 | } 9 | 10 | /** 11 | * Identifier for an object's field. 12 | */ 13 | export interface FieldId { 14 | /** The field's API name. */ 15 | fieldApiName: string; 16 | /** The object's API name. */ 17 | objectApiName: string; 18 | } 19 | 20 | /** 21 | * Wire adapter for list view records and metadata. 22 | * 23 | * https://developer.salesforce.com/docs/atlas.en-us.uiapi.meta/uiapi/ui_api_resources_list_views_records_md.htm 24 | * 25 | * @param objectApiName API name of the list view's object (must be specified along with listViewApiName). 26 | * @param listViewApiName API name of the list view (must be specified with objectApiName). 27 | * @param listViewId ID of the list view (may be specified without objectApiName or listViewApiName). 28 | * @param pageToken Page ID of records to retrieve. 29 | * @param pageSize Number of records to retrieve at once. The default value is 50. Value can be 1–2000. 30 | * @param sortBy Object-qualified field API name on which to sort. 31 | * @param fields Object-qualified field API names to retrieve. These fields don’t create visible columns. 32 | * If a field isn’t accessible to the context user, it causes an error. 33 | * @param optionalFields Object-qualified field API names to retrieve. These fields don’t create visible columns. 34 | * If an optional field isn’t accessible to the context user, it isn’t included in the response, but it doesn’t cause an error. 35 | * @param q Query string to filter list views (only for a list of lists). 36 | * @returns {Observable} See description. 37 | */ 38 | export function getListUi( 39 | objectApiName?: string | ObjectId, 40 | listViewApiName?: string | symbol, 41 | listViewId?: string, 42 | pageToken?: string, 43 | pageSize?: number, 44 | sortBy?: string | FieldId, 45 | fields?: Array, 46 | optionalFields?: Array, 47 | q?: string, 48 | ): void; 49 | } 50 | 51 | declare module 'lightning/uiObjectInfoApi' { 52 | /** 53 | * Identifier for an object. 54 | */ 55 | export interface ObjectId { 56 | /** The object's API name. */ 57 | objectApiName: string; 58 | } 59 | 60 | /** 61 | * Identifier for an object's field. 62 | */ 63 | export interface FieldId { 64 | /** The field's API name. */ 65 | fieldApiName: string; 66 | /** The object's API name. */ 67 | objectApiName: string; 68 | } 69 | 70 | /** 71 | * Wire adapter for object metadata. 72 | * 73 | * https://developer.salesforce.com/docs/atlas.en-us.uiapi.meta/uiapi/ui_api_resources_object_info.htm 74 | * 75 | * @param objectApiName The API name of the object to retrieve. 76 | */ 77 | export function getObjectInfo(objectApiName: string | ObjectId): void; 78 | 79 | /** 80 | * Wire adapter for values for a picklist field. 81 | * 82 | * https://developer.salesforce.com/docs/atlas.en-us.uiapi.meta/uiapi/ui_api_resources_picklist_values.htm 83 | * 84 | * @param fieldApiName The picklist field's object-qualified API name. 85 | * @param recordTypeId The record type ID. Pass '012000000000000AAA' for the master record type. 86 | */ 87 | export function getPicklistValues(fieldApiName: string | FieldId, recordTypeId: string): void; 88 | 89 | /** 90 | * Wire adapter for values for all picklist fields of a record type. 91 | * 92 | * https://developer.salesforce.com/docs/atlas.en-us.uiapi.meta/uiapi/ui_api_resources_picklist_values_collection.htm 93 | * 94 | * @param objectApiName API name of the object. 95 | * @param recordTypeId Record type ID. Pass '012000000000000AAA' for the master record type. 96 | */ 97 | export function getPicklistValuesByRecordType(objectApiName: string, recordTypeId: string): void; 98 | } 99 | 100 | /** 101 | * JavaScript API to Create and Update Records. 102 | */ 103 | declare module 'lightning/uiRecordApi' { 104 | /** 105 | * Identifier for an object. 106 | */ 107 | export interface ObjectId { 108 | /** The object's API name. */ 109 | objectApiName: string; 110 | } 111 | 112 | /** 113 | * Identifier for an object's field. 114 | */ 115 | export interface FieldId { 116 | /** The field's API name. */ 117 | fieldApiName: string; 118 | /** The object's API name. */ 119 | objectApiName: string; 120 | } 121 | 122 | export type FieldValueRepresentationValue = null | boolean | number | string | RecordRepresentation; 123 | export interface FieldValueRepresentation { 124 | displayValue: string | null; 125 | value: FieldValueRepresentationValue; 126 | } 127 | 128 | export interface RecordCollectionRepresentation { 129 | eTag?: string; 130 | count: number; 131 | currentPageToken: string; 132 | currentPageUrl: string; 133 | nextPageToken: string; 134 | nextPageUrl: string; 135 | previousPageToken: string; 136 | previousPageUrl: string; 137 | records: RecordRepresentation[]; 138 | } 139 | 140 | export interface RecordTypeInfoRepresentation { 141 | available: boolean; 142 | defaultRecordTypeMapping: boolean; 143 | master: boolean; 144 | name: string; 145 | recordTypeId: string; 146 | } 147 | 148 | export interface RecordRepresentation { 149 | apiName: string; 150 | childRelationships?: { [key: string]: RecordCollectionRepresentation }; 151 | fields: { [key: string]: FieldValueRepresentation }; 152 | id: string; 153 | lastModifiedById: string; 154 | lastModifiedDate: string; 155 | recordTypeInfo?: RecordTypeInfoRepresentation; 156 | systemModstamp: string; 157 | } 158 | 159 | export interface RecordInput { 160 | apiName?: string; 161 | fields: { [key: string]: string | null }; 162 | allowSaveOnDuplicate?: boolean; 163 | recordTypeInfo?: RecordTypeInfoRepresentation; 164 | LastModifiedDate?: string; 165 | } 166 | 167 | export interface ClientOptions { 168 | eTagToCheck?: string; 169 | ifUnmodifiedSince?: string; 170 | } 171 | 172 | export interface ChildRelationshipRepresentation { 173 | childObjectApiName: string; 174 | fieldName: string; 175 | junctionIdListNames: string[]; 176 | junctionReferenceTo: string[]; 177 | relationshipName: string; 178 | } 179 | 180 | export interface ReferenceToInfoRepresentation { 181 | apiName: string; 182 | nameFields: string[]; 183 | } 184 | 185 | export interface FilteredLookupInfoRepresentation { 186 | controllingFields: string[]; 187 | dependent: boolean; 188 | optionalFilter: boolean; 189 | } 190 | 191 | export const enum ExtraTypeInfo { 192 | ExternalLookup = 'ExternalLookup', 193 | ImageUrl = 'ImageUrl', 194 | IndirectLookup = 'IndirectLookup', 195 | PersonName = 'PersonName', 196 | PlainTextArea = 'PlainTextArea', 197 | RichTextArea = 'RichTextArea', 198 | SwitchablePersonName = 'SwitchablePersonName', 199 | } 200 | 201 | export const enum RecordFieldDataType { 202 | Address = 'Address', 203 | Base64 = 'Base64', 204 | Boolean = 'Boolean', 205 | ComboBox = 'ComboBox', 206 | ComplexValue = 'ComplexValue', 207 | Currency = 'Currency', 208 | Date = 'Date', 209 | DateTime = 'DateTime', 210 | Double = 'Double', 211 | Email = 'Email', 212 | EncryptedString = 'EncryptedString', 213 | Int = 'Int', 214 | Location = 'Location', 215 | MultiPicklist = 'MultiPicklist', 216 | Percent = 'Percent', 217 | Phone = 'Phone', 218 | Picklist = 'Picklist', 219 | Reference = 'Reference', 220 | String = 'String', 221 | TextArea = 'TextArea', 222 | Time = 'Time', 223 | Url = 'Url', 224 | } 225 | 226 | export interface FieldRepresentation { 227 | apiName: string; 228 | calculated: boolean; 229 | compound: boolean; 230 | compoundComponentName: string; 231 | compoundFieldName: string; 232 | controllerName: string; 233 | controllingFields: string[]; 234 | createable: boolean; 235 | custom: boolean; 236 | dataType: RecordFieldDataType; 237 | extraTypeInfo: ExtraTypeInfo; 238 | filterable: boolean; 239 | filteredLookupInfo: FilteredLookupInfoRepresentation; 240 | highScaleNumber: boolean; 241 | htmlFormatted: boolean; 242 | inlineHelpText: string; 243 | label: string; 244 | length: number; 245 | nameField: boolean; 246 | polymorphicForeignKey: boolean; 247 | precision: number; 248 | reference: boolean; 249 | referenceTargetField: string; 250 | referenceToInfos: ReferenceToInfoRepresentation[]; 251 | relationshipName: string; 252 | required: boolean; 253 | scale: number; 254 | searchPrefilterable: boolean; 255 | sortable: boolean; 256 | unique: boolean; 257 | updateable: boolean; 258 | } 259 | 260 | interface ThemeInfoRepresentation { 261 | color: string; 262 | iconUrl: string; 263 | } 264 | 265 | export interface ObjectInfoRepresentation { 266 | apiName: string; 267 | childRelationships: ChildRelationshipRepresentation[]; 268 | createable: boolean; 269 | custom: boolean; 270 | defaultRecordTypeId: string; 271 | deletable: boolean; 272 | deleteable: boolean; 273 | dependentFields: { [key: string]: any }; 274 | eTag: string; 275 | feedEnabled: boolean; 276 | fields: { [key: string]: FieldRepresentation }; 277 | keyPrefix: string; 278 | label: string; 279 | labelPlural: string; 280 | layoutable: boolean; 281 | mruEnabled: boolean; 282 | nameFields: string[]; 283 | queryable: boolean; 284 | recordTypeInfos: { [key: string]: RecordTypeInfoRepresentation }; 285 | searchable: boolean; 286 | themeInfo: ThemeInfoRepresentation; 287 | updateable: boolean; 288 | } 289 | 290 | /** 291 | * Wire adapter for a record. 292 | * 293 | * https://developer.salesforce.com/docs/atlas.en-us.uiapi.meta/uiapi/ui_api_resources_record_get.htm 294 | * 295 | * @param recordId ID of the record to retrieve. 296 | * @param fields Object-qualified field API names to retrieve. If a field isn’t accessible to the context user, it causes an error. 297 | * If specified, don't specify layoutTypes. 298 | * @param layoutTypes Layouts defining the fields to retrieve. If specified, don't specify fields. 299 | * @param modes Layout modes defining the fields to retrieve. 300 | * @param optionalFields Object-qualified field API names to retrieve. If an optional field isn’t accessible to the context user, 301 | * it isn’t included in the response, but it doesn’t cause an error. 302 | * @returns An observable of the record. 303 | */ 304 | export function getRecord( 305 | recordId: string, 306 | fields?: Array, 307 | layoutTypes?: string[], 308 | modes?: string[], 309 | optionalFields?: Array, 310 | ): void; 311 | 312 | /** 313 | * Wire adapter for default field values to create a record. 314 | * 315 | * https://developer.salesforce.com/docs/atlas.en-us.uiapi.meta/uiapi/ui_api_resources_record_defaults_create.htm#ui_api_resources_record_defaults_create 316 | * 317 | * @param objectApiName API name of the object. 318 | * @param formFactor Form factor. Possible values are 'Small', 'Medium', 'Large'. Large is default. 319 | * @param recordTypeId Record type id. 320 | * @param optionalFields Object-qualified field API names to retrieve. If an optional field isn’t accessible to the context user, 321 | * it isn’t included in the response, but it doesn’t cause an error. 322 | */ 323 | export function getRecordCreateDefaults( 324 | objectApiName: string | ObjectId, 325 | formFactor?: string, 326 | recordTypeId?: string, 327 | optionalFields?: Array, 328 | ): void; 329 | 330 | /** 331 | * Wire adapter for record data, object metadata and layout metadata 332 | * 333 | * https://developer.salesforce.com/docs/atlas.en-us.uiapi.meta/uiapi/ui_api_resources_record_ui.htm 334 | * 335 | * @param recordIds ID of the records to retrieve. 336 | * @param layoutTypes Layouts defining the fields to retrieve. 337 | * @param modes Layout modes defining the fields to retrieve. 338 | * @param optionalFields Object-qualified field API names to retrieve. If an optional field isn’t accessible to the context user, 339 | * it isn’t included in the response, but it doesn’t cause an error. 340 | */ 341 | export function getRecordUi( 342 | recordIds: string | string[], 343 | layoutTypes: string | string[], 344 | modes: string | string[], 345 | optionalFields: Array, 346 | ): void; 347 | 348 | /** 349 | * Updates a record using the properties in recordInput. recordInput.fields.Id must be specified. 350 | * @param recordInput The record input representation to use to update the record. 351 | * @param clientOptions Controls the update behavior. Specify ifUnmodifiedSince to fail the save if the record has changed since the provided value. 352 | * @returns A promise that will resolve with the patched record. 353 | */ 354 | export function updateRecord(recordInput: RecordInput, clientOptions?: ClientOptions): Promise; 355 | 356 | /** 357 | * Creates a new record using the properties in recordInput. 358 | * @param recordInput The RecordInput object to use to create the record. 359 | * @returns A promise that will resolve with the newly created record. 360 | */ 361 | export function createRecord(recordInput: RecordInput): Promise; 362 | 363 | /** 364 | * Deletes a record with the specified recordId. 365 | * @param recordId ID of the record to delete. 366 | * @returns A promise that will resolve to undefined. 367 | */ 368 | export function deleteRecord(recordId: string): Promise; 369 | 370 | /** 371 | * Returns an object with its data populated from the given record. All fields with values that aren't nested records will be assigned. 372 | * This object can be used to create a record with createRecord(). 373 | * @param record The record that contains the source data. 374 | * @param objectInfo The ObjectInfo corresponding to the apiName on the record. If provided, only fields that are createable=true 375 | * (excluding Id) are assigned to the object return value. 376 | * @returns RecordInput 377 | */ 378 | export function generateRecordInputForCreate(record: RecordRepresentation, objectInfo?: ObjectInfoRepresentation): RecordInput; 379 | 380 | /** 381 | * Returns an object with its data populated from the given record. All fields with values that aren't nested records will be assigned. 382 | * This object can be used to update a record. 383 | * @param record The record that contains the source data. 384 | * @param objectInfo The ObjectInfo corresponding to the apiName on the record. 385 | * If provided, only fields that are updateable=true (excluding Id) are assigned to the object return value. 386 | * @returns RecordInput. 387 | */ 388 | export function generateRecordInputForUpdate(record: RecordRepresentation, objectInfo?: ObjectInfoRepresentation): RecordInput; 389 | 390 | /** 391 | * Returns a new RecordInput containing a list of fields that have been edited from their original values. (Also contains the Id 392 | * field, which is always copied over.) 393 | * @param recordInput The RecordInput object to filter. 394 | * @param originalRecord The Record object that contains the original field values. 395 | * @returns RecordInput. 396 | */ 397 | export function createRecordInputFilteredByEditedFields(recordInput: RecordInput, originalRecord: RecordRepresentation): RecordInput; 398 | 399 | /** 400 | * Gets a field's value from a record. 401 | * @param record The record. 402 | * @param field Object-qualified API name of the field to return. 403 | * @returns The field's value (which may be a record in the case of spanning fields), or undefined if the field isn't found. 404 | */ 405 | export function getFieldValue(record: RecordRepresentation, field: FieldId | string): FieldValueRepresentationValue | undefined; 406 | 407 | /** 408 | * Gets a field's display value from a record. 409 | * @param record The record. 410 | * @param field Object-qualified API name of the field to return. 411 | * @returns The field's display value, or undefined if the field isn't found. 412 | */ 413 | export function getFieldDisplayValue(record: RecordRepresentation, field: FieldId | string): FieldValueRepresentationValue | undefined; 414 | } 415 | -------------------------------------------------------------------------------- /src/lwc/datatable/datatable.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @File Name : datatable.js 3 | * @Description : Methods supporting functionalities of datatable 4 | * @Author : Sasank Subrahmanyam V 5 | * @Group : 6 | * @Last Modified By : Sasank Subrahmanyam V 7 | * @Last Modified On : 8/3/2019, 3:17:35 PM 8 | * @Modification Log : 9 | *============================================================================== 10 | * Ver Date Author Modification 11 | *============================================================================== 12 | * 1.0 8/1/2019, 11:08:28 AM Sasank Subrahmanyam V Initial Version 13 | **/ 14 | import { LightningElement, api, track } from 'lwc'; 15 | import fetchDataMap from '@salesforce/apex/datatableController.fetchDataMap'; 16 | import fetchDataMapCached from '@salesforce/apex/datatableController.fetchDataMapCached'; 17 | 18 | export default class Datatable extends LightningElement { 19 | // this will have all the configuration for table 20 | @api config = {}; 21 | 22 | // Name of the object from from records have to be queried 23 | @track objectName; 24 | 25 | // All the attributes of lightning-datatable should be mentioned in this attribute (tableConfig). 26 | // It should be either kebab casing or camel casing 27 | @track tableConfig = {}; 28 | 29 | // By which field should the query sort the records from database 30 | @track sortBy; 31 | 32 | // For the field mentioned above, should it be ascending (true) or descending (false) 33 | @track sortAsc; 34 | 35 | // What is the limit of number of records to be fetched from database 36 | @track limit; 37 | 38 | // what query type should be used? SOQL or SOSL? 39 | @track queryType = "SOQL"; 40 | 41 | // should the pagination be hidden? 42 | @track hidePagination = false; 43 | 44 | // Do you want to hide the spinner in table and handle it yourself outside table? 45 | @track hideTableSpinner = false; 46 | 47 | // will set the apex method cacheable 48 | @track cacheable = false; 49 | 50 | // set height of table 51 | @track height = '10rem'; 52 | 53 | // internally used tracked variables for data processing 54 | @track tableProps = {}; 55 | @track recordsListInPage = []; 56 | @track selectedRowsMap = {}; 57 | @track selectedRowsPagesMap = {}; 58 | @track hideSpinner = false; 59 | @track userMessage = "Please wait..."; 60 | @track error; 61 | 62 | // internally used non-tracked variables for initialization 63 | _recordsListInAllPages = []; 64 | _startFromIndex = 0; 65 | _paginationInfo = { 66 | currentPage: 0, 67 | totalPages: 0 68 | }; 69 | _initDone = false; 70 | _soslMinCharsError = "Please enter atleast 2 characters to search"; 71 | 72 | 73 | // exposed api methods ---------------------------------------------------------------------------------------- 74 | 75 | // for setting custom messages in different contexts 76 | @api 77 | get userMessages() { 78 | if (this.isNotBlank(this._userMessages)) return this._userMessages; 79 | return { 80 | init: 'Please wait...', 81 | noRecords: 'NO RECORDS FOUND', 82 | search: 'Searching...' 83 | }; 84 | } 85 | set userMessages(value) { 86 | this._userMessages = value; 87 | } 88 | 89 | // invoked when sosl search term is changed 90 | @api 91 | get soslSearchTerm() { 92 | return this._soslSearchTerm; 93 | } 94 | set soslSearchTerm(value) { 95 | this._soslSearchTerm = value; 96 | this.doDataReset(); 97 | if (typeof value === "string" && (value.length > 1) && this._initDone) this.fetchRecords(); 98 | else this.handleSpinner(false, this._soslMinCharsError); 99 | } 100 | 101 | // invoked when page size is changed 102 | @api 103 | get pageSize() { 104 | if (!this.isNotBlank(this._pageSize)) this._pageSize = 10; 105 | return parseInt(this._pageSize, 10); 106 | } 107 | set pageSize(value) { 108 | this._pageSize = value; 109 | } 110 | 111 | // for dynamically filtering data 112 | @api 113 | get queryFilters() { 114 | if (this._queryFilters) return this._queryFilters; 115 | return ""; 116 | } 117 | set queryFilters(value) { 118 | this._queryFilters = value; 119 | if (this._initDone) { 120 | this.doDataReset(); 121 | this.fetchRecords(); 122 | } 123 | } 124 | 125 | // for manually refreshing table 126 | @api 127 | refresh() { 128 | this._startFromIndex = 0; 129 | this.doDataReset(); 130 | return this.fetchRecords(); 131 | } 132 | 133 | // for doing sosl search 134 | @api 135 | doSoslSearch(searchTerm) { 136 | this.soslSearchTerm = searchTerm; 137 | } 138 | 139 | // for getting selected rows 140 | @api 141 | getSelectedRows() { 142 | return this.selectedRowsMap; 143 | } 144 | 145 | // initialization of component 146 | connectedCallback() { 147 | this.processConfig(); 148 | this.userMessage = this.userMessages.init; // set initial user message 149 | this.fetchRecords(); 150 | this._originTagRowSelectionLocal = "LIGHTNING-DATATABLE"; // initialising to the expected source tag 151 | this._initDone = true; 152 | } 153 | 154 | // for developer purpose, errors are logged in console 155 | handleError(err) { 156 | console.error("error => ", err); 157 | this.error = err + ". Please check console for details."; 158 | return err; 159 | } 160 | 161 | // datatable events are processed and then dispatched to parent component ---------------------------------- 162 | handleRowAction = event => { 163 | this.dispatchEvent(new CustomEvent('rowaction', { 164 | detail: { 165 | action: event.detail.action, 166 | row: event.detail.row 167 | } 168 | })); 169 | } 170 | handleCancel = event => this.dispatchEvent(new CustomEvent('cancel', { detail: event.detail })); 171 | handleResize = event => this.dispatchEvent(new CustomEvent('resize', { detail: event.detail })) 172 | handleRowSelection = event => { 173 | if (this._originTagRowSelectionLocal === "LIGHTNING-DATATABLE") { 174 | this.selectedRowsMap = {}; 175 | this.selectedRowsPagesMap[this._paginationInfo.currentPage] = event.detail.selectedRows; 176 | Object.values(this.selectedRowsPagesMap).forEach(rowsList => { 177 | rowsList.forEach(row => { 178 | this.selectedRowsMap[row.Id] = row; 179 | }); 180 | }); 181 | 182 | let detail = { 183 | selectedRows: Object.values(this.selectedRowsMap), 184 | selectedRowsMap: this.selectedRowsMap 185 | }; 186 | 187 | this.dispatchEvent(new CustomEvent('rowselection', { 188 | detail: detail 189 | })); 190 | } else { 191 | this._originTagRowSelectionLocal = event.target.tagName; 192 | } 193 | } 194 | handleSave = event => this.dispatchEvent(new CustomEvent('save', { detail: event.detail })); 195 | handleSort = event => { 196 | this.selectedRowsMap = {}; 197 | this.selectedRowsPagesMap = {}; 198 | 199 | this.tableProps.sortedBy = event.detail.fieldName; 200 | this.tableProps.sortedDirection = event.detail.sortDirection; 201 | 202 | this._recordsListInAllPages.sort((a, b) => { 203 | if (!a[this.tableProps.sortedBy]) return 1; 204 | if (!b[this.tableProps.sortedBy]) return -1; 205 | if (this.tableProps.sortedDirection === "asc") { 206 | if (a[this.tableProps.sortedBy] < b[this.tableProps.sortedBy]) return -1; 207 | else if (a[this.tableProps.sortedBy] > b[this.tableProps.sortedBy]) return 1; 208 | if (a.Id < b.Id) return -1; 209 | return 1; 210 | } 211 | if (a[this.tableProps.sortedBy] < b[this.tableProps.sortedBy]) return 1; 212 | else if (a[this.tableProps.sortedBy] > b[this.tableProps.sortedBy]) return -1; 213 | if (a.Id > b.Id) return -1; 214 | return 1; 215 | }); 216 | 217 | this._startFromIndex = 0; 218 | this.processRecordsListPagination(); 219 | } 220 | 221 | // init processing ------------------------------------------------------------------------------------ 222 | processConfig() { 223 | if (this.config.hasOwnProperty("object-name") || this.config.hasOwnProperty("objectName")) 224 | this.objectName = this.config["object-name"] || this.config.objectName; 225 | if (this.config.hasOwnProperty("sort-by") || this.config.hasOwnProperty("sortBy")) 226 | this.sortBy = this.config["sort-by"] || this.config.sortBy; 227 | if (this.config.hasOwnProperty("sort-asc") || this.config.hasOwnProperty("sortAsc")) 228 | this.sortAsc = this.config["sort-asc"] || this.config.sortAsc; 229 | if (this.config.hasOwnProperty("limit")) 230 | this.limit = this.config.limit; 231 | if (this.config.hasOwnProperty("cacheable")) 232 | this.cacheable = this.config.cacheable; 233 | if (this.config.hasOwnProperty("height")) 234 | this.height = this.config.height; 235 | 236 | if (this.config.hasOwnProperty("query-type") || this.config.hasOwnProperty("queryType")) 237 | this.queryType = this.config["query-type"] || this.config.queryType; 238 | 239 | if (this.config.hasOwnProperty("hide-pagination") || this.config.hasOwnProperty("hidePagination")) 240 | this.hidePagination = this.config["hide-pagination"] || this.config.hidePagination; 241 | 242 | if (this.config.hasOwnProperty("hide-table-spinner") || this.config.hasOwnProperty("hideTableSpinner")) 243 | this.hideTableSpinner = this.config["hide-table-spinner"] || this.config.hideTableSpinner; 244 | 245 | if (this.config.hasOwnProperty("user-messages") || this.config.hasOwnProperty("userMessages")) 246 | this.userMessages = this.config["user-messages"] || this.config.userMessages; 247 | 248 | if (this.config.hasOwnProperty("page-size") || this.config.hasOwnProperty("pageSize")) 249 | this.pageSize = this.config["page-size"] || this.config.pageSize; 250 | 251 | if (this.config.hasOwnProperty("query-filters") || this.config.hasOwnProperty("queryFilters")) 252 | this.queryFilters = this.config["query-filters"] || this.config.queryFilters; 253 | 254 | if (this.config.hasOwnProperty("sosl-search-term") || this.config.hasOwnProperty("soslSearchTerm")) 255 | this.soslSearchTerm = this.config["sosl-search-term"] || this.config.soslSearchTerm; 256 | 257 | if (this.config.hasOwnProperty("table-config") || this.config.hasOwnProperty("tableConfig")) { 258 | this.tableConfig = this.config["table-config"] || this.config.tableConfig; 259 | this.processTableConfig(); 260 | this.fields = this.tableProps.columns.filter(col => col.hasOwnProperty("api")).map(col => col.api).join(); 261 | } 262 | } 263 | 264 | // get datatable attributes -------------------------------------------------------------------------- 265 | processTableConfig() { 266 | this.tableProps.columns = this.tableConfig.columns; 267 | this.tableProps.sortedBy = ""; 268 | this.tableProps.sortedDirection = ""; 269 | if (this.tableConfig.hasOwnProperty("hideCheckboxColumn") || this.tableConfig.hasOwnProperty("hide-checkbox-column")) 270 | this.tableProps.hideCheckboxColumn = this.tableConfig.hideCheckboxColumn || this.tableConfig["hide-checkbox-column"]; 271 | else this.tableProps.hideCheckboxColumn = false; 272 | if (this.tableConfig.hasOwnProperty("showRowNumberColumn") || this.tableConfig.hasOwnProperty("show-row-number-column")) 273 | this.tableProps.showRowNumberColumn = this.tableConfig.showRowNumberColumn || this.tableConfig["show-row-number-column"]; 274 | else this.tableProps.showRowNumberColumn = false; 275 | if (this.tableConfig.hasOwnProperty("rowNumberOffset") || this.tableConfig.hasOwnProperty("row-number-offset")) 276 | this.tableProps.rowNumberOffset = this.tableConfig.rowNumberOffset || this.tableConfig["row-number-offset"]; 277 | else this.tableProps.rowNumberOffset = 0; 278 | if (this.tableConfig.hasOwnProperty("resizeColumnDisabled") || this.tableConfig.hasOwnProperty("resize-column-disabled")) 279 | this.tableProps.resizeColumnDisabled = this.tableConfig.resizeColumnDisabled || this.tableConfig["resize-column-disabled"]; 280 | else this.tableProps.resizeColumnDisabled = false; 281 | if (this.tableConfig.hasOwnProperty("minColumnWidth") || this.tableConfig.hasOwnProperty("min-column-width")) 282 | this.tableProps.minColumnWidth = this.tableConfig.minColumnWidth || this.tableConfig["min-column-width"]; 283 | else this.tableProps.minColumnWidth = "50px"; 284 | if (this.tableConfig.hasOwnProperty("maxColumnWidth") || this.tableConfig.hasOwnProperty("max-column-width")) 285 | this.tableProps.maxColumnWidth = this.tableConfig.maxColumnWidth || this.tableConfig["max-column-width"]; 286 | else this.tableProps.maxColumnWidth = "1000px"; 287 | if (this.tableConfig.hasOwnProperty("resizeStep") || this.tableConfig.hasOwnProperty("resize-step")) 288 | this.tableProps.resizeStep = this.tableConfig.resizeStep || this.tableConfig["resize-step"]; 289 | else this.tableProps.resizeStep = "10px"; 290 | if (this.tableConfig.hasOwnProperty("defaultSortDirection") || this.tableConfig.hasOwnProperty("default-sort-direction")) 291 | this.tableProps.defaultSortDirection = this.tableConfig.defaultSortDirection || this.tableConfig["default-sort-direction"]; 292 | else this.tableProps.defaultSortDirection = "asc"; 293 | if (this.tableConfig.hasOwnProperty("enableInfiniteLoading") || this.tableConfig.hasOwnProperty("enable-infinite-loading")) 294 | this.tableProps.enableInfiniteLoading = this.tableConfig.enableInfiniteLoading || this.tableConfig["enable-infinite-loading"]; 295 | else this.tableProps.enableInfiniteLoading = false; 296 | if (this.tableConfig.hasOwnProperty("loadMoreOffset") || this.tableConfig.hasOwnProperty("load-more-offset")) 297 | this.tableProps.loadMoreOffset = this.tableConfig.loadMoreOffset || this.tableConfig["load-more-offset"]; 298 | else this.tableProps.loadMoreOffset = false; 299 | if (this.tableConfig.hasOwnProperty("isLoading") || this.tableConfig.hasOwnProperty("is-loading")) 300 | this.tableProps.isLoading = this.tableConfig.isLoading || this.tableConfig["is-loading"]; 301 | else this.tableProps.isLoading = false; 302 | if (this.tableConfig.hasOwnProperty("maxRowSelection") || this.tableConfig.hasOwnProperty("max-row-selection")) 303 | this.tableProps.maxRowSelection = this.tableConfig.maxRowSelection || this.tableConfig["max-row-selection"]; 304 | else this.tableProps.maxRowSelection = 1000; 305 | if (this.tableConfig.hasOwnProperty("selectedRows") || this.tableConfig.hasOwnProperty("selected-rows")) 306 | this.tableProps.selectedRows = this.tableConfig.selectedRows || this.tableConfig["selected-rows"]; 307 | else this.tableProps.selectedRows = []; 308 | if (this.tableConfig.hasOwnProperty("errors")) 309 | this.tableProps.errors = this.tableConfig.errors; 310 | else this.tableProps.errors = null; 311 | if (this.tableConfig.hasOwnProperty("draftValues") || this.tableConfig.hasOwnProperty("draft-values")) 312 | this.tableProps.draftValues = this.tableConfig.draftValues || this.tableConfig["draft-values"]; 313 | else this.tableProps.draftValues = null; 314 | if (this.tableConfig.hasOwnProperty("hideTableHeader") || this.tableConfig.hasOwnProperty("hide-table-header")) 315 | this.tableProps.hideTableHeader = this.tableConfig.hideTableHeader || this.tableConfig["hide-table-header"]; 316 | else this.tableProps.hideTableHeader = false; 317 | if (this.tableConfig.hasOwnProperty("suppressBottomBar") || this.tableConfig.hasOwnProperty("suppress-bottom-bar")) 318 | this.tableProps.suppressBottomBar = this.tableConfig.suppressBottomBar || this.tableConfig["suppress-bottom-bar"]; 319 | else this.tableProps.suppressBottomBar = false; 320 | } 321 | 322 | // retrieve the records form database 323 | fetchRecords() { 324 | return new Promise((resolve, reject) => { 325 | this.handleSpinner(true, this.userMessages.search); 326 | 327 | const params = { 328 | objectName: this.objectName, 329 | fields: this.fields, 330 | sortBy: this.sortBy, 331 | sortAsc: this.sortAsc, 332 | queryFilters: this.queryFilters, 333 | limitRecords: this.limit, 334 | queryType: this.queryType, 335 | soslSearchTerm: this.soslSearchTerm 336 | }; 337 | 338 | if (this.cacheable) { 339 | fetchDataMapCached({ params }) 340 | .then(DataMap => resolve(this.getResolve(DataMap.records))) 341 | .catch(error => reject(this.getReject(error))); 342 | } else { 343 | fetchDataMap({ params }) 344 | .then(DataMap => resolve(this.getResolve(DataMap.records))) 345 | .catch(error => reject(this.getReject(error))); 346 | } 347 | }); 348 | } 349 | 350 | // invoked on success 351 | getResolve(records) { 352 | this.error = undefined; 353 | this.processRecordsResult(records); 354 | return "SUCCESS"; 355 | } 356 | 357 | // invoked on error 358 | getReject(error) { 359 | this.handleSpinner(false, ""); 360 | if (error.body && error.body.message) this.handleError(error.body.message); 361 | else this.handleError(error); 362 | this._recordsListInAllPages = undefined; 363 | return "ERROR"; 364 | } 365 | 366 | // process the records returned from database 367 | processRecordsResult(recordsListResult) { 368 | this.handleSpinner(false, ""); 369 | 370 | if (recordsListResult && recordsListResult.length > 0) { 371 | this._recordsListInAllPages = recordsListResult; 372 | this._paginationInfo.totalPages = (((this._recordsListInAllPages.length / this.pageSize) - ((this._recordsListInAllPages.length % this.pageSize) / this.pageSize)) + (((this._recordsListInAllPages.length % this.pageSize) === 0) ? 0 : 1)); 373 | this.processRecordsListPagination(); 374 | } else { 375 | this.doDataReset(); 376 | this.handleSpinner(false, this.userMessages.noRecords); 377 | } 378 | } 379 | 380 | // paginate the records 381 | processRecordsListPagination(lastSetOfRecords = null, lastNumberOfRecords = null) { 382 | if (lastSetOfRecords) { 383 | this.recordsListInPage = this._recordsListInAllPages.slice(lastNumberOfRecords); 384 | } else { 385 | this.recordsListInPage = this._recordsListInAllPages.slice(this._startFromIndex, this.pageSize + this._startFromIndex); 386 | } 387 | 388 | this.processTableRows(); 389 | } 390 | 391 | // process each row to get direct and relationship fields 392 | processTableRows() { 393 | this.tableProps.selectedRows = []; 394 | this.recordsListInPage = this.recordsListInPage.map(thisRow => { 395 | let currentRow = Object.assign({}, thisRow); 396 | if (this.selectedRowsMap.hasOwnProperty(currentRow.Id)) 397 | this.tableProps.selectedRows.push(currentRow.Id); 398 | this.tableProps.columns.forEach(col => { 399 | if (col.hasOwnProperty("api")) 400 | currentRow[col.fieldName] = this.getFieldValueFromObject(currentRow, col.api); 401 | }); 402 | return currentRow; 403 | }); 404 | } 405 | 406 | // reset of data 407 | doDataReset() { 408 | this.tableProps.sortedBy = ""; 409 | this.tableProps.sortedDirection = ""; 410 | this._recordsListInAllPages = []; 411 | this.recordsListInPage = []; 412 | } 413 | 414 | isNotBlank(checkString) { 415 | return (checkString !== '' && checkString !== null && checkString !== undefined); 416 | } 417 | 418 | //GET THE FIELD VALUE IN GIVEN OBJECT 419 | getFieldValueFromObject(thisObject, fieldRelation) { 420 | let fieldRelationArray = fieldRelation.split("."); 421 | let objectFieldValue = thisObject; 422 | for (let f in fieldRelationArray) { 423 | if (objectFieldValue) { 424 | objectFieldValue = objectFieldValue[fieldRelationArray[f].trim()]; 425 | } 426 | } 427 | return objectFieldValue; 428 | } 429 | 430 | // invoked for all the async operations 431 | handleSpinner(showSpinner, userMessage) { 432 | if (!this.hideTableSpinner) { 433 | this.showSpinner = showSpinner; 434 | this.tableProps.isLoading = showSpinner; 435 | } 436 | 437 | this.userMessage = userMessage; 438 | 439 | this.dispatchEvent(new CustomEvent('tableloading', { 440 | detail: { 441 | showSpinner: showSpinner, 442 | userMessage: userMessage 443 | }, 444 | bubbles: true, 445 | composed: true 446 | })); 447 | } 448 | 449 | //PAGINATION - SHOW PREVIOUS PAGE 450 | showPreviousPage(event) { 451 | if (this._startFromIndex > 0) { 452 | if (this.selectedRowsPagesMap.hasOwnProperty(this._paginationInfo.currentPage) && this.selectedRowsPagesMap[this._paginationInfo.currentPage].length > 0) 453 | this._originTagRowSelectionLocal = event.target.tagName; 454 | this._startFromIndex = this._startFromIndex - this.pageSize; 455 | this.processRecordsListPagination(); 456 | } 457 | } 458 | 459 | //PAGINATION - SHOW NEXT PAGE 460 | showNextPage(event) { 461 | if (this._startFromIndex + this.pageSize < this._recordsListInAllPages.length) { 462 | if (this.selectedRowsPagesMap.hasOwnProperty(this._paginationInfo.currentPage) && this.selectedRowsPagesMap[this._paginationInfo.currentPage].length > 0) 463 | this._originTagRowSelectionLocal = event.target.tagName; 464 | this._startFromIndex = this._startFromIndex + this.pageSize; 465 | this.processRecordsListPagination(); 466 | } 467 | } 468 | 469 | showLastPage = () => { 470 | let result = this._recordsListInAllPages.length % this.pageSize; 471 | if (this._startFromIndex >= 0) { 472 | if (result === 0) { 473 | this._startFromIndex = this._recordsListInAllPages.length - this.pageSize; 474 | this.processRecordsListPagination(); 475 | } else { 476 | this._startFromIndex = this._recordsListInAllPages.length - result; 477 | this.processRecordsListPagination(true, -result); 478 | } 479 | } 480 | } 481 | 482 | //PAGINATION - INVOKED WHEN PAGE SIZE IS CHANGED 483 | pageSizeChanged = () => { 484 | this.doTableRefresh(); 485 | this.processRecordsListPagination(); 486 | } 487 | 488 | doTableRefresh = () => { 489 | this._startFromIndex = 0; 490 | } 491 | 492 | get showMessage() { 493 | return this.isNotBlank(this.userMessage) || this.showSpinner; 494 | } 495 | 496 | get pagesInfo() { 497 | if (this._recordsListInAllPages.length > 0) { 498 | this._paginationInfo.currentPage = (((this._startFromIndex + 1) / this.pageSize) - (((this._startFromIndex + 1) % this.pageSize) / this.pageSize) + ((((this._startFromIndex + 1) % this.pageSize) === 0) ? 0 : 1)); 499 | return 'Page ' + this._paginationInfo.currentPage + ' of ' + this._paginationInfo.totalPages; 500 | } 501 | return 'Page 0 of 0'; 502 | } 503 | 504 | get recordsInfo() { 505 | if (this._recordsListInAllPages.length > 0) { 506 | this._endIndex = this._startFromIndex + this.pageSize; 507 | return 'Showing ' + (this._startFromIndex + 1) + " to " + ((this._endIndex > this._recordsListInAllPages.length) ? this._recordsListInAllPages.length : this._endIndex) + " of " + this._recordsListInAllPages.length + " records"; 508 | } 509 | return 'Showing 0 of 0'; 510 | } 511 | 512 | get tableStyle() { 513 | return `height:${this.height};`; 514 | } 515 | } --------------------------------------------------------------------------------