• Michael Friedman 7
  • NEWBIE
  • 10 Points
  • Member since 2016

  • Chatter
    Feed
  • 0
    Best Answers
  • 0
    Likes Received
  • 0
    Likes Given
  • 2
    Questions
  • 4
    Replies
I am new to visualforce and am having trouble getting sorting working on my page. My page has two pageblocktables on it that pull data from two different object dynamically based on fields that users input into a custom setting. I would like both tables to be sortable independently. The solution I have is working for one table, but not the other. As far as I can tell the code for both tables is identical. Not sure why there is a difference in behavior.

The table that works is pulling data from an object called Timesheet__c. It is the second table on the page. The table that doesn't is the first and is pulling from an object called Expense_Line__c.

Any help would be much appreciated!

Here is my page:
 
<apex:page controller="invoicePickerClass" lightningStylesheets="TRUE">
  <apex:form>

    <!-- Output panel to make the buttons live side-by-side -->
    <apex:outputPanel id="thePanel" layout="block">

      <!-- Button to get Save changes and stay on form -->
      <apex:commandButton action="{!getSelected}" value="Save" id="Save"/>

      <!-- Button to Save changes and return to Invoice -->
      <apex:commandButton action="{!getSelectedandLeave}" value="Save and Done" id="SaveAndDone"/>
    </apex:outputPanel>
    
    <!--Panel grid that holds the two tables  -->
    <!-- <apex:panelGrid columns="1" width="100%" columnClasses="pgCol"> -->
    
      <!-- Expense Line Table -->
      <apex:pageBlock title="Expense Line List" id="expenseLines">
        <apex:pageBlockSection columns="1">
            
            <!-- ExpenseLine List -->
          <apex:pageBlockTable value="{! ExpenseLines }" var="el" rendered="{!(expenseLines.size != 0)}" cellpadding="5">
            
            <!-- Checkbox Column -->
            <apex:column >
              <apex:facet name="header">
                <apex:inputCheckbox styleClass="checkEL" value="{!el.isSelectedEL}" onclick="checkAll(this,'checkEL')"/>
              </apex:facet>
                <apex:inputCheckbox styleClass="checkEL" value="{!el.isSelectedEL}" id="checkedone">
                </apex:inputCheckbox>
            </apex:column>

            <!-- Dynamic Table Columns -->
            <apex:repeat value="{!ELCol}" var="elCol">
            <apex:column> 
              <apex:facet name="header">
                <apex:commandLink action="{!elSortDir}" immediate="true" reRender="expenseLines" value="{!$ObjectType.Expense_Line__c.Fields[elCol].Label }">
                  <apex:param name="sortParam" assignTo="{!elSort}" value="{!elCol}"/>
                  <apex:outputPanel>
                    <apex:image value="{!IF(elSort = elCol,$Resource[elSortIMG], $Resource.Sortable)}"/>
                  </apex:outputPanel>
                </apex:commandLink>
              </apex:facet>
              <apex:outputField value="{! el.exp[elCol] }"/> 
            </apex:column>
            </apex:repeat>

            <!-- Make table inline editable -->
            <apex:inlineEditSupport event="ondblClick"/>
          </apex:pageBlockTable>

          <!-- Text to display if no data in the table -->
          <apex:outputText rendered="{!(expenseLines.size = 0)}" value="There are no Expenses to display." />
        </apex:pageBlockSection>            
      </apex:pageBlock>


      <!-- Timesheets Table -->
      <apex:pageBlock title="TimeSheet List" id="timeSheets">
        <apex:pageBlockSection columns="1">
            
          <!-- TimeSheet List -->
          <apex:pageBlockTable value="{! TimeSheets }" var="ts" rendered="{!(TimeSheets.size != 0)}" cellpadding="5">
                
            <!-- Checkbox Column -->
            <apex:column>
              <apex:facet name="header">
                <apex:inputCheckbox styleClass="checkTS" value="{!ts.isSelectedTS}" onclick="checkAll(this,'checkTS')"/>
              </apex:facet>
                <apex:inputCheckbox styleClass="checkTS" value="{!ts.isSelectedTS}" id="checkedone">
                </apex:inputCheckbox>
            </apex:column>

            <!-- Dynamic table column -->
            <apex:repeat value="{!TSCol}" var="tsCol">
            <apex:column>
              <apex:facet name="header">
                <apex:commandLink action="{!tsSortDir}" immediate="true" reRender="timeSheets" value="{!$ObjectType.Timesheet__c.Fields[tsCol].Label }">
                  <apex:param name="sortParam" assignTo="{!tsSort}" value="{!tsCol}"/>
                  <apex:outputPanel>
                    <apex:image value="{!IF(tsSort = tsCol,$Resource[tsSortIMG], $Resource.Sortable)}"/>
                  </apex:outputPanel>
                </apex:commandLink>
              </apex:facet>
              <apex:outputField value="{! ts.tsh[tsCol] }"/> 
            </apex:column>
            </apex:repeat>

            <!-- Make table inline editable -->
            <apex:inlineEditSupport event="ondblClick"/>
          </apex:pageBlockTable>

          <!-- Text to display if no data for table -->
          <apex:outputText rendered="{!(TimeSheets.size = 0)}" value="There are no TimeSheets to display."/>
        </apex:pageBlockSection>
      </apex:pageBlock>
   
      
    <!-- </apex:panelGrid> -->
  </apex:form>

  <!-- Script that runs when check all is selected -->
  <script>
    function checkAll(cb, check)
    {
        var inputElem = document.getElementsByClassName(check);
        for(var i=0; i<inputElem.length; i++)
        {
            if(inputElem[i].id.indexOf("checkedone")!=-1)
            inputElem[i].checked = cb.checked;
        }
    }
  </script>
  
  <!-- Style needed if the tables are moved side-by-side -->
  <style type="text/css">
    .pgCol{
      width:50%;
    }
  </style>
</apex:page>

Here is my apex code:
 
public with sharing class invoicePickerClass {

	//URL and other Parameters 
	public String invId {get; set;}
	public String projId {get; set;}
	public static String recId {get; set;}

	//Sort Variables
	public String tsSort {get; set;}
	public String tsSortLast {get; set;}
	transient String tsSortDir {get; set;}
	public String tsSortDirLast {get; set;}
	
	public String elSort {get; set;}
	public String elSortLast {get; set;}
	transient String elSortDir {get; set;}
	public String elSortDirLast {get; set;}

	//Sort images
    public String tsSortIMG {get;set;}
    public String elSortIMG {get;set;}
	
	//Lists for synamic columns
	List<String> tsCol = new List<string>();
	List<String> elCol = new List<string>();

    //Wrapper class data
    public List<tsWrapper> tsList {get; set;}
    List<Timesheet__c> selectedTimeSheets = new List<Timesheet__c>();
    List<Timesheet__c> unSelectedTimeSheets = new List<Timesheet__c>();
    public List<elWrapper> elList {get; set;}
    List<Expense_Line__c> selectedExpenseLines = new List<Expense_Line__c>();
    List<Expense_Line__c> unSelectedExpenseLines = new List<Expense_Line__c>();

    //method to populate time sheet table using wrapper class
    public  List<tsWrapper> getTimeSheets() {

    		invId = Apexpages.currentPage().getparameters().get('invId');

    		projId = Apexpages.currentPage().getparameters().get('projId');

    		system.debug(tsSort+tsSortLast+tsSortDir+tsSortDirLast+tsSortIMG);

    		if(tsSort == NULL){tsSort = 'Date__c'; tsSortDir = 'DESC';}

    		system.debug(tsSort+tsSortLast+tsSortDir+tsSortDirLast+tsSortIMG);

    		tsSortDir();
    		tsSortLast = tsSort;

    		system.debug(tsSort+tsSortLast+tsSortDir+tsSortDirLast+tsSortIMG);

    		List<string> columns = getColumns('Timesheet__c');

    		String qry = 'select Id, Invoice__c, Project__c, '+String.join(columns, ',')+' FROM Timesheet__c WHERE Project__c = :projId AND (Invoice__c = \'\' OR Invoice__c = :invId) ORDER BY '+tsSort+' '+tsSortDir;
            
           // if(tsList == NULL){
            	tsList = new List<tsWrapper>();
            	for(Timesheet__c tsh : Database.query(qry)){
            		tsList.add(new tsWrapper(tsh));
            			
            	}
           // }

            return tsList;
    }

    
    //method to populate expense line table using wrapper class
    public  List<elWrapper> getExpenseLines() {

    		invId = Apexpages.currentPage().getparameters().get('invId');

    		projId = Apexpages.currentPage().getparameters().get('projId');

    		system.debug(elSort+elSortLast+elSortDir+elSortDirLast+elSortIMG);

    		if(elSort == NULL){elSort = 'Date__c'; elSortDir = 'DESC';}

    		system.debug(elSort+elSortLast+elSortDir+elSortDirLast+elSortIMG);

    		elSortDir();
    		elSortLast = elSort;

    		system.debug(elSort+elSortLast+elSortDir+elSortDirLast+elSortIMG);

    		List<string> columns = getColumns('Expense_Line__c');

    		String qry = 'select Id, Invoice__c, Project__c, '+String.join(columns, ',')+' FROM Expense_Line__c WHERE Project__c = :projId AND (Invoice__c = \'\' OR Invoice__c = :invId) ORDER BY '+elSort+' DESC';

            //if(elList == NULL){
            elList = new List<elWrapper>();
            	for(Expense_Line__c exp : Database.query(qry)){
            		elList.add(new elWrapper(exp));	
            	}
            //}

            return elList;
    }

    public List<String> getColumns(string obj){
    	List<InvoicePicker__c> ipCol = new List<InvoicePicker__c>();
    	List<String> columns = new List<String>();
    	if(obj=='Timesheet__c'){
    		ipCol = [select Field__c FROM InvoicePicker__c WHERE Object__c = :obj ORDER BY Column_Order__c ASC];
    	}
    	else if(obj=='Expense_Line__c'){
    		ipCol = [select Field__c FROM InvoicePicker__c WHERE Object__c = :obj ORDER BY Column_Order__c ASC];
    	}
    	for(InvoicePicker__c ip : ipCol){
    		columns.add(ip.Field__c);
    	}
    	return columns;
    }

    //method for dynamic columns for Timesheets
    public List<String> getTSCol(){
    	tsCol.clear();
    	tsCol.addAll(getColumns('Timesheet__c'));
    	return tsCol;

    }

    //method for dynamic columns for Expense Lines
    public List<String> getELCol(){
    	elCol.clear();
    	elCol.addAll(getColumns('Expense_Line__c'));
    	return elCol;

    }


    //Sort data table action
    public void tsSortDir() {
		
		if(tsSortLast == tsSort && tsSortDirLast == 'DESC'){tsSortDir = 'ASC';}
		else if(tsSortLast == tsSort && tsSortDirLast == 'ASC'){tsSortDir = 'DESC';}
		else{tsSortDir = 'DESC';}

		tsSortDirLast = tsSortDir;
		tsSortIMG = tsSortDir;
    }

    public void elSortDir() {
		
		if(elSortLast == elSort && elSortDirLast == 'DESC'){elSortDir = 'ASC';}
		else if(elSortLast == elSort && elSortDirLast == 'ASC'){elSortDir = 'DESC';}
		else{elSortDir = 'DESC';}

		elSortDirLast = elSortDir;
		elSortIMG = elSortDir;
    }

	//timesheet wrapper class
    public class tsWrapper{
        public Timesheet__c tsh {get; set;}
        public Boolean isSelectedTS {get; set;}
        public tsWrapper(Timesheet__c ts){
            tsh = ts;
            if(tsh.Invoice__c != NULL){
            	isSelectedTS = true;
            }
            else{isSelectedTS = false;}
        }
    }

    //Expense Line Wrapper
    public class elWrapper{
        public Expense_Line__c exp{get; set;}
        public Boolean isSelectedEL {get; set;}
        public elWrapper(Expense_Line__c el){
            exp = el;
            if(exp.Invoice__c != NULL){
            	isSelectedEL = true;
            }
            else{isSelectedEL = false;}
        }
    }


    //Original Processing of selected items
    public PageReference getSelectedEL(){
        selectedExpenseLines.clear();
        unSelectedExpenseLines.clear();
        for(elWrapper elWrapper : this.elList){
	        if(elWrapper.isSelectedEL == true) {
		        selectedExpenseLines.add(elWrapper.exp);
	    	}
	    	if(elWrapper.isSelectedEL == false){
	    		unSelectedExpenseLines.add(elWrapper.exp);
	    	}
	    }
       	return null;
    }

	public PageReference getSelectedTS(){
        selectedTimeSheets.clear();
        unSelectedTimeSheets.clear();

        for(tsWrapper tsWrapper : this.tsList){
	        if(tsWrapper.isSelectedTS == true) {
		        selectedTimeSheets.add(tsWrapper.tsh);
	    	}
	    	if(tsWrapper.isSelectedTS == false){
	    		unSelectedTimeSheets.add(tsWrapper.tsh);
	    	}
	    }
       	return null;
    }

     //wrapper class for Comparable Interface
    
    //public class sortN implements Comparable {
    //    public String sortValue {get;set;}    
        
    //    public sortN(String sortName) {
    //        sortValue = sortName;
    //    }
        
    //    public Integer compareTo(Object ObjToCompare) {
    //        return sortValue.CompareTo(((sortN)ObjToCompare).sortValue);
    //    }
    //}
     
    


    //Button method

    public PageReference getSelected(){
    	//get all selected and unselected records
		getSelectedEL();
    	getSelectedTS();
    	
    	//process and update all selected and unselected records
    	processTimeSheets();
    	processExpenseLines();
    	

    	
    	return NULL;
    }

     public PageReference getSelectedandLeave(){
    	//get all selected and unselected records
    	getSelectedEL();
    	getSelectedTS();
    	
    	//process and update all selected and unselected records
    	processTimeSheets();
    	processExpenseLines();
    	
    	//navigate back to invoice record
    	PageReference pr = new PageReference('/'+invId);
        system.debug(pr);
        pr.setRedirect(true);
    	
    	return pr;
    }

    public void processTimeSheets(){

    	List<Timesheet__c> processedTimeSheets = new List<Timesheet__c>();

    	for(Timesheet__c t : selectedTimeSheets){
    		Timesheet__c newTimeSheet = new Timesheet__c(id=t.Id);
    		newTimeSheet.Invoice__c = invId;
    		Map<String, SObjectField> m = Timesheet__c.SObjectType.getDescribe().fields.getMap();
    		for(String f : tsCol){
    			DescribeFieldResult r = m.get(f).getDescribe();
		      	if(r.isUpdateable()){
    				newTimeSheet.put(f, t.get(f));
		      	}	
    		}
    		processedTimeSheets.add(newTimeSheet);
    	}

    	for(Timesheet__c t : unSelectedTimeSheets){
    		Timesheet__c newTimeSheet = new Timesheet__c(id=t.Id);
    		newTimeSheet.Invoice__c = null;
    		Map<String, SObjectField> m = Timesheet__c.SObjectType.getDescribe().fields.getMap();
    		for(String f : tsCol){
    			DescribeFieldResult r = m.get(f).getDescribe();
		      	if(r.isUpdateable()){
    				newTimeSheet.put(f, t.get(f));
		      	}	
    		}
    		processedTimeSheets.add(newTimeSheet);
    	}

    	update processedTimeSheets;
    }

    public void processExpenseLines(){

    	List<Expense_Line__c> processedExpenseLines = new List<Expense_Line__c>();

    	for(Expense_Line__c e : selectedExpenseLines){
    		Expense_Line__c newExpenseLines = new Expense_Line__c(id=e.Id);
    		newExpenseLines.Invoice__c = invId;
    		Map<String, SObjectField> m = Expense_Line__c.SObjectType.getDescribe().fields.getMap();
    		for(String f : elCol){
    			DescribeFieldResult r = m.get(f).getDescribe();
		      	if(r.isUpdateable()){
    				newExpenseLines.put(f, e.get(f));
		      	}
    			
    		}

    		processedExpenseLines.add(newExpenseLines);
    	}

    	for(Expense_Line__c e : unSelectedExpenseLines){
    		Expense_Line__c newExpenseLines = new Expense_Line__c(id=e.Id);
    		newExpenseLines.Invoice__c = null;
    		Map<String, SObjectField> m = Expense_Line__c.SObjectType.getDescribe().fields.getMap();
    		for(String f : elCol){
    			DescribeFieldResult r = m.get(f).getDescribe();
		      	if(r.isUpdateable()){
    				newExpenseLines.put(f, e.get(f));
    			}
    		}

    		processedExpenseLines.add(newExpenseLines);
    	}
    	update processedExpenseLines;
    }

}

 
I am using the Expense__c tracker app as a template to create a custom lightning component for adding new Events. When I click save on the form, the log in the developer console returns an error: Attempted to upsert a null list.

Here is my Component:
 
<aura:component controller="EventController" implements="force:appHostable,flexipage:availableForAllPageTypes" access="global">
<aura:attribute name="allDay" type="Boolean" default="False"/>
<aura:attribute name="events" type="Event[]"/>
<aura:attribute name="newEvent" type="Event"
                 default="{ 'sobjectType': 'Event',
                         'Subject': 'Default Subject',
                         'OwnerId': '005j000000BRyZ1AAL',
                         'StartDateTime': 'now()',
                         
                         'WhoId': '005j000000BRyZ1AAL',
                         'WhatId': '00Q63000001qAPqEAM',
                         'Consult_Amount__c': '123.10',
                         'Event_Status__c': 'Completed',
                         'Event_Type__c': 'Lunch',
                         'DurationInMinutes': '30'
                       }"/>


<!--Assigned To Form Element -->
<ui:inputSelect label="Assigned To" multiple="false" required="true">
    <ui:inputSelectOption text="All Primary" label="All Contacts" value="true"/>
    <ui:inputSelectOption text="All Primary" label="All Primary"/>
    <ui:inputSelectOption text="All Secondary" label="All Secondary"/>
</ui:inputSelect>
    
<!--Subject Form Element -->	
<ui:inputText label="Subject"  placeholder="Enter Subject" value="{!v.newEvent.Subject}"/>
    
<!--All Day Event Form Element -->
<ui:inputCheckbox label="All Day Event" value="{!v.newEvent.IsAllDayEvent}" />

<!--Renders Date or DateTime UI Input based on All Day Event Check Box -->    
    <aura:if isTrue="{!v.newEvent.IsAllDayEvent}">
        
<!--Start/End Date Form Elements -->
<ui:inputDate aura:id="startdate" label="Start Date" value="{!v.newEvent.StartDateTime}" displayDatePicker="true" />
<ui:inputDate aura:id="enddate" label="End Date" value="{!v.newEvent.EndDateTime}" displayDatePicker="true" />
        <aura:set attribute="else">
            
<!--Start/End DateTime Form Elements -->
<ui:inputDateTime aura:id="startdatetime" label="Start Date" class="field" value="{!v.newEvent.StartDateTime}" displayDatePicker="true" />
<ui:inputDateTime aura:id="enddatetime" label="End Date" class="field" value="{!v.newEvent.EndDateTime}" displayDatePicker="true" />    
    </aura:set>
    </aura:if>
    
<!--Invitee Form Element -->
<ui:inputSelect label="Invitees Of This Event" multiple="True" required="true">
    <ui:inputSelectOption text="All Primary" label="All Contacts" value="true"/>
    <ui:inputSelectOption text="All Primary" label="All Primary"/>
    <ui:inputSelectOption text="All Secondary" label="All Secondary"/>
</ui:inputSelect>
    
<!--Event Type Form Element -->
<ui:inputSelect label="Event Type" multiple="False" required="true">
    <ui:inputSelectOption text="All Primary" label="All Contacts" value="true"/>
    <ui:inputSelectOption text="All Primary" label="All Primary"/>
    <ui:inputSelectOption text="All Secondary" label="All Secondary"/>
</ui:inputSelect>
    
<!--Event Status Type Form Element -->
<ui:inputSelect label="Event Status Type" multiple="False" required="true">
    <ui:inputSelectOption text="Completed" label="Completed" value="true"/>
    <ui:inputSelectOption text="Canceled" label="Canceled"/>
    <ui:inputSelectOption text="No/Show" label="No/Show"/>
</ui:inputSelect>
    
<!--Rescheduled from Prior Consult Form Element -->
<ui:inputCheckbox label="Rescheduled from Prior Consult" value=""/>

<!--Is Paid Consult Form Element -->
<ui:inputCheckbox label="Is Paid Consult" value=""/>

<!--Consult Amount Form Element -->	
<ui:inputText label="Consult Amount" value="Consult Amount" required="true"/>
    
<!--Related To Form Element -->
<ui:inputSelect label="Related To" multiple="False" required="true">
    <ui:inputSelectOption text="Matter" label="Matter" value="true"/>
    <ui:inputSelectOption text="Prospect" label="Prospect"/>
</ui:inputSelect>

<!--Save Event Button-->
    <ui:button label="Save" press="{!c.createEvent}"/>
    
</aura:component>
Here is my JS Controller:
 
({
	createEvent : function(component, event, helper) {
    // Validate form fields
    // Pass form data to a helper function
    var newEvent = component.get("v.newEvent");
    helper.createEvent(component, newEvent);
}
})

Here is my Helper:
 
({
	createEvent: function(component, event) {
    //Save the event and update the view
    this.upsertEvent(component, event, function(a) {
        var events = component.get("v.events");
        
        events.push(a.getReturnValue());
        
        component.set("v.events", events);
       
    });
},
upsertEvent : function(component, event, callback) {
  var action = component.get("c.saveEvent");
  action.setParams({ 
      'event': event
  });
  if (callback) {
      action.setCallback(this, callback);
  }
  $A.enqueueAction(action);
}
})
Here is my Apex Controller:
public with sharing class EventController {
@AuraEnabled
    public static List<Event> getEvents() {
        String runningUser = UserInfo.getUserId();
        List<Event> events =  Database.query(
                'SELECT Id, Subject, WhoId, WhatId, EndDateTime, StartDateTime, Matter_Type__c, Color_Code__c, Type, Event_Type__c, OwnerId FROM Event WHERE OwnerId =  :runningUser AND (StartDateTime = LAST_N_DAYS:45 OR StartDateTime = NEXT_N_DAYS:45)');

        //Add isAccessible() check
        return events;
    }
    @AuraEnabled
    // Retrieve all primary contacts
    public static List<Event> getConsult() {
        List<Event> consultEvents = 
             [SELECT Id, Subject, WhoId, WhatId, EndDateTime, StartDateTime, ActivityDate, OwnerId, Type FROM Event WHERE Event_Type__c = 'Consult' AND OwnerId = '005j000000BRyZ1'];

        //Add isAccessible() check
        return consultEvents;
    }

  @AuraEnabled
public static Event saveEvent(Event event) {
    // Perform isUpdateable() check here 
    
   
    					
    upsert event;
    return event;
} 
    

  
    
}
The extra code in the controller is for another part of the app. It looks like the data is being passed to the Apex controller from the component and that the Apex controller is set up the same way as the example.

Any ideas on how to troubleshoot would be appreciated.


 
I am new to visualforce and am having trouble getting sorting working on my page. My page has two pageblocktables on it that pull data from two different object dynamically based on fields that users input into a custom setting. I would like both tables to be sortable independently. The solution I have is working for one table, but not the other. As far as I can tell the code for both tables is identical. Not sure why there is a difference in behavior.

The table that works is pulling data from an object called Timesheet__c. It is the second table on the page. The table that doesn't is the first and is pulling from an object called Expense_Line__c.

Any help would be much appreciated!

Here is my page:
 
<apex:page controller="invoicePickerClass" lightningStylesheets="TRUE">
  <apex:form>

    <!-- Output panel to make the buttons live side-by-side -->
    <apex:outputPanel id="thePanel" layout="block">

      <!-- Button to get Save changes and stay on form -->
      <apex:commandButton action="{!getSelected}" value="Save" id="Save"/>

      <!-- Button to Save changes and return to Invoice -->
      <apex:commandButton action="{!getSelectedandLeave}" value="Save and Done" id="SaveAndDone"/>
    </apex:outputPanel>
    
    <!--Panel grid that holds the two tables  -->
    <!-- <apex:panelGrid columns="1" width="100%" columnClasses="pgCol"> -->
    
      <!-- Expense Line Table -->
      <apex:pageBlock title="Expense Line List" id="expenseLines">
        <apex:pageBlockSection columns="1">
            
            <!-- ExpenseLine List -->
          <apex:pageBlockTable value="{! ExpenseLines }" var="el" rendered="{!(expenseLines.size != 0)}" cellpadding="5">
            
            <!-- Checkbox Column -->
            <apex:column >
              <apex:facet name="header">
                <apex:inputCheckbox styleClass="checkEL" value="{!el.isSelectedEL}" onclick="checkAll(this,'checkEL')"/>
              </apex:facet>
                <apex:inputCheckbox styleClass="checkEL" value="{!el.isSelectedEL}" id="checkedone">
                </apex:inputCheckbox>
            </apex:column>

            <!-- Dynamic Table Columns -->
            <apex:repeat value="{!ELCol}" var="elCol">
            <apex:column> 
              <apex:facet name="header">
                <apex:commandLink action="{!elSortDir}" immediate="true" reRender="expenseLines" value="{!$ObjectType.Expense_Line__c.Fields[elCol].Label }">
                  <apex:param name="sortParam" assignTo="{!elSort}" value="{!elCol}"/>
                  <apex:outputPanel>
                    <apex:image value="{!IF(elSort = elCol,$Resource[elSortIMG], $Resource.Sortable)}"/>
                  </apex:outputPanel>
                </apex:commandLink>
              </apex:facet>
              <apex:outputField value="{! el.exp[elCol] }"/> 
            </apex:column>
            </apex:repeat>

            <!-- Make table inline editable -->
            <apex:inlineEditSupport event="ondblClick"/>
          </apex:pageBlockTable>

          <!-- Text to display if no data in the table -->
          <apex:outputText rendered="{!(expenseLines.size = 0)}" value="There are no Expenses to display." />
        </apex:pageBlockSection>            
      </apex:pageBlock>


      <!-- Timesheets Table -->
      <apex:pageBlock title="TimeSheet List" id="timeSheets">
        <apex:pageBlockSection columns="1">
            
          <!-- TimeSheet List -->
          <apex:pageBlockTable value="{! TimeSheets }" var="ts" rendered="{!(TimeSheets.size != 0)}" cellpadding="5">
                
            <!-- Checkbox Column -->
            <apex:column>
              <apex:facet name="header">
                <apex:inputCheckbox styleClass="checkTS" value="{!ts.isSelectedTS}" onclick="checkAll(this,'checkTS')"/>
              </apex:facet>
                <apex:inputCheckbox styleClass="checkTS" value="{!ts.isSelectedTS}" id="checkedone">
                </apex:inputCheckbox>
            </apex:column>

            <!-- Dynamic table column -->
            <apex:repeat value="{!TSCol}" var="tsCol">
            <apex:column>
              <apex:facet name="header">
                <apex:commandLink action="{!tsSortDir}" immediate="true" reRender="timeSheets" value="{!$ObjectType.Timesheet__c.Fields[tsCol].Label }">
                  <apex:param name="sortParam" assignTo="{!tsSort}" value="{!tsCol}"/>
                  <apex:outputPanel>
                    <apex:image value="{!IF(tsSort = tsCol,$Resource[tsSortIMG], $Resource.Sortable)}"/>
                  </apex:outputPanel>
                </apex:commandLink>
              </apex:facet>
              <apex:outputField value="{! ts.tsh[tsCol] }"/> 
            </apex:column>
            </apex:repeat>

            <!-- Make table inline editable -->
            <apex:inlineEditSupport event="ondblClick"/>
          </apex:pageBlockTable>

          <!-- Text to display if no data for table -->
          <apex:outputText rendered="{!(TimeSheets.size = 0)}" value="There are no TimeSheets to display."/>
        </apex:pageBlockSection>
      </apex:pageBlock>
   
      
    <!-- </apex:panelGrid> -->
  </apex:form>

  <!-- Script that runs when check all is selected -->
  <script>
    function checkAll(cb, check)
    {
        var inputElem = document.getElementsByClassName(check);
        for(var i=0; i<inputElem.length; i++)
        {
            if(inputElem[i].id.indexOf("checkedone")!=-1)
            inputElem[i].checked = cb.checked;
        }
    }
  </script>
  
  <!-- Style needed if the tables are moved side-by-side -->
  <style type="text/css">
    .pgCol{
      width:50%;
    }
  </style>
</apex:page>

Here is my apex code:
 
public with sharing class invoicePickerClass {

	//URL and other Parameters 
	public String invId {get; set;}
	public String projId {get; set;}
	public static String recId {get; set;}

	//Sort Variables
	public String tsSort {get; set;}
	public String tsSortLast {get; set;}
	transient String tsSortDir {get; set;}
	public String tsSortDirLast {get; set;}
	
	public String elSort {get; set;}
	public String elSortLast {get; set;}
	transient String elSortDir {get; set;}
	public String elSortDirLast {get; set;}

	//Sort images
    public String tsSortIMG {get;set;}
    public String elSortIMG {get;set;}
	
	//Lists for synamic columns
	List<String> tsCol = new List<string>();
	List<String> elCol = new List<string>();

    //Wrapper class data
    public List<tsWrapper> tsList {get; set;}
    List<Timesheet__c> selectedTimeSheets = new List<Timesheet__c>();
    List<Timesheet__c> unSelectedTimeSheets = new List<Timesheet__c>();
    public List<elWrapper> elList {get; set;}
    List<Expense_Line__c> selectedExpenseLines = new List<Expense_Line__c>();
    List<Expense_Line__c> unSelectedExpenseLines = new List<Expense_Line__c>();

    //method to populate time sheet table using wrapper class
    public  List<tsWrapper> getTimeSheets() {

    		invId = Apexpages.currentPage().getparameters().get('invId');

    		projId = Apexpages.currentPage().getparameters().get('projId');

    		system.debug(tsSort+tsSortLast+tsSortDir+tsSortDirLast+tsSortIMG);

    		if(tsSort == NULL){tsSort = 'Date__c'; tsSortDir = 'DESC';}

    		system.debug(tsSort+tsSortLast+tsSortDir+tsSortDirLast+tsSortIMG);

    		tsSortDir();
    		tsSortLast = tsSort;

    		system.debug(tsSort+tsSortLast+tsSortDir+tsSortDirLast+tsSortIMG);

    		List<string> columns = getColumns('Timesheet__c');

    		String qry = 'select Id, Invoice__c, Project__c, '+String.join(columns, ',')+' FROM Timesheet__c WHERE Project__c = :projId AND (Invoice__c = \'\' OR Invoice__c = :invId) ORDER BY '+tsSort+' '+tsSortDir;
            
           // if(tsList == NULL){
            	tsList = new List<tsWrapper>();
            	for(Timesheet__c tsh : Database.query(qry)){
            		tsList.add(new tsWrapper(tsh));
            			
            	}
           // }

            return tsList;
    }

    
    //method to populate expense line table using wrapper class
    public  List<elWrapper> getExpenseLines() {

    		invId = Apexpages.currentPage().getparameters().get('invId');

    		projId = Apexpages.currentPage().getparameters().get('projId');

    		system.debug(elSort+elSortLast+elSortDir+elSortDirLast+elSortIMG);

    		if(elSort == NULL){elSort = 'Date__c'; elSortDir = 'DESC';}

    		system.debug(elSort+elSortLast+elSortDir+elSortDirLast+elSortIMG);

    		elSortDir();
    		elSortLast = elSort;

    		system.debug(elSort+elSortLast+elSortDir+elSortDirLast+elSortIMG);

    		List<string> columns = getColumns('Expense_Line__c');

    		String qry = 'select Id, Invoice__c, Project__c, '+String.join(columns, ',')+' FROM Expense_Line__c WHERE Project__c = :projId AND (Invoice__c = \'\' OR Invoice__c = :invId) ORDER BY '+elSort+' DESC';

            //if(elList == NULL){
            elList = new List<elWrapper>();
            	for(Expense_Line__c exp : Database.query(qry)){
            		elList.add(new elWrapper(exp));	
            	}
            //}

            return elList;
    }

    public List<String> getColumns(string obj){
    	List<InvoicePicker__c> ipCol = new List<InvoicePicker__c>();
    	List<String> columns = new List<String>();
    	if(obj=='Timesheet__c'){
    		ipCol = [select Field__c FROM InvoicePicker__c WHERE Object__c = :obj ORDER BY Column_Order__c ASC];
    	}
    	else if(obj=='Expense_Line__c'){
    		ipCol = [select Field__c FROM InvoicePicker__c WHERE Object__c = :obj ORDER BY Column_Order__c ASC];
    	}
    	for(InvoicePicker__c ip : ipCol){
    		columns.add(ip.Field__c);
    	}
    	return columns;
    }

    //method for dynamic columns for Timesheets
    public List<String> getTSCol(){
    	tsCol.clear();
    	tsCol.addAll(getColumns('Timesheet__c'));
    	return tsCol;

    }

    //method for dynamic columns for Expense Lines
    public List<String> getELCol(){
    	elCol.clear();
    	elCol.addAll(getColumns('Expense_Line__c'));
    	return elCol;

    }


    //Sort data table action
    public void tsSortDir() {
		
		if(tsSortLast == tsSort && tsSortDirLast == 'DESC'){tsSortDir = 'ASC';}
		else if(tsSortLast == tsSort && tsSortDirLast == 'ASC'){tsSortDir = 'DESC';}
		else{tsSortDir = 'DESC';}

		tsSortDirLast = tsSortDir;
		tsSortIMG = tsSortDir;
    }

    public void elSortDir() {
		
		if(elSortLast == elSort && elSortDirLast == 'DESC'){elSortDir = 'ASC';}
		else if(elSortLast == elSort && elSortDirLast == 'ASC'){elSortDir = 'DESC';}
		else{elSortDir = 'DESC';}

		elSortDirLast = elSortDir;
		elSortIMG = elSortDir;
    }

	//timesheet wrapper class
    public class tsWrapper{
        public Timesheet__c tsh {get; set;}
        public Boolean isSelectedTS {get; set;}
        public tsWrapper(Timesheet__c ts){
            tsh = ts;
            if(tsh.Invoice__c != NULL){
            	isSelectedTS = true;
            }
            else{isSelectedTS = false;}
        }
    }

    //Expense Line Wrapper
    public class elWrapper{
        public Expense_Line__c exp{get; set;}
        public Boolean isSelectedEL {get; set;}
        public elWrapper(Expense_Line__c el){
            exp = el;
            if(exp.Invoice__c != NULL){
            	isSelectedEL = true;
            }
            else{isSelectedEL = false;}
        }
    }


    //Original Processing of selected items
    public PageReference getSelectedEL(){
        selectedExpenseLines.clear();
        unSelectedExpenseLines.clear();
        for(elWrapper elWrapper : this.elList){
	        if(elWrapper.isSelectedEL == true) {
		        selectedExpenseLines.add(elWrapper.exp);
	    	}
	    	if(elWrapper.isSelectedEL == false){
	    		unSelectedExpenseLines.add(elWrapper.exp);
	    	}
	    }
       	return null;
    }

	public PageReference getSelectedTS(){
        selectedTimeSheets.clear();
        unSelectedTimeSheets.clear();

        for(tsWrapper tsWrapper : this.tsList){
	        if(tsWrapper.isSelectedTS == true) {
		        selectedTimeSheets.add(tsWrapper.tsh);
	    	}
	    	if(tsWrapper.isSelectedTS == false){
	    		unSelectedTimeSheets.add(tsWrapper.tsh);
	    	}
	    }
       	return null;
    }

     //wrapper class for Comparable Interface
    
    //public class sortN implements Comparable {
    //    public String sortValue {get;set;}    
        
    //    public sortN(String sortName) {
    //        sortValue = sortName;
    //    }
        
    //    public Integer compareTo(Object ObjToCompare) {
    //        return sortValue.CompareTo(((sortN)ObjToCompare).sortValue);
    //    }
    //}
     
    


    //Button method

    public PageReference getSelected(){
    	//get all selected and unselected records
		getSelectedEL();
    	getSelectedTS();
    	
    	//process and update all selected and unselected records
    	processTimeSheets();
    	processExpenseLines();
    	

    	
    	return NULL;
    }

     public PageReference getSelectedandLeave(){
    	//get all selected and unselected records
    	getSelectedEL();
    	getSelectedTS();
    	
    	//process and update all selected and unselected records
    	processTimeSheets();
    	processExpenseLines();
    	
    	//navigate back to invoice record
    	PageReference pr = new PageReference('/'+invId);
        system.debug(pr);
        pr.setRedirect(true);
    	
    	return pr;
    }

    public void processTimeSheets(){

    	List<Timesheet__c> processedTimeSheets = new List<Timesheet__c>();

    	for(Timesheet__c t : selectedTimeSheets){
    		Timesheet__c newTimeSheet = new Timesheet__c(id=t.Id);
    		newTimeSheet.Invoice__c = invId;
    		Map<String, SObjectField> m = Timesheet__c.SObjectType.getDescribe().fields.getMap();
    		for(String f : tsCol){
    			DescribeFieldResult r = m.get(f).getDescribe();
		      	if(r.isUpdateable()){
    				newTimeSheet.put(f, t.get(f));
		      	}	
    		}
    		processedTimeSheets.add(newTimeSheet);
    	}

    	for(Timesheet__c t : unSelectedTimeSheets){
    		Timesheet__c newTimeSheet = new Timesheet__c(id=t.Id);
    		newTimeSheet.Invoice__c = null;
    		Map<String, SObjectField> m = Timesheet__c.SObjectType.getDescribe().fields.getMap();
    		for(String f : tsCol){
    			DescribeFieldResult r = m.get(f).getDescribe();
		      	if(r.isUpdateable()){
    				newTimeSheet.put(f, t.get(f));
		      	}	
    		}
    		processedTimeSheets.add(newTimeSheet);
    	}

    	update processedTimeSheets;
    }

    public void processExpenseLines(){

    	List<Expense_Line__c> processedExpenseLines = new List<Expense_Line__c>();

    	for(Expense_Line__c e : selectedExpenseLines){
    		Expense_Line__c newExpenseLines = new Expense_Line__c(id=e.Id);
    		newExpenseLines.Invoice__c = invId;
    		Map<String, SObjectField> m = Expense_Line__c.SObjectType.getDescribe().fields.getMap();
    		for(String f : elCol){
    			DescribeFieldResult r = m.get(f).getDescribe();
		      	if(r.isUpdateable()){
    				newExpenseLines.put(f, e.get(f));
		      	}
    			
    		}

    		processedExpenseLines.add(newExpenseLines);
    	}

    	for(Expense_Line__c e : unSelectedExpenseLines){
    		Expense_Line__c newExpenseLines = new Expense_Line__c(id=e.Id);
    		newExpenseLines.Invoice__c = null;
    		Map<String, SObjectField> m = Expense_Line__c.SObjectType.getDescribe().fields.getMap();
    		for(String f : elCol){
    			DescribeFieldResult r = m.get(f).getDescribe();
		      	if(r.isUpdateable()){
    				newExpenseLines.put(f, e.get(f));
    			}
    		}

    		processedExpenseLines.add(newExpenseLines);
    	}
    	update processedExpenseLines;
    }

}

 
I am using the Expense__c tracker app as a template to create a custom lightning component for adding new Events. When I click save on the form, the log in the developer console returns an error: Attempted to upsert a null list.

Here is my Component:
 
<aura:component controller="EventController" implements="force:appHostable,flexipage:availableForAllPageTypes" access="global">
<aura:attribute name="allDay" type="Boolean" default="False"/>
<aura:attribute name="events" type="Event[]"/>
<aura:attribute name="newEvent" type="Event"
                 default="{ 'sobjectType': 'Event',
                         'Subject': 'Default Subject',
                         'OwnerId': '005j000000BRyZ1AAL',
                         'StartDateTime': 'now()',
                         
                         'WhoId': '005j000000BRyZ1AAL',
                         'WhatId': '00Q63000001qAPqEAM',
                         'Consult_Amount__c': '123.10',
                         'Event_Status__c': 'Completed',
                         'Event_Type__c': 'Lunch',
                         'DurationInMinutes': '30'
                       }"/>


<!--Assigned To Form Element -->
<ui:inputSelect label="Assigned To" multiple="false" required="true">
    <ui:inputSelectOption text="All Primary" label="All Contacts" value="true"/>
    <ui:inputSelectOption text="All Primary" label="All Primary"/>
    <ui:inputSelectOption text="All Secondary" label="All Secondary"/>
</ui:inputSelect>
    
<!--Subject Form Element -->	
<ui:inputText label="Subject"  placeholder="Enter Subject" value="{!v.newEvent.Subject}"/>
    
<!--All Day Event Form Element -->
<ui:inputCheckbox label="All Day Event" value="{!v.newEvent.IsAllDayEvent}" />

<!--Renders Date or DateTime UI Input based on All Day Event Check Box -->    
    <aura:if isTrue="{!v.newEvent.IsAllDayEvent}">
        
<!--Start/End Date Form Elements -->
<ui:inputDate aura:id="startdate" label="Start Date" value="{!v.newEvent.StartDateTime}" displayDatePicker="true" />
<ui:inputDate aura:id="enddate" label="End Date" value="{!v.newEvent.EndDateTime}" displayDatePicker="true" />
        <aura:set attribute="else">
            
<!--Start/End DateTime Form Elements -->
<ui:inputDateTime aura:id="startdatetime" label="Start Date" class="field" value="{!v.newEvent.StartDateTime}" displayDatePicker="true" />
<ui:inputDateTime aura:id="enddatetime" label="End Date" class="field" value="{!v.newEvent.EndDateTime}" displayDatePicker="true" />    
    </aura:set>
    </aura:if>
    
<!--Invitee Form Element -->
<ui:inputSelect label="Invitees Of This Event" multiple="True" required="true">
    <ui:inputSelectOption text="All Primary" label="All Contacts" value="true"/>
    <ui:inputSelectOption text="All Primary" label="All Primary"/>
    <ui:inputSelectOption text="All Secondary" label="All Secondary"/>
</ui:inputSelect>
    
<!--Event Type Form Element -->
<ui:inputSelect label="Event Type" multiple="False" required="true">
    <ui:inputSelectOption text="All Primary" label="All Contacts" value="true"/>
    <ui:inputSelectOption text="All Primary" label="All Primary"/>
    <ui:inputSelectOption text="All Secondary" label="All Secondary"/>
</ui:inputSelect>
    
<!--Event Status Type Form Element -->
<ui:inputSelect label="Event Status Type" multiple="False" required="true">
    <ui:inputSelectOption text="Completed" label="Completed" value="true"/>
    <ui:inputSelectOption text="Canceled" label="Canceled"/>
    <ui:inputSelectOption text="No/Show" label="No/Show"/>
</ui:inputSelect>
    
<!--Rescheduled from Prior Consult Form Element -->
<ui:inputCheckbox label="Rescheduled from Prior Consult" value=""/>

<!--Is Paid Consult Form Element -->
<ui:inputCheckbox label="Is Paid Consult" value=""/>

<!--Consult Amount Form Element -->	
<ui:inputText label="Consult Amount" value="Consult Amount" required="true"/>
    
<!--Related To Form Element -->
<ui:inputSelect label="Related To" multiple="False" required="true">
    <ui:inputSelectOption text="Matter" label="Matter" value="true"/>
    <ui:inputSelectOption text="Prospect" label="Prospect"/>
</ui:inputSelect>

<!--Save Event Button-->
    <ui:button label="Save" press="{!c.createEvent}"/>
    
</aura:component>
Here is my JS Controller:
 
({
	createEvent : function(component, event, helper) {
    // Validate form fields
    // Pass form data to a helper function
    var newEvent = component.get("v.newEvent");
    helper.createEvent(component, newEvent);
}
})

Here is my Helper:
 
({
	createEvent: function(component, event) {
    //Save the event and update the view
    this.upsertEvent(component, event, function(a) {
        var events = component.get("v.events");
        
        events.push(a.getReturnValue());
        
        component.set("v.events", events);
       
    });
},
upsertEvent : function(component, event, callback) {
  var action = component.get("c.saveEvent");
  action.setParams({ 
      'event': event
  });
  if (callback) {
      action.setCallback(this, callback);
  }
  $A.enqueueAction(action);
}
})
Here is my Apex Controller:
public with sharing class EventController {
@AuraEnabled
    public static List<Event> getEvents() {
        String runningUser = UserInfo.getUserId();
        List<Event> events =  Database.query(
                'SELECT Id, Subject, WhoId, WhatId, EndDateTime, StartDateTime, Matter_Type__c, Color_Code__c, Type, Event_Type__c, OwnerId FROM Event WHERE OwnerId =  :runningUser AND (StartDateTime = LAST_N_DAYS:45 OR StartDateTime = NEXT_N_DAYS:45)');

        //Add isAccessible() check
        return events;
    }
    @AuraEnabled
    // Retrieve all primary contacts
    public static List<Event> getConsult() {
        List<Event> consultEvents = 
             [SELECT Id, Subject, WhoId, WhatId, EndDateTime, StartDateTime, ActivityDate, OwnerId, Type FROM Event WHERE Event_Type__c = 'Consult' AND OwnerId = '005j000000BRyZ1'];

        //Add isAccessible() check
        return consultEvents;
    }

  @AuraEnabled
public static Event saveEvent(Event event) {
    // Perform isUpdateable() check here 
    
   
    					
    upsert event;
    return event;
} 
    

  
    
}
The extra code in the controller is for another part of the app. It looks like the data is being passed to the Apex controller from the component and that the Apex controller is set up the same way as the example.

Any ideas on how to troubleshoot would be appreciated.