├── .gitignore ├── LICENSE ├── README.md └── src ├── aura ├── DataTableCellCmp │ ├── DataTableCellCmp.cmp │ ├── DataTableCellCmp.cmp-meta.xml │ ├── DataTableCellCmpController.js │ └── DataTableCellCmpHelper.js ├── DataTableCmp │ ├── DataTableCmp.cmp │ ├── DataTableCmp.cmp-meta.xml │ ├── DataTableCmpController.js │ ├── DataTableCmpHelper.js │ └── DataTableCmpRenderer.js ├── DataTableColumnCmp │ ├── DataTableColumnCmp.cmp │ ├── DataTableColumnCmp.cmp-meta.xml │ └── DataTableColumnCmpController.js ├── DataTableDemoApp │ ├── DataTableDemoApp.app │ ├── DataTableDemoApp.app-meta.xml │ ├── DataTableDemoApp.css │ ├── DataTableDemoAppController.js │ └── DataTableDemoAppHelper.js ├── DataTablePageChangeEvent │ ├── DataTablePageChangeEvent.evt │ └── DataTablePageChangeEvent.evt-meta.xml └── DataTableSortChangeEvent │ ├── DataTableSortChangeEvent.evt │ └── DataTableSortChangeEvent.evt-meta.xml ├── classes ├── DataTableDemoAppController.cls └── DataTableDemoAppController.cls-meta.xml └── package.xml /.gitignore: -------------------------------------------------------------------------------- 1 | config/ 2 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2017, Doug Ayers 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Lightning Data Tables Component 2 | 3 | Lightning Component that renders SLDS data table that supports sorting and infinite scrolling with no third-party JS libraries. 4 | 5 | 6 | Deploy to Salesforce 8 | 9 | -------------------------------------------------------------------------------- /src/aura/DataTableCellCmp/DataTableCellCmp.cmp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 8 | 9 | 12 | 13 | 16 | 17 | 20 | 21 | 25 | 26 | 27 | 28 | 32 | 33 | 37 | 38 | 42 | 43 | 47 | 48 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 |
62 | {!v.value} 63 |
64 | 65 | 66 | 67 | 68 | 69 |
70 | 71 |
72 | 73 | 74 | 75 | 80 | 81 | 82 | 83 |
84 | 85 |
86 | 87 |
88 | 89 |
-------------------------------------------------------------------------------- /src/aura/DataTableCellCmp/DataTableCellCmp.cmp-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 38.0 4 | A Lightning Component Bundle 5 | 6 | -------------------------------------------------------------------------------- /src/aura/DataTableCellCmp/DataTableCellCmpController.js: -------------------------------------------------------------------------------- 1 | ({ 2 | doInit : function( component, event, helper ) { 3 | 4 | var row = component.get( 'v.row' ); 5 | var column = component.get( 'v.column' ); 6 | 7 | // the column's name might be a single property on the object 8 | // like 'Subject' or it might be a compound reference 9 | // like 'Who.Name' so we split the string into its parts 10 | // and try to traverse the object graph through the properties. 11 | // 12 | // if the row does not have the full property graph 13 | // then null is returned, otherwise the value at the end of the rainbow. 14 | component.set( 'v.value', helper.parseFieldValue( component, row, column.get( 'v.name' ) ) ); 15 | 16 | // set css class from column definition 17 | component.set( 'v.class', column.get( 'v.valueClass' ) ); 18 | 19 | // determine ui theme 20 | var uiTheme = helper.getUITheme(); 21 | component.set( 'v.uiTheme', uiTheme ); 22 | 23 | // determine type of links to use 24 | var linkToRecord = column.get( 'v.linkToRecord' ); 25 | var linkToURL = column.get( 'v.linkToURL' ); 26 | 27 | // if linking to a record we first check if the expression evaluates to a field on the row object 28 | // that holds the value to link to, otherwise will use the value as-is for linking. 29 | // since this is intended to be an sobject record id, for classic theme only then we ensure 30 | // there's a leading '/'. 31 | if ( !$A.util.isUndefinedOrNull( linkToRecord ) ) { 32 | 33 | var parsedLinkToRecord = helper.parseFieldValue( component, row, linkToRecord ); 34 | 35 | if ( !$A.util.isUndefinedOrNull( parsedLinkToRecord ) ) { 36 | component.set( 'v.linkToRecord', ( uiTheme === 'Classic' ? '/' : '' ) + parsedLinkToRecord ); 37 | } else { 38 | component.set( 'v.linkToRecord', ( uiTheme === 'Classic' ? '/' : '' ) + linkToRecord ); 39 | } 40 | 41 | if ( uiTheme === 'Classic' ) { 42 | component.set( 'v.classicLink', component.get( 'v.linkToRecord' ) ); 43 | } 44 | 45 | } 46 | 47 | // if linking to a record we first check if the expression evaluates to a field on the row object 48 | // that holds the value to link to, otherwise will use the value as-is for linking 49 | if ( !$A.util.isUndefinedOrNull( linkToURL ) ) { 50 | 51 | var parsedLinkToURL = helper.parseFieldValue( component, row, linkToURL ); 52 | 53 | if ( !$A.util.isUndefinedOrNull( parsedLinkToURL ) ) { 54 | component.set( 'v.linkToURL', parsedLinkToURL ); 55 | } else { 56 | component.set( 'v.linkToURL', linkToURL ); 57 | } 58 | 59 | if ( uiTheme === 'Classic' ) { 60 | component.set( 'v.classicLink', component.get( 'v.linkToURL' ) ); 61 | } 62 | 63 | } 64 | 65 | }, 66 | 67 | /** 68 | * For Salesforce1 and Lightning themes, action handler 69 | * for navigating to record or arbitrary URL. 70 | */ 71 | handleOnClick : function( component, event, helper ) { 72 | 73 | var linkToRecord = component.get( 'v.linkToRecord' ); 74 | var linkToURL = component.get( 'v.linkToURL' ); 75 | 76 | if ( !$A.util.isUndefinedOrNull( linkToRecord ) ) { 77 | helper.navigateToRecord( linkToRecord ); 78 | } else if ( !$A.util.isUndefinedOrNull( linkToURL ) ) { 79 | helper.navigateToURL( linkToURL ); 80 | } else { 81 | console.warn( 'Unexpected click event. No value for v.linkToRecord or v.linkToURL' ); 82 | console.log( event ); 83 | } 84 | 85 | } 86 | }) -------------------------------------------------------------------------------- /src/aura/DataTableCellCmp/DataTableCellCmpHelper.js: -------------------------------------------------------------------------------- 1 | ({ 2 | /** 3 | * Retrieves the field value from the JSON object 4 | * using dot notation to traverse the object graph. 5 | * If any part of the field path is not accessible then returns null. 6 | * 7 | * @param obj 8 | * The JSON object (e.g. { 'Account' : { 'Name' : 'Burlington' } } ) 9 | * @param fieldPath 10 | * Uses dot notation to represent the JSON field value to retrieve (e.g. 'Account.Name' ) 11 | */ 12 | parseFieldValue : function( component, obj, fieldPath ) { 13 | 14 | var fields = fieldPath.split( '.' ); 15 | 16 | var value = null; 17 | 18 | if ( obj.hasOwnProperty( fields[0] ) ) { 19 | 20 | value = obj[fields[0]]; 21 | 22 | if ( fields.length > 1 ) { 23 | 24 | for ( var i = 1; i < fields.length; i++ ) { 25 | if ( value != null && value.hasOwnProperty( fields[i] ) ) { 26 | value = value[fields[i]]; 27 | } else { 28 | value = null; 29 | break; 30 | } 31 | } 32 | 33 | } 34 | 35 | } 36 | 37 | return value; 38 | }, 39 | 40 | // ---------------------------------------------------------------------------- 41 | 42 | /** 43 | * Quick and dirty check to determine if code is running in 44 | * - Classic 45 | * - Salesforce1 46 | * - Lightning 47 | * based on the existence of certain features. 48 | */ 49 | getUITheme : function() { 50 | 51 | var theme = null; 52 | 53 | var event = $A.get( 'e.force:navigateToSObject' ); 54 | 55 | if ( event ) { 56 | 57 | theme = 'Lightning'; 58 | 59 | } else if ( ( typeof sforce !== 'undefined' ) && ( typeof sforce.one !== 'undefined' ) ) { 60 | 61 | theme = 'Salesforce1'; 62 | 63 | } else { 64 | 65 | theme = 'Classic' 66 | 67 | } 68 | 69 | return theme; 70 | }, 71 | 72 | navigateToRecord : function( recordId ) { 73 | 74 | console.log( 'navigating to record: ' + recordId ); 75 | 76 | var event = $A.get( 'e.force:navigateToSObject' ); 77 | 78 | if ( event ) { 79 | 80 | event.setParams({ 81 | 'recordId' : recordId 82 | }).fire(); 83 | 84 | } else if ( ( typeof sforce !== 'undefined' ) && ( typeof sforce.one !== 'undefined' ) ) { 85 | 86 | sforce.one.navigateToSObject( recordId ); 87 | 88 | } else { 89 | 90 | window.location.href = '/' + recordId; 91 | 92 | } 93 | 94 | }, 95 | 96 | navigateToURL : function( url ) { 97 | 98 | console.log( 'navigating to url: ' + url ); 99 | 100 | var event = $A.get( 'e.force:navigateToURL' ); 101 | 102 | if ( event ) { 103 | 104 | event.setParams({ 105 | 'url' : url 106 | }).fire(); 107 | 108 | } else if ( ( typeof sforce !== 'undefined' ) && ( typeof sforce.one !== 'undefined' ) ) { 109 | 110 | sforce.one.navigateToURL( url ); 111 | 112 | } else { 113 | 114 | window.location.href = url; 115 | 116 | } 117 | 118 | } 119 | }) -------------------------------------------------------------------------------- /src/aura/DataTableCmp/DataTableCmp.cmp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 13 | 14 | 15 | 16 | 20 | 21 | 25 | 26 | 29 | 30 | 33 | 34 | 35 | 36 | 40 | 41 | 45 | 46 | 50 | 51 | 52 | 53 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | {!column} 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 84 | 85 | 86 | 87 | 88 |
79 | 83 |
89 | 90 |
-------------------------------------------------------------------------------- /src/aura/DataTableCmp/DataTableCmp.cmp-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 38.0 4 | A Lightning Component Bundle 5 | 6 | -------------------------------------------------------------------------------- /src/aura/DataTableCmp/DataTableCmpController.js: -------------------------------------------------------------------------------- 1 | ({ 2 | doInit : function( component, event, helper ) { 3 | 4 | // since these components are set via attribute and 5 | // not known at compile time then is not 6 | // correctly registering our event listener as anticipated. 7 | // 8 | // therefore, we dynamically add our event listener to all 9 | // added columns upon initialization. 10 | // https://developer.salesforce.com/docs/atlas.en-us.lightning.meta/lightning/js_cb_dynamic_handler.htm 11 | 12 | var columns = component.get( 'v.columns' ); 13 | for ( var i = 0; i < columns.length; i++ ) { 14 | columns[i].addHandler( 'sortChangeEvent', component, 'c.handleSortChangeEvent' ); 15 | } 16 | 17 | // default sort column if not specified yet 18 | if ( columns.length > 0 ) { 19 | 20 | var sortColumnName = component.get( 'v.sortColumnName' ); 21 | var sortDirection = component.get( 'v.sortDirection' ); 22 | 23 | if ( $A.util.isUndefinedOrNull( sortColumnName ) ) { 24 | sortColumnName = columns[0].get( 'v.name' ); 25 | } 26 | 27 | if ( $A.util.isUndefinedOrNull( sortDirection ) ) { 28 | sortDirection = 'asc'; 29 | } 30 | 31 | helper.syncColumnStates( component, sortColumnName, sortDirection ); 32 | 33 | } 34 | 35 | // notify listeners to react and display initial page of data 36 | // this will send v.page and v.pageSize initial attribute values 37 | helper.firePageChangeEvent( component, component.get( 'v.pageNumber' ), component.get( 'v.pageSize' ) ); 38 | 39 | }, 40 | 41 | /** 42 | * Toggles the sort state of all the columns to reflect the 43 | * currently sorted column captured by the sort change event. 44 | */ 45 | handleSortChangeEvent : function( component, event, helper ) { 46 | 47 | // column requested to sort data by 48 | var sortColumnName = event.getParam( 'columnName' ); 49 | var sortDirection = event.getParam( 'sortDirection' ); 50 | 51 | helper.syncColumnStates( component, sortColumnName, sortDirection ); 52 | 53 | } 54 | }) -------------------------------------------------------------------------------- /src/aura/DataTableCmp/DataTableCmpHelper.js: -------------------------------------------------------------------------------- 1 | ({ 2 | /** 3 | * Notify that there's a request for specific page of data. 4 | * 5 | * It is the responsibility of the developer to listen for the 6 | * page change event and update the table's rows accordingly. 7 | */ 8 | firePageChangeEvent : function( component, pageNumber, pageSize ) { 9 | 10 | component.getEvent( 'pageChangeEvent' ).setParams({ 11 | 'pageNumber' : pageNumber, 12 | 'pageSize' : pageSize 13 | }).fire(); 14 | 15 | }, 16 | 17 | /** 18 | * Designed to be called by the renderer during infinite scrolling events. 19 | * Increments the page number then fires page change event. 20 | */ 21 | getNextPage : function( component ) { 22 | 23 | var pageNumber = component.get( 'v.pageNumber' ); 24 | var pageSize = component.get( 'v.pageSize' ); 25 | 26 | pageNumber++; 27 | 28 | component.set( 'v.pageNumber', pageNumber ); 29 | component.set( 'v.pageSize', pageSize ); 30 | 31 | this.firePageChangeEvent( component, pageNumber, pageSize ); 32 | 33 | }, 34 | 35 | /** 36 | * Designed to be called whenever a sort change event is fired. 37 | * The component iterates through all the columns attribute and 38 | * updates their isSorted status so that the table column headers 39 | * reflect the UI of the user's sorting intent. 40 | * 41 | * It is the responsibility of the developer to also listen for the 42 | * sort change event and update the table's rows accordingly. 43 | */ 44 | syncColumnStates : function( component, sortColumnName, sortDirection ) { 45 | 46 | // for all columns update their attributes 47 | // to indicate if it is the sorted column or not 48 | var columns = component.get( 'v.columns' ); 49 | for ( var i = 0; i < columns.length; i++ ) { 50 | 51 | var column = columns[i]; 52 | var columnName = column.get( 'v.name' ); 53 | 54 | if ( sortColumnName === columnName ) { 55 | 56 | column.set( 'v.isSorted', true ); 57 | column.set( 'v.sortDirection', sortDirection ); 58 | 59 | component.set( 'v.sortColumnName', sortColumnName ); 60 | component.set( 'v.sortDirection', sortDirection ); 61 | 62 | } else { 63 | 64 | column.set( 'v.isSorted', false ); 65 | 66 | } 67 | 68 | } 69 | 70 | } 71 | }) -------------------------------------------------------------------------------- /src/aura/DataTableCmp/DataTableCmpRenderer.js: -------------------------------------------------------------------------------- 1 | ({ 2 | afterRender : function( component, helper ) { 3 | 4 | this.superAfterRender(); 5 | 6 | // this is done in renderer because we don't get 7 | // access to the window element in the helper js. 8 | 9 | // per John Resig, we should not take action on every scroll event 10 | // as that has poor performance but rather we should take action periodically. 11 | // http://ejohn.org/blog/learning-from-twitter/ 12 | 13 | var didScroll = false; 14 | 15 | window.onscroll = function() { 16 | didScroll = true; 17 | }; 18 | 19 | // periodically attach the scroll event listener 20 | // so that we aren't taking action for all events 21 | var scrollCheckIntervalId = setInterval( $A.getCallback( function() { 22 | 23 | // since this function is called asynchronously outside the component's lifecycle 24 | // we need to check if the component still exists before trying to do anything else 25 | if ( didScroll && component.isValid() ) { 26 | 27 | didScroll = false; 28 | 29 | // adapted from stackoverflow to detect when user has scrolled sufficiently to end of document 30 | // http://stackoverflow.com/questions/4841585/alternatives-to-jquery-endless-scrolling 31 | if ( window['scrollY'] >= document.body['scrollHeight'] - window['outerHeight'] - 100 ) { 32 | helper.getNextPage( component ); 33 | } 34 | 35 | } 36 | 37 | }), 1000 ); 38 | 39 | component.set( 'v.scrollCheckIntervalId', scrollCheckIntervalId ); 40 | 41 | }, 42 | 43 | unrender : function( component, helper ) { 44 | 45 | this.superUnrender(); 46 | 47 | var scrollCheckIntervalId = component.get( 'v.scrollCheckIntervalId' ); 48 | 49 | if ( !$A.util.isUndefinedOrNull( scrollCheckIntervalId ) ) { 50 | window.clearInterval( scrollCheckIntervalId ); 51 | } 52 | 53 | } 54 | }) -------------------------------------------------------------------------------- /src/aura/DataTableColumnCmp/DataTableColumnCmp.cmp: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 9 | 10 | 14 | 15 | 19 | 20 | 24 | 25 | 29 | 30 | 34 | 35 | 39 | 40 | 43 | 44 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 63 | 64 | 65 | 66 | 67 | 68 | Sort 69 | {!v.label} 70 | 71 | 75 | 76 | 80 | 81 | 82 | 83 | 84 | 85 | {!v.label} 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /src/aura/DataTableColumnCmp/DataTableColumnCmp.cmp-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 38.0 4 | A Lightning Component Bundle 5 | 6 | -------------------------------------------------------------------------------- /src/aura/DataTableColumnCmp/DataTableColumnCmpController.js: -------------------------------------------------------------------------------- 1 | ({ 2 | fireSortChangeEvent : function( component, event, helper ) { 3 | 4 | var sortable = component.get( 'v.sortable' ); 5 | 6 | if ( sortable == true ) { 7 | 8 | var sortDirection = component.get( 'v.sortDirection' ); 9 | 10 | if ( sortDirection === 'asc' ) { 11 | sortDirection = 'desc'; 12 | } else { 13 | sortDirection = 'asc'; 14 | } 15 | 16 | component.getEvent( 'sortChangeEvent' ).setParams({ 17 | 'columnLabel' : component.get( 'v.label' ), 18 | 'columnName' : component.get( 'v.name' ), 19 | 'sortDirection' : sortDirection 20 | }).fire(); 21 | 22 | } 23 | 24 | } 25 | }) -------------------------------------------------------------------------------- /src/aura/DataTableDemoApp/DataTableDemoApp.app: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 25 | 26 | 30 | 31 | 35 | 36 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /src/aura/DataTableDemoApp/DataTableDemoApp.app-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 38.0 4 | A Lightning Component Bundle 5 | 6 | -------------------------------------------------------------------------------- /src/aura/DataTableDemoApp/DataTableDemoApp.css: -------------------------------------------------------------------------------- 1 | /* https://github.com/salesforce-ux/design-system/issues/357 */ 2 | .THIS.slds-spinner_container { 3 | position: fixed; 4 | } -------------------------------------------------------------------------------- /src/aura/DataTableDemoApp/DataTableDemoAppController.js: -------------------------------------------------------------------------------- 1 | ({ 2 | doInit: function( component, event, helper ) { 3 | 4 | }, 5 | 6 | handlePageChangeEvent : function( component, event, helper ) { 7 | 8 | var tableCmp = component.find( 'dataTable' ); 9 | 10 | console.log( 'handling page change event in app container' ); 11 | console.log( 'columnName=' + tableCmp.get( 'v.sortColumnName' ) ); 12 | console.log( 'sortDirection=' + tableCmp.get( 'v.sortDirection' ) ); 13 | console.log( 'page=' + event.getParam( 'pageNumber' ) ); 14 | console.log( 'pageSize=' + event.getParam( 'pageSize' ) ); 15 | 16 | helper.callAction( 17 | component, 18 | 'c.getContacts', 19 | { 20 | 'page' : event.getParam( 'pageNumber' ), 21 | 'pageSize' : event.getParam( 'pageSize' ), 22 | 'sortCol' : tableCmp.get( 'v.sortColumnName' ), 23 | 'sortDir' : tableCmp.get( 'v.sortDirection' ) 24 | }, 25 | function( data ) { 26 | 27 | var tableCmp = component.find( 'dataTable' ); 28 | 29 | var rows = tableCmp.get( 'v.rows' ); 30 | 31 | tableCmp.set( 'v.rows', rows.concat( data ) ); 32 | 33 | } 34 | ); 35 | 36 | }, 37 | 38 | handleSortChangeEvent : function( component, event, helper ) { 39 | 40 | var tableCmp = component.find( 'dataTable' ); 41 | 42 | tableCmp.set( 'v.pageNumber', 1 ); 43 | 44 | console.log( 'handling sort event in app container' ); 45 | console.log( 'columnName=' + event.getParam( 'columnName' ) ); 46 | console.log( 'sortDirection=' + event.getParam( 'sortDirection' ) ); 47 | console.log( 'pageNumber=' + tableCmp.get( 'v.pageNumber' ) ); 48 | console.log( 'pageSize=' + tableCmp.get( 'v.pageSize' ) ); 49 | 50 | helper.callAction( 51 | component, 52 | 'c.getContacts', 53 | { 54 | 'page' : tableCmp.get( 'v.pageNumber' ), 55 | 'pageSize' : tableCmp.get( 'v.pageSize' ), 56 | 'sortCol' : event.getParam( 'columnName' ), 57 | 'sortDir' : event.getParam( 'sortDirection' ) 58 | }, 59 | function( data ) { 60 | 61 | var tableCmp = component.find( 'dataTable' ); 62 | 63 | tableCmp.set( 'v.rows', data ); 64 | 65 | } 66 | ); 67 | 68 | } 69 | }) -------------------------------------------------------------------------------- /src/aura/DataTableDemoApp/DataTableDemoAppHelper.js: -------------------------------------------------------------------------------- 1 | ({ 2 | showSpinner : function( component ) { 3 | 4 | $A.util.removeClass( component.find( 'spinner' ), 'slds-hide' ); 5 | 6 | }, 7 | 8 | hideSpinner : function( component ) { 9 | 10 | $A.util.addClass( component.find( 'spinner' ), 'slds-hide' ); 11 | 12 | }, 13 | 14 | navigateToRecord : function( recordId ) { 15 | 16 | console.log( 'navigating to record: ' + recordId ); 17 | 18 | var event = $A.get( 'e.force:navigateToSObject' ); 19 | 20 | if ( event ) { 21 | 22 | event.setParams({ 23 | 'recordId' : recordId 24 | }).fire(); 25 | 26 | } else if ( ( typeof sforce !== 'undefined' ) && ( typeof sforce.one !== 'undefined' ) ) { 27 | 28 | sforce.one.navigateToSObject( recordId ); 29 | 30 | } else { 31 | 32 | window.location.href = '/' + recordId; 33 | 34 | } 35 | 36 | }, 37 | 38 | /** 39 | * actionName = the apex controller method to call (e.g. 'c.myMethod' ) 40 | * params = JSON object specifying action parameters (e.g. { 'x' : 42 } ) 41 | * successCallback = function to call when action completes (e.g. function( response ) { ... } ) 42 | * failureCallback = function to call when action fails (e.g. function( response ) { ... } ) 43 | */ 44 | callAction : function( component, actionName, params, successCallback, failureCallback ) { 45 | 46 | this.showSpinner( component ); 47 | 48 | var action = component.get( actionName ); 49 | 50 | if ( params ) { 51 | action.setParams( params ); 52 | } 53 | 54 | action.setCallback( this, function( response ) { 55 | 56 | this.hideSpinner( component ); 57 | 58 | if ( component.isValid() && response.getState() === 'SUCCESS' ) { 59 | 60 | if ( successCallback ) { 61 | successCallback( response.getReturnValue() ); 62 | } 63 | 64 | } else { 65 | 66 | console.error( 'Error calling action "' + actionName + '" with state: ' + response.getState() ); 67 | 68 | if ( failureCallback ) { 69 | failureCallback( response.getError(), response.getState() ); 70 | } else { 71 | this.logActionErrors( component, response.getError() ); 72 | } 73 | 74 | } 75 | }); 76 | 77 | $A.enqueueAction( action ); 78 | 79 | }, 80 | 81 | logActionErrors : function( component, errors ) { 82 | if ( errors ) { 83 | for ( var index in errors ) { 84 | console.error( 'Error: ' + errors[index].message ); 85 | } 86 | } else { 87 | console.error( 'Unknown error' ); 88 | } 89 | } 90 | }) -------------------------------------------------------------------------------- /src/aura/DataTablePageChangeEvent/DataTablePageChangeEvent.evt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/aura/DataTablePageChangeEvent/DataTablePageChangeEvent.evt-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 38.0 4 | A Lightning Component Bundle 5 | 6 | -------------------------------------------------------------------------------- /src/aura/DataTableSortChangeEvent/DataTableSortChangeEvent.evt: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | 7 | 10 | 11 | 14 | 15 | -------------------------------------------------------------------------------- /src/aura/DataTableSortChangeEvent/DataTableSortChangeEvent.evt-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 38.0 4 | A Lightning Component Bundle 5 | 6 | -------------------------------------------------------------------------------- /src/classes/DataTableDemoAppController.cls: -------------------------------------------------------------------------------- 1 | public with sharing class DataTableDemoAppController { 2 | 3 | @AuraEnabled 4 | public static List getContacts( Decimal page, Decimal pageSize, String sortCol, String sortDir ) { 5 | 6 | System.debug('querying contacts'); 7 | System.debug('page=' + page); 8 | System.debug('pageSize=' + pageSize); 9 | System.debug('sortCol=' + sortCol); 10 | System.debug('sortDir=' + sortDir); 11 | 12 | Integer skipRecords = Integer.valueOf( ( page - 1 ) * pageSize ); 13 | Integer maxRecords = Integer.valueOf( pageSize ); 14 | 15 | String query = 16 | ' SELECT ' + 17 | ' id, firstName, lastName, accountId, account.name ' + 18 | ' FROM ' + 19 | ' Contact ' 20 | ; 21 | 22 | if ( String.isNotBlank( sortCol ) ) { 23 | query += ' ORDER BY ' + String.escapeSingleQuotes( sortCol ) + ' ' + String.escapeSingleQuotes( sortDir ); 24 | } 25 | 26 | query += ' LIMIT :maxRecords '; 27 | 28 | query += ' OFFSET :skipRecords '; 29 | 30 | List contacts = (List) Database.query( query ); 31 | 32 | //List contacts = [ 33 | // SELECT 34 | // id, firstName, lastName, accountId, account.name 35 | // FROM 36 | // Contact 37 | // ORDER BY 38 | // firstName 39 | // LIMIT 40 | // :maxRecords 41 | // OFFSET 42 | // :skipRecords 43 | //]; 44 | 45 | System.debug('queried contacts'); 46 | System.debug( contacts ); 47 | 48 | return contacts; 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /src/classes/DataTableDemoAppController.cls-meta.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 38.0 4 | Active 5 | 6 | -------------------------------------------------------------------------------- /src/package.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | DataTableDemoAppController 5 | ApexClass 6 | 7 | 8 | DataTableCellCmp 9 | DataTableCmp 10 | DataTableColumnCmp 11 | DataTableDemoApp 12 | DataTablePageChangeEvent 13 | DataTableSortChangeEvent 14 | AuraDefinitionBundle 15 | 16 | 38.0 17 | 18 | --------------------------------------------------------------------------------