In this part you create Turnout in App Builder and build its first five pages: Discover events, Clubs, a Club page, an Event page and Sign in. Visitors can browse without an account. Signed-in members can join a club and RSVP, and every one of those actions goes through the packages you wrote in Part 3.
Before you start
You need the twelve tables, the sample data and the seven packages from Parts 2 and 3. To check, run this in SQL Workshop › SQL Commands:
select count(*) as packages from user_objects
where object_type = 'PACKAGE' and object_name like 'TO\_%' escape '\';
It should say 7. If it says 0, or you're starting here, download the Start here script below and run it in SQL Workshop › SQL Scripts (Upload, then Run). It builds everything Part 4 needs in about a minute.
| You'll build | Page | Who can open it | Lesson |
|---|---|---|---|
| Discover events | 1 | Everyone | 4.1, 4.2 |
| Clubs | 2 | Everyone | 4.2 |
| Club | 10 | Everyone | 4.2 |
| Event | 20 | Everyone (RSVP needs sign-in) | 4.2, 4.4 |
| Sign in | 9999 | Everyone | 4.5 |
| Breadcrumbs on Club and Event | 10, 20 | Everyone | 4.6 |
Each lesson has a Build it section with numbered steps. Do them in order; each ends with a check, so you know it worked before you move on. If anything goes wrong, the Finished app in the downloads is exactly what you should have at the end of this part.
Lesson 4.1 — Create the app and plan page numbers
Outcome: you create the Turnout application with the right settings from the start, and number its pages so the number says who can open them.
Why an almost empty app? The Create App wizard can generate dashboards, forms and reports on your tables. But generated forms write straight to the tables, which skips every rule in your Part 3 packages. So you start with a shell and add each page on purpose.
The page numbering plan (from Lesson 1.4):
| Range | Who | Turnout pages |
|---|---|---|
| 1–29 | Everyone | 1 Discover, 2 Clubs, 10 Club, 20 Event |
| 30–99 | Signed-in members | 30 My events, 31 My ticket (Part 7) |
| 100–199 | Organizers of that club | 100 Dashboard, 120 Edit event, 130 Check-in (Part 7) |
| 200–299 | Site admins | 200 Club approvals (Part 6) |
| 9999 | Everyone | Sign in |
Gaps (10, 20, not 3, 4) leave room to add related pages later without renumbering.
Build it
- Open App Builder, click Create, then New Application.
- Name:
Turnout. - Click Appearance (the pencil next to it). Set Navigation to Top, then Save Changes.
- Leave Pages as the single Home page. Leave every Feature unchecked.
- Under Settings, check that Authentication is Oracle APEX Accounts. You'll switch to email codes and Google in Part 5.
- Click Create Application. APEX builds two pages: 1 Home and 9999 Login Page.
- Open Shared Components › Application Definition. Set Application Alias to
TURNOUT. Then open the Error Handling section and set Error Handling Function toto_err.apex_error_handler(Lesson 3.6). Click Apply Changes. - Still in Application Definition, open Properties and switch Friendly URLs on. Apply Changes.
- Open Shared Components › Navigation Menu, then the list Navigation Menu. Click the Home entry and change its List Entry Label to
Discover. Apply Changes.
Check: click Run Application. You sign in with your workspace username, then see a page called Home with Discover in the top menu. Your address bar shows /r/<your workspace>/turnout/home, not f?p=.
Check yourselfWhy not let the wizard generate a form on TO_RSVPS?
It would insert rows directly and skip the capacity, waitlist and membership rules in TO_RSVP_API.
Lesson 4.2 — Public pages and friendly URLs
Outcome: you build pages 1, 2, 10 and 20 as public pages that anyone can open from a shared link.
The decision. Discovery pages are public: a link Priya posts in her running group's chat opens straight away, and signing in is asked for only when someone acts. Three settings make that safe:
| Setting | Where | Value | Why |
|---|---|---|---|
| Authentication | Page › Security | Page Is Public | Visitors can browse |
| Page Access Protection | Page › Security | Unrestricted | A link shared in a chat must work for anyone |
| Session State Protection | The page's id item | Unrestricted | The id only chooses what to show; every action re-checks in the packages |
Build it: page 1, Discover events
- Open page 1 in Page Designer. In the Property Editor on the right, with the page selected: Name
Discover Events, AliasDISCOVER, TitleDiscover events. - Scroll to Security: Authentication
Page Is Public, Page Access ProtectionUnrestricted. - If the page has any regions the wizard made, right-click each in the Rendering tree and Delete it.
- Right-click Breadcrumb Bar in the Rendering tree, Create Region. (If the tree doesn't show Breadcrumb Bar, right-click Body instead, then set Layout › Slot to Breadcrumb Bar: a page only lists the slots it already uses.) Set Name and Title
Find your people, TypeStatic Content, Source › HTML CodeRunning, cycling, chess, book clubs and more. See what's on, then join the clubs you like.Under Appearance, set Template toHero. - Right-click Body, Create Region. Name and Title
Upcoming events, TypeCards. Under Source, set TypeSQL Queryand paste:
select e.id,
e.title,
c.name as club_name,
cat.name as category,
-- Shown in the club's own time zone (Lesson 2.5)
to_char(e.starts_at at time zone c.time_zone,
'fmDy, Mon DD "at" HH:fmMI AM') as event_when,
nvl(v.name, 'Meeting point to be announced') as venue_name,
case
when e.capacity is null then 'Open to all'
when to_rsvp_api.spots_left(e.id) = 0 then 'Waitlist'
else to_rsvp_api.spots_left(e.id) || ' spots left'
end as spots_label
from to_events e
join to_clubs c on c.id = e.club_id
join to_categories cat on cat.id = c.category_id
left join to_venues v on v.id = e.venue_id
where e.status = 'PUBLISHED'
and c.status = 'ACTIVE'
and c.visibility = 'PUBLIC'
and e.starts_at > systimestamp
order by e.starts_at
- Click the region's Attributes tab. Set Card › Primary Key Column 1
ID, Title › ColumnTITLE, Subtitle › ColumnCLUB_NAME, Body › ColumnEVENT_WHEN, Secondary Body › ColumnVENUE_NAME, Icon and Badge › Icon SourceInitials, Icon ColumnCATEGORY, Badge ColumnSPOTS_LABEL. Set Pagination › Type toScroll. - Save and run the page.
Check: you see a hero saying "Find your people" and a page of event cards, each with a club, a time, a meeting point and a badge like "3 spots left". Two details come from earlier parts: times are in the club's time zone (Lesson 2.5), and "spots left" comes from to_rsvp_api.spots_left, so the page and the RSVP rules can never disagree.
Build it: page 2, Clubs
- Click Create Page (the + at the top of Page Designer), choose Blank Page. Page Number
2, NameBrowse Clubs, Page ModeNormal. Under Navigation, switch Use Breadcrumb off and Use Navigation on, with a New Navigation Menu Entry calledClubs. Create Page. - Page properties: Alias
CLUBS, TitleClubs, AuthenticationPage Is Public, Page Access ProtectionUnrestricted. - Create a Cards region in Body, Name and Title
Clubs, with this query:
select c.id,
c.name,
cat.name || nvl2(c.city, ' · ' || c.city, null) as category_city,
c.description,
(select count(*)
from to_club_members m
where m.club_id = c.id
and m.status = 'ACTIVE') || ' members' as member_count,
case c.join_policy
when 'OPEN' then 'Anyone can join'
when 'APPROVAL' then 'Organizers approve members'
else 'Invite only'
end as join_policy_label
from to_clubs c
join to_categories cat on cat.id = c.category_id
where c.status = 'ACTIVE'
and c.visibility = 'PUBLIC'
order by c.name
- Attributes: Primary Key Column 1
ID, TitleNAME, SubtitleCATEGORY_CITY, BodyDESCRIPTION, Secondary BodyJOIN_POLICY_LABEL, Icon SourceInitials, Icon ColumnNAME, Badge ColumnMEMBER_COUNT, PaginationScroll.
Check: run it. The top menu shows Discover and Clubs, and the Clubs page lists the active public clubs, Tampa Runners and Bay Chess Club among them.
Build it: page 10, Club
- Create Page › Blank Page, number
10, nameClub, Use Breadcrumb off, Use Navigation off. Page properties: AliasCLUB, Title&P10_NAME., public and unrestricted as before. - Create a Static Content region in Body: Name
Club, Title&P10_NAME.(leave Text empty). - Right-click the Club region, Create Page Item, five times:
| Name | Type | Label | Notes |
|---|---|---|---|
P10_CLUB_ID |
Hidden | Security › Session State Protection Unrestricted |
|
P10_NAME |
Hidden | ||
P10_ABOUT |
Display Only | About | Appearance › Template Optional - Above |
P10_DETAILS |
Display Only | Details | Template Optional - Above |
P10_MY_STATUS |
Display Only | Your membership | Template Optional - Above; Server-side Condition Item is NOT NULL, item P10_MY_STATUS |
- Under Pre-Rendering, right-click Before Header, Create Process. Name
Load club, TypeExecute Code, and paste:
select c.name,
c.description,
cat.name || nvl2(c.city, ' in ' || c.city, null)
|| '. ' || case c.join_policy
when 'OPEN' then 'Anyone can join.'
when 'APPROVAL' then 'Organizers approve new members.'
else 'Invite only.'
end
into :P10_NAME, :P10_ABOUT, :P10_DETAILS
from to_clubs c
join to_categories cat on cat.id = c.category_id
where c.id = :P10_CLUB_ID
and c.status = 'ACTIVE';
-- The signed-in person's membership, if any
begin
select initcap(status)
into :P10_MY_STATUS
from to_club_members
where club_id = :P10_CLUB_ID
and user_id = to_security.current_user_id;
exception
when no_data_found then
:P10_MY_STATUS := null;
end;
- Right-click Body, Create Region again, so it lands below Club: Name and Title
Upcoming events, TypeCards. Under Source, set TypeSQL Queryand paste the query below. Set Source › Page Items to Submit toP10_CLUB_ID, and the Attributes as on page 1 but with no Subtitle or Icon (TitleTITLE, BodyEVENT_WHEN, Secondary BodyVENUE_NAME, BadgeSPOTS_LABEL, Primary KeyID).
select e.id,
e.title,
to_char(e.starts_at at time zone c.time_zone,
'fmDy, Mon DD "at" HH:fmMI AM') as event_when,
nvl(v.name, 'Meeting point to be announced') as venue_name,
case
when e.capacity is null then 'Open to all'
when to_rsvp_api.spots_left(e.id) = 0 then 'Waitlist'
else to_rsvp_api.spots_left(e.id) || ' spots left'
end as spots_label
from to_events e
join to_clubs c on c.id = e.club_id
left join to_venues v on v.id = e.venue_id
where e.club_id = :P10_CLUB_ID
and e.status = 'PUBLISHED'
and e.starts_at > systimestamp
order by e.starts_at
- Join comes in Lesson 4.4, once there's someone to sign in as.
Build it: page 20, Event
- Create Page › Blank Page, number
20, nameEvent, Use Breadcrumb off, Use Navigation off. AliasEVENT, Title&P20_TITLE., public and unrestricted. - A Static Content region in Body: Name
Event, Title&P20_TITLE.. - Page items in the Event region:
| Name | Type | Label | Notes |
|---|---|---|---|
P20_EVENT_ID |
Hidden | Session State Protection Unrestricted |
|
P20_TITLE |
Hidden | ||
P20_CLUB |
Display Only | Club | Template Optional - Above |
P20_WHEN |
Display Only | When | Template Optional - Above |
P20_WHERE |
Display Only | Where | Template Optional - Above |
P20_SPOTS |
Display Only | Spots | Template Optional - Above |
P20_DESCRIPTION |
Display Only | About this event | Template Optional - Above |
P20_MY_STATUS |
Hidden | ||
P20_MY_STATUS_TEXT |
Display Only | Your RSVP | Template Optional - Above; condition Item is NOT NULL |
- A Before Header process, Name
Load event, TypeExecute Code:
declare
l_left number;
begin
select e.title,
c.name,
to_char(e.starts_at at time zone c.time_zone,
'fmDay, Month DD "at" HH:fmMI AM')
|| ' (' || c.time_zone || ')',
nvl(v.name || nvl2(v.notes, '. ' || v.notes, null),
'Meeting point to be announced'),
e.description
into :P20_TITLE, :P20_CLUB, :P20_WHEN, :P20_WHERE, :P20_DESCRIPTION
from to_events e
join to_clubs c on c.id = e.club_id
left join to_venues v on v.id = e.venue_id
where e.id = :P20_EVENT_ID
and c.status = 'ACTIVE';
l_left := to_rsvp_api.spots_left(:P20_EVENT_ID);
:P20_SPOTS := case
when l_left is null then 'Open to all'
when l_left = 0 then 'Full: new RSVPs join the waitlist'
else l_left || ' left'
end;
begin
select status,
case status
when 'GOING' then 'You''re going'
|| case when guests > 0 then ' with ' || guests || ' guest(s)' end
when 'WAITLISTED' then 'On the waitlist, number ' || waitlist_position
when 'CANCELLED' then 'You cancelled'
when 'ATTENDED' then 'You were there'
when 'NO_SHOW' then 'You RSVPed but didn''t check in'
end
into :P20_MY_STATUS, :P20_MY_STATUS_TEXT
from to_rsvps
where event_id = :P20_EVENT_ID
and user_id = to_security.current_user_id;
exception
when no_data_found then
:P20_MY_STATUS := null;
:P20_MY_STATUS_TEXT := null;
end;
end;
Build it: link the cards to the pages
- On page 1, right-click the Upcoming events region, Create Action. Type
Full Card. Click Link › Target: Page20, set item P20_EVENT_ID to&ID., Clear Cache20. OK. - On page 2, the same on the Clubs region: target page
10, P10_CLUB_ID&ID., Clear Cache10. - On page 10, the same on Upcoming events: target page
20, P20_EVENT_ID&ID., Clear Cache20.
Check: sign out (or open a private browser window) and go to Discover. Click Saturday Long Run: the Event page shows Tampa Runners, a time like "Saturday, October 24 at 7:00 AM (America/New_York)", five days after the day you ran the seed, the meeting point and the spots. Go to Clubs › Tampa Runners: its events are listed, and clicking one opens it. No sign-in was asked for. Look at the address bar: it says /turnout/event?p20_event_id=…, a link anyone can open.
Check yourselfWhy is it safe to make page 20's id item unrestricted?
It only chooses which event to show; RSVP and cancel run through TO_RSVP_API, which checks who you are.
Going further. Event links still carry a number. As an exercise, give page 20 two hidden items, P20_CLUB_SLUG and P20_EVENT_SLUG, for the two slugs (not P20_CLUB: that name is taken by the Club display item), and look the id up in a Before Header process. The link becomes …/event?p20_club=tampa-runners&p20_event=saturday-long-run: this is exactly why Lesson 2.3 made slugs unique.
Lesson 4.3 — Normal pages vs modal dialogs
Outcome: you can decide whether a Turnout page should be a full page or a dialog that opens on top of another.
The question to ask: does the person need to come straight back to where they were?
| Page | Type | Why |
|---|---|---|
| 20 Event | Normal | People land here from a shared link; it has to stand alone |
| 35 RSVP | Modal dialog | A quick choice; the event page stays underneath, then refreshes |
| 31 My ticket | Normal | Opened at the door, from a bookmark or email, full-screen on a phone |
| 120 Edit event | Modal dialog | Organizers edit from the dashboard and go back to it |
| 130 Check-in | Normal | Used for an hour at the door; needs the whole screen |
In APEX a dialog is just a page whose Page Mode is Modal Dialog. Three things change: any link to it opens it on top of the current page; it uses the dialog template, with buttons in its footer; and a Close Dialog process ends it and tells the page underneath (Lesson 4.4).
Turnout's first RSVP is a button on page 20 itself, because that's simpler to learn. You'll build the dialog version as an exercise once RSVP works.
Check yourselfWhy is My ticket a normal page, not a dialog?
It's opened at the door, often from a bookmark or email, so it must work on its own and fill the phone screen.
Lesson 4.4 — Branches, redirects and closing dialogs
Outcome: members can join a club and RSVP, and land back on the same page with a message that says what happened.
Grace taps RSVP on the Saturday Long Run. What should she see next? The same event page, refreshed, with a message in words: going or waitlisted. That takes three pieces working together: a process that calls the package and turns its answer into a message, a branch back to the page carrying the event id, and the Before Header process you already have, which reloads her status so the buttons change.
If the package raises an error (RSVPs closed, too many guests), the branch never runs. APEX stays on the page and shows the message from TO_ERR (Lesson 3.6). You write no error handling on the page at all.
Build it: RSVP on page 20
- Add a Number Field item to the Event region: Name
P20_GUESTS, LabelGuests, templateOptional - Above, Default › Static Value0, and this Server-side Condition (Expression, PL/SQL):
:APP_USER <> 'nobody' and nvl(:P20_MY_STATUS, 'NONE') not in ('GOING', 'WAITLISTED')
- Right-click the Event region, Create Button, three times:
| Button name | Label | Slot | Hot | Action | Server-side Condition |
|---|---|---|---|---|---|
RSVP |
RSVP | Edit | Yes | Submit Page | The same expression as P20_GUESTS |
CANCEL_RSVP |
Cancel my RSVP | Edit | No | Submit Page | Expression: :P20_MY_STATUS in ('GOING', 'WAITLISTED') |
SIGN_IN |
Sign in to RSVP | Edit | Yes | Redirect to Page in this Application, page 9999 |
User is the public user (user has not authenticated) |
- On CANCEL_RSVP, under Behavior, switch Requires Confirmation on and set the Message to
Cancel your RSVP? If you had a spot, the next person on the waitlist gets it.and StyleWarning - Click the Processing tab (the second icon at the top of the left pane). Right-click Processes, Create Process: Name
RSVP, TypeExecute Code, Server-side Condition › When Button PressedRSVP:
apex_application.g_print_success_message :=
case to_rsvp_api.rsvp(:P20_EVENT_ID, nvl(:P20_GUESTS, 0))
when 'GOING' then 'You''re going. See you there.'
else 'The event is full, so you''re on the waitlist. We''ll email you if a spot opens.'
end;
- Another process: Name
Cancel RSVP, When Button PressedCANCEL_RSVP, Success MessageYour RSVP is cancelled.:
to_rsvp_api.cancel_rsvp(:P20_EVENT_ID);
- Right-click After Processing, Create Branch. Behavior › Type
Page or URL (Redirect), Target: page20, set P20_EVENT_ID to&P20_EVENT_ID.. Switch Include Process Success Message on.
Build it: Join on page 10
- On page 10, two buttons in the Club region, both Layout › Slot
Editand Hot on:
| Button name | Label | Action | Server-side Condition |
|---|---|---|---|
JOIN |
Join this club | Submit Page | Expression: :APP_USER <> 'nobody' and nvl(upper(:P10_MY_STATUS), 'REMOVED') = 'REMOVED' |
SIGN_IN |
Sign in to join | Redirect to Page in this Application, page 9999 |
User is the public user (user has not authenticated) |
- A process Join club, When Button Pressed
JOIN:
apex_application.g_print_success_message :=
case to_club_api.join_club(:P10_CLUB_ID)
when 'ACTIVE' then 'Welcome! You''re a member.'
else 'Request sent. An organizer will approve it.'
end;
- A branch After Processing to page
10, with P10_CLUB_ID&P10_CLUB_ID.and Include Process Success Message on.
Build it: people to sign in as
First, refresh the sample data. The sample events are dated from the day you ran the seed, and Tuesday Track Intervals is only two days after that. In SQL Workshop › SQL Scripts, run turnout_seed.sql again now, so the walkthrough below matches.
Until Part 5, Turnout signs people in with your workspace's own accounts. Turnout knows who you are by matching your username to an email in TO_USERS, so create accounts named after two sample people.
- Open the Administration menu (the person-and-key icon at the bottom of the dark bar on the left) and choose Manage Users and Groups, then Create User.
- Username
grace.kim@example.com, Email Address the same, a Password you'll remember. Uncheck Require Change of Password on First Use. Create User. - Do the same for
omar.haddad@example.com.
Check: run Turnout and sign in as grace.kim@example.com.
- Open Tuesday Track Intervals. Grace is "On the waitlist, number 1", so the button says Cancel my RSVP. Click it and confirm: "Your RSVP is cancelled." Now click RSVP: the event is still full, so she's back on the waitlist, but at number 2. Lucas moved up when she left. That's Lesson 3.5 working through your page.
- Open Clubs › Bay Chess Club and click Join this club: "Request sent. An organizer will approve it.", because Bay Chess Club approves its members (Lesson 3.3).
Sign out and sign in as omar.haddad@example.com.
- Open Saturday Long Run, set Guests to
3, and click RSVP. You get "You can bring up to 2 guests." fromTO_ERR, and you stay on the page with your 3 still in the box. Change it to1and RSVP again: "You're going. See you there.", and Your RSVP says "You're going with 1 guest(s)".
You changed the sample data. Before you run the Part 3 tests again, run turnout_seed.sql again to put it back.
Check yourselfWhat happens to the branch when the package raises an error?
It doesn't run; APEX stays on the page and shows the error message.
Dialogs close instead of branching. If you build page 35 as a dialog (the exercise in Lesson 4.5), its last step is a Close Dialog process instead of a branch, and page 20 refreshes itself with a Dialog Closed dynamic action (Part 8).
Lesson 4.5 — Deep links: sign in and come back
Outcome: turn on Deep Linking, and know when APEX brings someone back to the page they asked for after they sign in.
Try the problem first. Sign out, open an event, tap Sign in to RSVP, and sign in. You land on Discover, and have to find the event again. That's the moment people give up.
APEX can bring them back, when two things are true:
- Deep Linking is enabled. When someone asks for a page that needs sign-in, APEX remembers the page and its items, shows the sign-in page, then sends them on.
- The link points at a page that needs sign-in, not at the sign-in page itself. Linking straight to page 9999 throws the destination away, because the sign-in page is public: there's nothing to come back to.
Turnout's Sign in to RSVP button links straight to page 9999, so for now it still lands on Discover. Part 7 adds pages that need sign-in, such as My events, and that's where you'll see deep linking work.
Build it
Shared Components › Security Attributes. Under Session Management, set Deep Linking to Enabled. Apply Changes.
Check: the setting shows Enabled. In Part 7, sign out, paste the address of My events into the browser, and sign in: you land on My events, not Discover.
Exercise (not in the finished app). Make Sign in to RSVP bring people back. Create page 35,
RSVP, as a Modal Dialog with AuthenticationPage Requires Authentication: a hiddenP35_EVENT_ID, a number fieldP35_GUESTS, an RSVP button, a process callingto_rsvp_api.rsvp(:P35_EVENT_ID, nvl(:P35_GUESTS, 0))when it's pressed, then a Close Dialog process. Point page 20's SIGN_IN button at page 35 withP35_EVENT_IDset to&P20_EVENT_ID.. A visitor now signs in and lands on the dialog for the same event. Keep this in a copy of your app: the later parts assume SIGN_IN still goes to page 9999.
Check yourselfWhy does linking straight to the sign-in page lose the visitor's place?
APEX only remembers the destination when the requested page needs sign-in; the sign-in page itself is public, so there's nothing to come back to.
Lesson 4.6 — Breadcrumbs: a way back up
Outcome: the Club and Event pages show where you are, "Clubs \ Tampa Runners \ Saturday Long Run", and each step is a link back.
The decision. The top menu takes you to Discover and Clubs, but once you're on a club or an event there's no visible way back up. A breadcrumb fixes that. In APEX it's a shared component, a tree of entries, one per page, each pointing at its parent. A Breadcrumb region on a page draws the path from the top of the tree down to that page's entry.
One wrinkle: the Club entry has to show the club's name, and the Event page needs it too, for the middle step. So the name lives in two application items, small variables that belong to the session rather than to one page. Each page's load process sets them, and the breadcrumb reads them with &CURRENT_CLUB_NAME..
| Entry | Page | Parent | Shows | Links to |
|---|---|---|---|---|
| Clubs | 2 | none | Clubs | page 2 |
| Club | 10 | Clubs | &CURRENT_CLUB_NAME. |
page 10, with P10_CLUB_ID = &CURRENT_CLUB_ID. |
| Event | 20 | Club | &P20_TITLE. |
(the page you're on) |
Your app may already have a Clubs entry; Turnout's does. If yours doesn't, you'll make it first.
Build it: the application items
- Shared Components › Application Items › Create. Name
CURRENT_CLUB_ID, ScopeApplication, Session State ProtectionRestricted - May not be set from browser(the default). Create Application Item. - Again for
CURRENT_CLUB_NAME.
Restricted means a visitor can't set them from a link: only your PL/SQL can.
Build it: the breadcrumb entries
- Shared Components › Breadcrumbs, open Breadcrumb (the Create Application wizard made it). If the tree on the left has no Clubs, click New Entry and make it: Page
2, no parent, Short NameClubs, Target page2, Static IDclubs, Create Entry. - Click Clubs in the tree, then New Entry:
| Field | Value |
|---|---|
| Page | 10 |
| Parent Entry | Clubs (Page 2) |
| Short Name | &CURRENT_CLUB_NAME. |
| Synchronize page name and title | Unchecked (otherwise it renames page 10) |
| Target is a | Page in this Application, Page 10 |
| Set these items / With these values | P10_CLUB_ID / &CURRENT_CLUB_ID. |
| Static ID | club |
Create Entry.
- Click the new entry in the tree, then New Entry again: Page
20, Parent Entry the club entry (Page 10), Short Name&P20_TITLE., Synchronize unchecked, Target page20withP20_EVENT_ID/&P20_EVENT_ID., Static IDevent. Create Entry.
Build it: the regions
- Page 10. In Page Designer, right-click Body, Create Region, then set Layout › Slot to Breadcrumb Bar (a page only lists the slots it uses). Name
Breadcrumb, TypeBreadcrumb, Source › BreadcrumbBreadcrumb, Appearance › TemplateTitle Bar. The Title Bar template's default option, Use Current Breadcrumb Entry, shows the club's name as the page heading. - The breadcrumb now carries the club's name, so the Club region doesn't need to repeat it: set its Title to
Club details. - Open the Load club process and add two lines at the end:
-- For the breadcrumb: which club we're in
:CURRENT_CLUB_ID := :P10_CLUB_ID;
:CURRENT_CLUB_NAME := :P10_NAME;
- Page 20. The same Breadcrumb region, in the Breadcrumb Bar, Title Bar template. Set the Event region's Title to
Event details. - In page 20's Load event process, the first
selectalready joins the club. Add two columns and two items to it, so the end of the column list and theintoread:
e.description,
e.club_id, c.name -- for the breadcrumb
into :P20_TITLE, :P20_CLUB, :P20_WHEN, :P20_WHERE, :P20_DESCRIPTION,
:CURRENT_CLUB_ID, :CURRENT_CLUB_NAME
Check: sign out and open Clubs › Tampa Runners. Above the page you see "Clubs \" and the heading Tampa Runners. Open Tuesday Track Intervals: the path reads "Clubs \ Tampa Runners \" with the event as the heading. Click Tampa Runners in the path and you're back on the club. Now open Bay Chess Club from Clubs and one of its events: the middle step says Bay Chess Club, because each load process set the items afresh.
Check yourselfWhy not give the Club entry the short name &P10_NAME.?
On page 20, P10_NAME still holds whichever club page 10 showed last, or nothing at all if you came straight from Discover. The application items are set by the page you're on.
Checkpoint
Turnout runs: four public pages anyone can browse from a shared link, with breadcrumbs on the club and event pages, a sign-in page, and Join and RSVP for signed-in members, all through your Part 3 packages.
| Download | What it is |
|---|---|
| Start here | Everything from Parts 2 and 3 in one script, if you're starting at Part 4 |
| Finished | Turnout as it is at the end of this part, to compare with yours or to catch up |
Compare with the finished app. Open the downloaded turnout-app-part-4.zip and read pages/p00020-event.apx in any text editor. It lists every region, item, button and process on the page, with the same names you used. If your page behaves differently, the difference is usually in there.
Catching up instead? Import turnout-app-part-4.zip in App Builder › Import. When APEX offers to install supporting objects, click Install Supporting Objects only if your schema has no Turnout tables yet; they install the tables, sample data and packages for you. Then do "People to sign in as" in Lesson 4.4.
The exercises from Lessons 4.2 and 4.5 (slug links and the RSVP dialog) aren't in the finished app: they're yours.
Downloads for Part 4
Everything this part uses. Each file is plain text you can open in any editor before you run it.
- Start hereturnout_start_part4.sqlTables, sample data and packages from Parts 2 and 3, if you're starting at Part 4
- Finishedturnout-app-part-4.zipTurnout at the end of Part 4. Import it to compare, or to catch up
- Part 4 kitturnout-part-4.zipBoth of the above and a README
- Start overturnout_reset.sqlRemoves every TO_ object from your schema
Part 4 quiz
5 questions. Get 4 right to pass. You can retake it as often as you like.