7/6/2023
The Why
This part of the guide will go over the Canvas app portion of the build. The goal here is to build a very simple canvas app that will allow the users to add, update, and remove their hours. It will also serve as the location for leads to approve hours.
The How
Initial Setup
- Create a blank Canvas app
- Connect in the User, Hours, Project, and Team Tables.
- On the App OnStart add the following variables. The varUser and varTeam are used to minimize the amount of looks to be done later.
//Get User and Team Data Set( varUser, User() ); Set( varteam, LookUp( Users, 'Primary Email' = varUser.Email ).Team.'Team Name' );
- Add a Date Picker Control and set it as required. Name it calinputTS.
💡 Note: I am using a newer modern control in this application. I really love the look and feel of the new date picker.
- Set the Value to Today()
![](https://automatethemundane.com/wp-content/uploads/2023/08/untitled-2-1024x215.png)
Today()
Logic Setup
- We now need to configure a few variables and collections to get the data and time box it.
- The first two variables we will create set the start and stop dates based on the calendrer selection.
Set( selectedStartDate, DateAdd( calinputTS.Value, 1 - Weekday( calinputTS.Value, StartOfWeek.Monday ) ) ); Set( selectedEndDate, DateAdd( calinputTS.Value, 7 - Weekday( calinputTS.Value, StartOfWeek.Monday ) ) );
💡 Note: for all of the configurations here my week day starts on Monday.
- The Next two are the collections that gather the users authorized projects and any hours they may have already entered.
ClearCollect( colAuthorizedProjects, LookUp( Users, 'Primary Email' = varUser.Email ).'Projects (ew_SystemUser_ew_Project_ew_Project)' ); ClearCollect( colcurrentHours, Filter( Hours, 'Date Worked' >= selectedStartDate && 'Date Worked' <= selectedEndDate && User.'Primary Email' = varUser.Email ) );
💡 Note: the “).'Projects (ew_SystemUser_ew_Project_ew_Project)'” is the N:N relationship that we setup in
- The Next collection is collection colworkdays. It is the time box so that when we add the days to the gallery only the days that fall within it show up.
Clear(colworkdays); ForAll( Sequence(7), Collect( colworkdays, { DayName: Text( DateAdd( calinputTS.Value, Value - Weekday( calinputTS.Value, StartOfWeek.Monday ) ) ), Date: DateAdd( calinputTS.Value, Value - Weekday( calinputTS.Value, StartOfWeek.Monday ) ) } ) )
- All together these are loaded into the OnVisible property of the timesheet screen
Set( selectedStartDate, DateAdd( calinputTS.Value, 1 - Weekday( calinputTS.Value, StartOfWeek.Monday ) ) ); Set( selectedEndDate, DateAdd( calinputTS.Value, 7 - Weekday( calinputTS.Value, StartOfWeek.Monday ) ) ); ClearCollect( colAuthorizedProjects, LookUp( Users, 'Primary Email' = varUser.Email ).'Projects (ew_SystemUser_ew_Project_ew_Project)' ); ClearCollect( colcurrentHours, Filter( Hours, 'Date Worked' >= selectedStartDate && 'Date Worked' <= selectedEndDate && User.'Primary Email' = varUser.Email ) ); Clear(colworkdays); ForAll( Sequence(7), Collect( colworkdays, { DayName: Text( DateAdd( calinputTS.Value, Value - Weekday( calinputTS.Value, StartOfWeek.Monday ) ) ), Date: DateAdd( calinputTS.Value, Value - Weekday( calinputTS.Value, StartOfWeek.Monday ) ) } ) )
We also need to add logic to the date picker so that when a date is selected, the colection is updated. Copy the above logic and place it in the OnChange of the date picker.
![](https://automatethemundane.com/wp-content/uploads/2023/08/untitled-4-1024x338.png)
Gallery Configuration
- There are two galleries to be added to the time sheet screen. The first is called galProjectsTS, it is the gallery for all the projects and will use the collection colAuthorizedProjects. The second will be a sub gallery in the galProjectsTS and be called galHoursTS, it is where all the hours are to be entered and is fed by the colworkdays collection.
galProjectsTS
- Add a blank vertical gallery and call it galProjectsTS set the items to colAuthorizedProjects
- Add a label control and call it lblProjectNameHS. Set it to
ThisItem.'Project Name'
![](https://automatethemundane.com/wp-content/uploads/2023/08/untitled-5-1024x242.png)
galHoursTS
- Insert a blank hortintoal gallery inside galProjectsTS. Name it galHoursTS
- Set the Items to colworkdays
- Add a text label to the gallery and name it lblDayHoursTS. Adjust the label and the gallery to make it look like the following.
- Update the Text for the label to the following
Text( ThisItem.Date, "[$-en-US]ddd ,mmm dd" )
- Add a text input and name it inputHoursTS. Set the input to be a number value and the hint text to 0
- The next step is to add the default value of the input. This will search the collection to see if there are any hours for that given day.
- Set the Default value of the input to the following
LookUp( colcurrentHours, Project.'Project Name' = lblProjectNameHS.Text && 'Date Worked' = ThisItem.Date ).'Hours (eca_hours)'
💡 Note: Yours hours name will be different then mine.
- Set the Default value of the input to the following
- Next we will set what happens when an entry is either added or removed from the input. I have chosen to do this in the on change of the input. This was the simplest method when working in the sub galleries.
- Set the Delay Output to true. This will ensure that the patch dose not happen until the user clicks out of the text box.
- Set the On Change to the following
// First, look for an existing record and set it in "existingRecord" With( { existingRecord: LookUp( colcurrentHours, 'Date Worked' = ThisItem.Date && Project.'Project Name' = galProjectsTS.Selected.'Project Name' && User.'Primary Email' = LookUp( Users, 'Primary Email' = varUser.Email ).'Primary Email' ) }, If( !IsBlank(inputHoursTS.Text) || Value(inputHoursTS.Text) = 0, // Patch the record: Patch( Hours, // If an existing record was found, update it; if not, create a new one: If( IsBlank(existingRecord), Defaults(Hours), existingRecord ), { 'Hours (ew_hours)': Value(inputHoursTS.Text), Project: LookUp( Projects, 'Project Name' = galProjectsTS.Selected.'Project Name' ), 'Date Worked': ThisItem.Date, User: LookUp( Users, 'Primary Email' = varUser.Email ) } ), //Remove the record if removed from input If( !IsBlank(existingRecord), Remove( Hours, existingRecord ) ) ) ); ClearCollect( colcurrentHours, Filter( Hours, 'Date Worked' >= selectedStartDate && 'Date Worked' <= selectedEndDate && User.'Primary Email' = varUser.Email ) );
- Set the Display mode as follows. This will disable the input if the lead has approved the hours.
If( LookUp( colcurrentHours, Project.'Project Name' = lblProjectNameHS.Text && 'Date Worked' = ThisItem.Date ).'Lead Verify' = true, DisplayMode.Disabled, DisplayMode.Edit )
- Set the Delay Output to true. This will ensure that the patch dose not happen until the user clicks out of the text box.
The core of the canvas app is now done. In the next guide we will add in supervisors approval.