/// <summary>
        /// Binds the grid.
        /// </summary>
        private void BindGrid()
        {
            if (HasFilterErrors())
            {
                return;
            }

            var         checkinAreaFilter = CheckinManagerHelper.GetCheckinAreaFilter(this);
            CampusCache campus            = GetCampusFromContext();

            var selectedScheduleIds = lbSchedules.SelectedValues.AsIntegerList();

            if (selectedScheduleIds.Any())
            {
                btnShowFilter.AddCssClass("criteria-exists bg-warning");
            }
            else
            {
                btnShowFilter.RemoveCssClass("criteria-exists bg-warning");
            }

            CheckinManagerHelper.SaveRoomListFilterToCookie(selectedScheduleIds.ToArray());

            var rockContext      = new RockContext();
            var groupService     = new GroupService(rockContext);
            var groupTypeService = new GroupTypeService(rockContext);
            IEnumerable <CheckinAreaPath> checkinAreaPaths;

            if (checkinAreaFilter != null)
            {
                checkinAreaPaths = groupTypeService.GetCheckinAreaDescendantsPath(checkinAreaFilter.Id);
            }
            else
            {
                checkinAreaPaths = groupTypeService.GetAllCheckinAreaPaths();
            }

            var selectedGroupTypeIds = checkinAreaPaths.Select(a => a.GroupTypeId).Distinct().ToArray();

            var groupLocationService = new GroupLocationService(rockContext);
            var groupLocationsQuery  = groupLocationService.Queryable()
                                       .Where(gl => selectedGroupTypeIds.Contains(gl.Group.GroupTypeId) && gl.Group.IsActive && (!gl.Group.IsArchived));

            var parentLocationIdParameter = PageParameter(PageParameterKey.ParentLocationId).AsIntegerOrNull();
            var locationIdParameter       = PageParameter(PageParameterKey.LocationId).AsIntegerOrNull();
            var locationGridField         = gRoomList.ColumnsOfType <RockLiteralField>().FirstOrDefault(a => a.ID == "lRoomName");

            if (locationGridField != null && !locationGridField.Visible)
            {
                locationGridField.Visible = true;
            }

            List <int> locationIds;

            if (locationIdParameter.HasValue)
            {
                // If LocationId is specified in the URL, list only items for the specified location.
                // Also, hide the Location Grid Column and set the PanelTitle as the location's name
                // This will take precedence over the selected campus+locations and/or <seealso cref="ParentLocationId"/>
                var locationService = new LocationService(rockContext);
                lPanelTitle.Text = locationService.GetSelect(locationIdParameter.Value, s => s.Name);

                locationIds = new List <int>();
                locationIds.Add(locationIdParameter.Value);

                if (locationGridField != null)
                {
                    // since a LocationId parameter was specified, the LocationGrid field doesn't need to be shown
                    locationGridField.Visible = false;
                }
            }
            else if (parentLocationIdParameter.HasValue)
            {
                // If parentLocationId is specified, show the direct (first level) child locations of the specified ParentLocationId.
                // This will take precedence over the selected campus+locations.
                var locationService = new LocationService(rockContext);
                locationIds = locationService.Queryable()
                              .Where(a => a.ParentLocationId.HasValue && a.ParentLocationId.Value == parentLocationIdParameter.Value)
                              .Select(a => a.Id).ToList();

                lPanelTitle.Text = string.Format("{0} Child Locations", locationService.GetSelect(parentLocationIdParameter.Value, s => s.Name));
            }
            else
            {
                // Limit locations (rooms) to locations within the selected campus.
                locationIds = new LocationService(rockContext).GetAllDescendentIds(campus.LocationId.Value).ToList();
                locationIds.Add(campus.LocationId.Value, true);

                lPanelTitle.Text = "Room List";
            }

            groupLocationsQuery = groupLocationsQuery.Where(a => locationIds.Contains(a.LocationId));

            if (selectedScheduleIds.Any())
            {
                groupLocationsQuery = groupLocationsQuery.Where(a => a.Schedules.Any(s => s.IsActive && s.CheckInStartOffsetMinutes.HasValue && selectedScheduleIds.Contains(s.Id)));
            }
            else
            {
                groupLocationsQuery = groupLocationsQuery.Where(a => a.Schedules.Any(s => s.IsActive && s.CheckInStartOffsetMinutes.HasValue));
            }

            var groupLocationList = groupLocationsQuery.Select(a => new GroupLocationInfo
            {
                LocationId      = a.LocationId,
                LocationName    = a.Location.Name,
                ParentGroupId   = a.Group.ParentGroupId,
                ParentGroupName = a.Group.ParentGroup.Name,
                GroupId         = a.Group.Id,
                GroupName       = a.Group.Name,
                GroupTypeId     = a.Group.GroupTypeId
            }).ToList();

            var      startDateTime = RockDateTime.Today;
            DateTime currentDateTime;

            if (campus != null)
            {
                currentDateTime = campus.CurrentDateTime;
            }
            else
            {
                currentDateTime = RockDateTime.Now;
            }

            // Get all Attendance records for the current day and location.
            var attendanceQuery = new AttendanceService(rockContext).Queryable().Where(a =>
                                                                                       a.StartDateTime >= startDateTime &&
                                                                                       a.DidAttend == true &&
                                                                                       a.StartDateTime <= currentDateTime &&
                                                                                       a.PersonAliasId.HasValue &&
                                                                                       a.Occurrence.GroupId.HasValue &&
                                                                                       a.Occurrence.LocationId.HasValue &&
                                                                                       a.Occurrence.ScheduleId.HasValue);

            // Limit attendances (rooms) to the groupLocations' LocationId and GroupIds that we'll be showing
            var groupLocationLocationIds = groupLocationList.Select(a => a.LocationId).Distinct().ToList();
            var groupLocationGroupsIds   = groupLocationList.Select(a => a.GroupId).Distinct().ToList();

            attendanceQuery = attendanceQuery.Where(a =>
                                                    groupLocationLocationIds.Contains(a.Occurrence.LocationId.Value) &&
                                                    groupLocationGroupsIds.Contains(a.Occurrence.GroupId.Value));

            attendanceQuery = attendanceQuery.Where(a => selectedGroupTypeIds.Contains(a.Occurrence.Group.GroupTypeId));

            if (selectedScheduleIds.Any())
            {
                attendanceQuery = attendanceQuery.Where(a => selectedScheduleIds.Contains(a.Occurrence.ScheduleId.Value));
            }

            var rosterAttendeeAttendanceList = RosterAttendeeAttendance.Select(attendanceQuery).ToList();

            var groupTypeIdsWithAllowCheckout = selectedGroupTypeIds
                                                .Select(a => GroupTypeCache.Get(a))
                                                .Where(a => a != null)
                                                .Where(gt => gt.GetCheckInConfigurationAttributeValue(Rock.SystemKey.GroupTypeAttributeKey.CHECKIN_GROUPTYPE_ALLOW_CHECKOUT).AsBoolean())
                                                .Select(a => a.Id)
                                                .Distinct().ToList();

            var groupTypeIdsWithEnablePresence = selectedGroupTypeIds
                                                 .Select(a => GroupTypeCache.Get(a))
                                                 .Where(a => a != null)
                                                 .Where(gt => gt.GetCheckInConfigurationAttributeValue(Rock.SystemKey.GroupTypeAttributeKey.CHECKIN_GROUPTYPE_ENABLE_PRESENCE).AsBoolean())
                                                 .Select(a => a.Id)
                                                 .Distinct();

            var scheduleIds  = rosterAttendeeAttendanceList.Select(a => a.ScheduleId.Value).Distinct().ToList();
            var scheduleList = new ScheduleService(rockContext).GetByIds(scheduleIds).ToList();
            var scheduleIdsWasScheduleOrCheckInActiveForCheckOut = new HashSet <int>(scheduleList.Where(a => a.WasScheduleOrCheckInActiveForCheckOut(currentDateTime)).Select(a => a.Id).ToList());

            rosterAttendeeAttendanceList = rosterAttendeeAttendanceList.Where(a =>
            {
                var allowCheckout = groupTypeIdsWithAllowCheckout.Contains(a.GroupTypeId);
                if (!allowCheckout)
                {
                    /*
                     *  If AllowCheckout is false, remove all Attendees whose schedules are not currently active. Per the 'WasSchedule...ActiveForCheckOut()'
                     *  method below: "Check-out can happen while check-in is active or until the event ends (start time + duration)." This will help to keep
                     *  the list of 'Present' attendees cleaned up and accurate, based on the room schedules, since the volunteers have no way to manually mark
                     *  an Attendee as 'Checked-out'.
                     *
                     *  If, on the other hand, AllowCheckout is true, it will be the volunteers' responsibility to click the [Check-out] button when an
                     *  Attendee leaves the room, in order to keep the list of 'Present' Attendees in order. This will also allow the volunteers to continue
                     *  'Checking-out' Attendees in the case that the parents are running late in picking them up.
                     */

                    return(scheduleIdsWasScheduleOrCheckInActiveForCheckOut.Contains(a.ScheduleId.Value));
                }
                else
                {
                    return(true);
                }
            }).ToList();

            var attendancesByLocationId = rosterAttendeeAttendanceList
                                          .GroupBy(a => a.LocationId.Value).ToDictionary(k => k.Key, v => v.ToList());

            _attendancesByLocationIdAndGroupId = attendancesByLocationId.ToDictionary(
                k => k.Key,
                v => v.Value.GroupBy(x => x.GroupId.Value).ToDictionary(x => x.Key, xx => xx.ToList()));

            _checkinAreaPathsLookupByGroupTypeId = checkinAreaPaths.ToDictionary(k => k.GroupTypeId, v => v);

            _showOnlyParentGroup = this.GetAttributeValue(AttributeKey.ShowOnlyParentGroup).AsBoolean();

            var roomList = new List <RoomInfo>();

            foreach (var groupLocation in groupLocationList)
            {
                AddToRoomList(roomList, groupLocation);
            }

            List <RoomInfo> sortedRoomList;

            if (_showOnlyParentGroup)
            {
                sortedRoomList = roomList.OrderBy(a => a.LocationName).ToList();
            }
            else
            {
                sortedRoomList = new List <RoomInfo>();
                sortedRoomList.AddRange(roomList.OfType <RoomInfoByGroup>().OrderBy(a => a.LocationName).ThenBy(a => a.GroupName).ToList());
            }

            var checkedInCountField  = gRoomList.ColumnsOfType <RockLiteralField>().FirstOrDefault(a => a.ID == "lCheckedInCount");
            var presentCountField    = gRoomList.ColumnsOfType <RockLiteralField>().FirstOrDefault(a => a.ID == "lPresentCount");
            var checkedOutCountField = gRoomList.ColumnsOfType <RockLiteralField>().FirstOrDefault(a => a.ID == "lCheckedOutCount");

            checkedOutCountField.Visible = groupTypeIdsWithAllowCheckout.Any();

            // Always show Present Count regardless of the 'Enable Presence' setting. (A person gets automatically marked present if 'Enable Presence' is disabled.)
            presentCountField.Visible = true;
            if (groupTypeIdsWithEnablePresence.Any())
            {
                // Presence is enabled, so records could be in the 'Checked-in' state
                // and Present column should be labeled 'Present'.
                checkedInCountField.Visible  = true;
                presentCountField.HeaderText = "Present";
            }
            else
            {
                // https://app.asana.com/0/0/1199637795718017/f
                // 'Enable Presence' is disabled, so a person automatically gets marked present.
                // So, no records will be in the 'Checked-In (but no present)' state.
                // Also, a user thinks of 'Present' as 'Checked-In' if they don't use the 'Enable Presence' feature
                checkedInCountField.Visible  = false;
                presentCountField.HeaderText = "Checked-In";
            }

            if (_showOnlyParentGroup)
            {
                gRoomList.DataKeyNames = new string[1] {
                    "LocationId"
                };
            }
            else
            {
                gRoomList.DataKeyNames = new string[2] {
                    "LocationId", "GroupId"
                };
            }

            gRoomList.DataSource = sortedRoomList;
            gRoomList.DataBind();
        }