Initial commit

Co-Authored-By: kotte <14197736+mrtamagotchi@users.noreply.github.com>
Co-Authored-By: mikaeltellhed <2311083+mikaeltellhed@users.noreply.github.com>
Co-Authored-By: Tore Knudsen <18231882+torekndsn@users.noreply.github.com>
Co-Authored-By: Michael Cartner <32543275+michaelcartner@users.noreply.github.com>
This commit is contained in:
Eric Tuvesson
2023-09-05 12:08:55 +02:00
commit 53f0d6320e
2704 changed files with 76354 additions and 0 deletions

View File

@@ -0,0 +1,199 @@
---
title: Basic Navigation
hide_title: true
---
import ImportButton from '../../../src/components/importbutton'
# Basic Navigation
![](/docs/guides/navigation/basic-navigation/final.gif)
## What you will learn in this guide
In this guide you will be introduced to the [Page Router](/nodes/navigation/page-router), the [Page](/nodes/navigation/page) and the [Navigate](/nodes/navigation/navigate) node to create a simple UI where you can navigate between pages. You will learn how to navigate both by interacting with the UI or by entering URLs directly in the browser.
### Overview
We will go through the following steps in the tutorial
- Set up a **Page Router** with three **Pages**
- Configure the URL for the pages
- Implement navigation in the App
## What is Web Style Navigation?
Noodl supports two types of navigations: App style navigation and Web style navigation. This guide covers the Web style navigation using the **Page Router** and **Pages**.
On a high level the main differences are
- Web Style:
- Each Page has a distinct URL and by typing that URL in a browser window you will be routed to that page.
- You can encode data in the URL that can be extracted in the Page.
- The browser keeps track of your navigation stack, i.e. back history. The user can click back/forward in the browser window to navigate.
- Generally there are no transitions when moving between pages.
- App Style:
- The browser is unaware of the navigation - there is no change in URL when navigating.
- Noodl keeps track of the navigation stack.
- You can use different transitions when navigating.
You can mix the two navigation styles. For example a web page could have URLs leading in to the main section of the web site and within each section you use App style navigation.
When to use which style depends what the user expects from your app. Should it work like classic web-site or more like an app.
This guide will focus on Web Style navigation.
## The Page Router
The first node to look at in this guide is the **Page Router**. This node mainly keeps track of two things:
- Provide space for the **Page** that is currently showing
- Keep track of the other **Pages** you can navigate to
The **Page Router** node is a visual node meaning it takes up space on the screen. Any **Page** you navigate to will become child of the **Page Router** and use its space. So when designing your navigation flow, you typically create a bunch of **Page** components holding the content for each page and navigate between them. They will be put in the space that the **Page Router** takes up. All nodes in the visual hierarchy that are not in the **Pages** will stay on screen no matter how you navigate. So title and footer would normally not be part of the **Page** but lie next to the **Page Router** in the hierarchy.
Let's begin by creating a new project based on the "Hello World" Template. Delete the existing **Text** node and add in a **Page Router** using the Node Picker.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/basic-navigation/page-router-1.png)
</div>
Click on the **Page Router** to review its settings.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/basic-navigation/page-router-2.png)
</div>
For now - lets only change one thing: The background color to more clearly see the area on the screen that the page router takes up. Change it to a nice grey color.
As you can see, the whole screen changes color. It's because the **Page Router** by default takes up as much screen as it can on the screen.
Lets create a simple sidebar and add to the App. Since this sidebar is lying next to the **Page Router** in the hierarchy it will be a consistent component in the App no matter where you navigate. Navigation can only change what's in the **Page Router**.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/basic-navigation/ui-1.png)
</div>
The sidebar is 100 px wide and white. It's placed together with the **Page Router** in a [Group](/nodes/basic-elements/group) node with a horizontal layout.
## Pages
The next step is to add a few pages. The **Page** node is a bit special in the way that you cannot create it using the Node Picker. Instead you add it from the components sidebar as part of a component. Click "+" and the "Page Component". Call the page "Home".
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/basic-navigation/add-page-component.png)
</div>
Then add two more pages, "Products" and "Settings". Your component list should look as the image below.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/basic-navigation/components.png)
</div>
If you click one of the Page components you will se that it has two nodes by default. A **Page** node and a [Page Inputs](/nodes/navigation/page-inputs) node.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/basic-navigation/page-1.png)
</div>
Let's ignore the **Page Inputs** node for now. We will use it in later guides to send parameters to the **Pages**.
The **Page** node is a container that will hold the content you want to show. Let's add some content to it.
Add a **Group** node and then a [Text](/nodes/basic-elements/text) node as a child node. The **Text** will become the title of the page, so give it the name "Title" and center it. Also change the text so it matches the page you are changing, for example "Home" if you are in the Home page.
To more easily differentiate the pages from each other we will change the color of the Group to something that stands out, for example red.
Now change the other two pages, i.e. add the nodes, update the text title and change the color to a new color. You can select the two nodes and then copy the them using (Ctrl+C/Cmd+C) and paste them (Ctrl+P/Cmd+P) into the other pages.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/basic-navigation/page-2.png)
</div>
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/basic-navigation/screen.png)
</div>
As you change the pages you will see that one of them is already shown in the **Page Router**, the one that you added first. It has become the **Starting Page** of the **Page Router**. If you want to change that, you can go back to the Main App and click the **Page Router**. You will now see that there are two **Pages** added to your list of available **Pages** in the **Page Router**. You can hover over the **Pages** and a edit icon appears. It will open a menu where you can remove the page or make it the starting page. Try changing the starting page. You need to refresh the viewer for the changes to take place.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/basic-navigation/page-router-3.png)
</div>
## Page paths
Now, lets look at the paths to the pages. Go back to one of the **Page components** and click the **Page** node. You will se that it has a Title and a path that's based on the name of the component.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/basic-navigation/page-props.png)
</div>
For the case of the "Home" Page Component, it's path is "home". This means that the url to that page is `<your deployed domain>/home`. Let's try it with our viewer running on localhost.
Open a window in your favourite browser. Enter the URL `http://localhost:8574#/home`. Then try the paths to the other pages, for example `http://localhost:8574#/products`, `http://localhost:8574#/settings`. The URLs should lead you to the different pages.
## Navigating in the app
Finally lets add some navigation within the app. First step is to add buttons to the empty sidebar.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/basic-navigation/sidebar-1.png)
</div>
Change the buttons slightly to fit with the sidebar design, for example enabling the icon, removing the label and adding some padding and margins.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/basic-navigation/ui-2.png)
</div>
Finally we are going to connect this buttons to a [Navigate](/nodes/navigation/navigate) node, that will trigger the page navigation. So create three **Navigation** nodes. When you click them, you will see that you can change their target page to either one of the **Page Components** that are available in the **Page Router**.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/basic-navigation/navigate-1.png)
</div>
Make sure the three **Navigate** nodes points to the right target, and then connect the **Click** signal from the button to the **Navigate** signal on the **Navigate** nodes. Make sure the the correct button is connected to the correct **Navigate** node.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/basic-navigation/signal-1.png)
</div>
Now try clicking the buttons. If everything was set up correctly you should see the **Page Component** changing on the screen. Also try it in the browser and see how the URL changes. Try pressing the "Back"/"Forward" button in the browser and see how you move back and forth in the browser history.
If you want import the full project in Noodl click the "Import" button below and follow the instructions below.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/basic-navigation/final.gif)
<ImportButton zip="/docs/guides/navigation/basic-navigation/basic-navigation.zip" name="Basic Navigation" thumb="/docs/guides/navigation/basic-navigation/ui-2.png"/>
</div>

View File

@@ -0,0 +1,551 @@
---
title: Component Stack
hide_title: true
---
import ImportButton from '/src/components/importbutton';
import ReactPlayer from 'react-player';
# Component Stack Guide
## What you will learn in this guide
In this guide we will take a look at the [Component Stack](/nodes/component-stack/component-stack-node) node and the related navigation node [Push Component To Stack](/nodes/component-stack/push-component) and [Pop Component From Stack](/nodes/component-stack/pop-component).
They are an alternative navigation component compared to the [Page Router](/nodes/navigation/page-router) and related nodes. Instead of giving each **Page** a URL, and making use if the browser back history, **Component Stack** is completely handled from within the App. Both these styles of navigations can of course be combined. In this guide we will combine **Component Stack** with **Popups**. You will learn how to make a little modal wizard.
## Overview
The guide will cover the following topics
- Defining a component stack
- Pushing and popping components on the stack
- Forwarding data between components on the stack and outside the stack
- Keeping track of which component that is on top of the stack (and how many)
- Transitions when pushing and popping components
The guide is a continuation of the [Popup Guide](/docs/guides/navigation/popups) and it's recommended that you go through that guide first. It's also good to know the basics on data in Noodl, so also have a look at the [Object](/docs/guides/data/objects) and [Arrays](/docs/guides/data/arrays) guides before starting this guide. We will also use [Events](/docs/guides/business-logic/events) in the guide.
## Creating a modal Wizard using **Component Stack**
This guide will show you how to create a little "Wizard" type UI contained in a **Popup**. It's a continuation on the [Popup Guide](/docs/guides/navigation/popups). So start by importing the project below (by clicking the Import Button) into Noodl if you haven't finished that guide.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/component-stack/popup-final.png)
<ImportButton
zip="/docs/guides/navigation/component-stack/popups.zip"
name="Popup Example"
thumb="/docs/guides/navigation/component-stack/popup-final.png"
/>
</div>
## Navigation within a Popup
To quickly recap what we achieved in the Popup Guide. We had a list of People and we created a Popup that had us confirm any deletion in the list. We also created a `Popup Base` component that we can use to make new nice looking Popups.
We now want to do a wizard style **Popup** were the user can add more persons to our list of people. "Wizard style" meaning the user will move through multiple screens within the **Popup** to create the entry. Normally, navigation in Noodl is done using the **Page Router** node (check out the guide [here](/docs/guides/navigation/basic-navigation)), but this it typically not meant for modal states of the app, since each **Page** have a unique URL. Instead you can use the [Component Stack](/nodes/component-stack/component-stack-node) node which doesn't affect the URL and therefore is more fit for a modal state in the app. It also supports transitions which is nice.
We want our "Add new Person" popup two have three states.
1. Fill out first name, last name and age of the new person to add to the list
2. Select country
3. Based on the country, select city
We also need to think about the data aspect. From the Popup guide we know that each person is stored in an object of this type:
```json
{
"firstname":<first name of the person>,
"lastname":<last name of the person>,
"age":<The age of the person>,
"location":<the city and country where the person lives>
}
```
The plan is the following:
1. When we open the popup, we create a new **Object** represening the new person
2. The first screen fills out `firstname`, `lastname` and `age` of the **Object**
3. The second screen the user selects their country
4. The third screen presents city options based on the country and then stores location in the **Object**
5. When the popup is closed, the new **Object** is added to the **Array**
Let's get started!
Start by creating a new Visual Component. Call it `Add New Person Popup`. Replace the **Group** node in it with a `Popup Base`. Change the title of it to "Add New Person".
As content in that **Popup** we are going to start with a **Text** node which will be consistent in the **Popup** that holds the current step of the wizard. ("Step x of 3"). So add a **String Format** node with the string `Step {current_step} of 3`.
Then we want our dynamic content that will change as the user progresses through the wizard. Create a **Component Stack** and add after the **Text** node.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/component-stack/add-new-person-1.png)
</div>
## Adding components to a **Component Stack**
We are ready to create our components in our **Component Stack**.
:::note
When using a **Component Stack** we should not use **Page** components, since they are only to be used with **Page Routers**.
:::
Click on the **Component Stack**. Click the `+` under "Components" to add new components.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/component-stack/component-stack-1.png)
</div>
We want three components, call them `Step 1 - Name`, `Step 2 - Country`, `Step 3 - City`.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/component-stack/component-stack-2.png)
</div>
We are going to need to create components for each of these components. To keep better track of them we will put them in a folder of its own, so create a new component folder, `Create Person Wizard`. Create three visual components in the folder called `Step 1`, `Step 2` and `Step 3`.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/component-stack/folder-1.png)
</div>
You can now assign the components in the **Component Stack** in the `Add New Person Popup`. Click on it and set the components.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/component-stack/component-stack-3.png)
</div>
It's probably time that we add a **Button** to open the **Popup**. Go back to the main App. Add a horizontally centered **Button** with the label `Add New Person`. Also give it some top margin seperate it from the list. Then add a new **Show Popup** node. Connect the **Click** signal from the **Button** to the **Show Popup** signal **Show**. Also make sure to select the **Add New Person Popup** as the component to show.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/component-stack/list-1.png)
</div>
Make sure the **Button** opens up the **Popup**.
Now let's work on the data part of the Wizard. We want to start by creating a new **Object** that we can fill out with data as the wizard progresses. In the `Add New Person Popup` we add a **Create New Object** node. We want to trigger the creation of the **Object** when the **Popup** is created. However we are not exposing the **Did Mount** signal on our `Popup Base` component, so we need to do that first. Open the `Popup Base` component, add a new property `Did Mount` on the **Component Outputs** and connect it to the **Did Mount** signal of the root **Group**.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/component-stack/popup-base-1.png)
</div>
We can now connect the `Did Mount` signal from the `Popup Base` component to the **Do** signal of the **Create New Object** node in the `Add New Person Popup`, creating a new **Object** once the **Popup** opens.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/component-stack/add-new-person-2.png)
</div>
## Sharing data betweem components in a **Component Stack**
This new **Object** is the one we will fill with data in the Wizard, so we need to be able to access it in our different subcomponents, `Step 1`, `Step 2` and `Step 3`. We could store the **Id** of the **Object** in a [Variable](/nodes/data/variable/variable-node) but an even neater solution is to use a [Component Object](/nodes/component-utilities/component-object). While **Variables** are global, the **Component Object** is only available to the children (and sub children) of the component where it resides, so there is no risk that someone accidently changes it where it shouldn't.
:::tip
The components on the **Component Stack** are considered children of the **Component Stack**, hence using **Component Objects** as a way to share data between different components on the **Component Stack** is often a good idea.
:::
So in the `Add New Person Popup`, create a **Component Object**. Give it a property `New Person Object Id`. Then connect the **Id** of the **Create New Project** to the new property of the **Component Object**.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/component-stack/add-new-person-3.png)
</div>
We can now start creating our simplr form in the first component, `Step 1`. First, lets get our newly created **Object** set up. This time we use the [Parent Component Object](/nodes/component-utilities/parent-component-object) to retrieve it. Make sure to add the same property as before `New Person Object Id`. Then we can connect it to the **Id** of an **Object** node to retrieve the **Object**.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/component-stack/step-1-1.png)
</div>
So let's add three [Text Input](/nodes/ui-controls/text-input) fields (for first name, last name and age). Hook them up to the **Object** (by adding the properties `firstname`, `lastname` and `age` - the last one being a number input only **Text Input**). Also add two **Buttons** one for "Cancel" and one for "Next" in a horizontal layout in a **Group**.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/component-stack/step-1-2.png)
</div>
## Sending events to communicate outside the **Component Stack**
Left to do on this screen is to store the content of our **Text Inputs** in the **Object** once the user clicks "Next" and then go to the next step, `Step 2`. We also need to handle the "Cancel" click. Let's start with the latter.
When the user clicks "Cancel" we want to close the **Popup** but we have to do **Close Popup** in the component that was opened directly using a **Show Popup** (i.e. the `Add New Person Popup`). How do we notify that component that the user clicked "Cancel"? Again, since the component that's on top of the **Component Stack** can be seen as a child to the stack, we can [Send an Event](/nodes/events/send-event) to the parent component (for a dedicated guide on **Events**, see [here](/docs/guides/business-logic/events)).
So add a **Send Event** node and give the channel name `Cancel Add Person`. Make sure it's only directed towards the parent.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/component-stack/event-1.png)
</div>
Then trigger **Send** when the user clicks "Cancel".
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/component-stack/step-1-3.png)
</div>
Go back to the `Add New Person Popup`. We need to add a [Receive Event](/nodes/events/receive-event) node here to react to the event. Make sure the `Cancel Add Person` channel is set. Also consume the event (set it to `Always`) to avoid the event propagating the tree - it's not needed beyond this component. Add a **Close Popup** node. Then we trigger the **Start Close Transition** signal on the `Base Popup` (to start the close transition) and then when the transition is done (the **Close Transition Done** signal is triggered), we trigger **Close** on the **Close Popup Node**.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/component-stack/add-new-person-4.png)
</div>
## Pushing and replacing components on the **Component Stack**
Ok, now let's go back to the `Step 1` component to finish it off. First, we need to save the content of the **Text Inputs** in the **Object**. We do this by adding a **Set Object Properties** node, add our three properties (`firstname`,`lastname`, `age`). Also, don't forget to set the **Id** to the same **Id** we got from our **Parent Component Object**. Then we connect the **Click** signal on the "Next" **Button** to the **Do** signal on the **Set Object Properties**.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/component-stack/step-1-4.png)
</div>
After saving we need to move to the `Step 2` component. Add a **Push Component To Stack** node. When you click on it, you can see a number of options. We see that it's using our `Main` (if we had more than one **Component Stack** in our project we would have to select the right one here). First let's change the **Target Component** to `Step 2 - Country`. We also see the **Mode** property. Here you can select **Push** or **Replace**. Since we want to be able to let the user go back in the Wizard in the each step, we probably want to keep **Push** as the mode. Essentially each component will be stacked on top of each other, and we can use the **Pop Component Stack** to go back to the previous component on the stack.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/component-stack/push-component-1.png)
</div>
We should trigger the **Navigate** signal on the **Push Component To Stack** when the data is stored, using the **Done** signal.
## Tweaking Transitions
Try out the navigation to the next step. You will see that the **Dark Overlay** option in the **Push Component To Stack** makes it looks weird so untick that option. Feel free to play around more with the transitions. Maybe change the **Shift Transition** to 100% to make it look more like a proper transition. You will see that it's important the the containing **Group** node clips its children for the transition to work. In the `Popup Base` component, that is the second **Group** that holds the **Component Children**.
## Editing `Step 2`
This will be a simpler form, containing a [Radio Button Group](/nodes/ui-controls/radio-button-group) with our country options. So let's add it in, together with three [Radio Buttons](/nodes/ui-controls/radio-button). We only have three options in this case, `England`, `Scotland` and `Ireland`. Make sure both **Label** and **Value** of the **Radio Buttons** are set to the respective value. Also add a sub title that says `Pick Country`. Finally, add two buttons at the bottom `Back` and `Next`. We can copy/paste them from the previous step and just change the title of the first **Button**.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/component-stack/step-2-1.png)
</div>
## Popping from the **Component Stack**
We start by adding in a **Pop Component Stack** node. As you can see you can send results back in a similar way as the **Close Popup** (**Results** and **Back Actions**). In this case we don't have any data to pass. Instead just connect **Click** from the "Back" button to **Navigate**.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/component-stack/step-2-2.png)
</div>
As you can see, popping from the stack plays the push transition, but in reverse. We can see that having different sizes of our components is a little bit annoying since the popup changes size dynamically as they are shown and hidden. Let's fix that by having a fixed size on the components `Step 1`, `Step 2` and `Step 3`.
We do this by going to our `Add New Parson Popup` and move in a new **Group** with a fixed size (250px / 300 px) as a parent to the **Component Stack**.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/component-stack/add-new-person-5.png)
</div>
While we see that there is still a little layout-tweaking to be done (the buttons at the bottom moves around, etc). Let's fix that quickly. We want to make sure the buttons are always at the bottom of the screen. So find the **Group** containin the **Buttons** for `Step 1` and `Step 2`. Make sure to remove the **Margin** of the **Group**, or it will compete with the alignment, and then align it at the bottom.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/component-stack/group-alignment.png)
</div>
Great, now the buttons stay at the bottom!
Instead we want to add `Step 3` to the **Component Stack** when the user clicks "Next" in Step 2. We also need to pass on the selected country. We could of course store it in our **Object** but as you can see in the data model above, the country is not stored seperately. Instead we want to pass it on to `Step 3`. We could store it in the **Component Object**, as before, but in this case we can make use of the **Component Inputs** of the component to be opened.
Go to `Step 3` and add in a **Component Inputs** node. Add the port `Country`.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/component-stack/step-3-1.png)
</div>
<div className="ndl-image-with-background">
![](/docs/guides/navigation/component-stack/comp-input-1.png)
</div>
## Forwarding data to a component on the stack using **Component Inputs**
Now, if you go back to `Step 2`, and add in a **Push Component To Stack** and select `Step 3` as the target (also fix the transition as before), you will se that there is a new input called `Country`. We can connect the **Value** of the **Radio Button Group** to the `Country` input. Also connect **Click** on the "Next" **Button** to **Navigate** on **Push Component To Stack**.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/component-stack/step-2-3.png)
</div>
Great! Let's move to `Step 3` then. Here we want to display options of cities depending on which country that was selected. Let's create a **Static Array** (set to JSON format) with the following data:
```json
[
{ "city": "Bristol", "country": "England" },
{ "city": "Hartlepool", "country": "England" },
{ "city": "Newcastle", "country": "England" },
{ "city": "Wigan", "country": "England" },
{ "city": "Kilkenny", "country": "Ireland" },
{ "city": "Dublin", "country": "Ireland" },
{ "city": "Cork", "country": "Ireland" },
{ "city": "Galway", "country": "Ireland" },
{ "city": "Glasgow", "country": "Scotland" },
{ "city": "Edinburgh", "country": "Scotland" },
{ "city": "Dundee", "country": "Scotland" },
{ "city": "Kildrummy", "country": "Scotland" }
]
```
We want to present a **Radio Button Group** with only the valid cities for the country that was selected in the previous step. So let's start with an [Array Filter](/nodes/data/array/array-filter).
Add a filter on the property `country`.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/component-stack/array-filter-1.png)
</div>
Make sure it's of the **Equals** type (and a String).
<div className="ndl-image-with-background">
![](/docs/guides/navigation/component-stack/array-filter-2.png)
</div>
Then connect **Items** of the **Static Array** to the **Items** of the **Array Filter**. Then connect the `country` output of the **Component Inputs** to the **Value** of the country filter.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/component-stack/array-filter-3.png)
</div>
Add an **Array** to have a look at the results. We can now see that only the items that matches the country is filtered out. Nice!
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/component-stack/step-3-2.png)
</div>
Now we need to visualize the option in a list, using a **Radio Button Group**, **Repeater** and **Radio Button**. Start by creating the list item. Call it `Step 3 List Item`. It should look like below:
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/component-stack/step-3-list-item-1.png)
<CopyToClipboardButton
json={{
nodes: [
{
id: '5d3f90ec-abf3-7473-b43f-935e02141387',
type: 'Group',
x: -163,
y: -64,
parameters: { height: { value: 30, unit: 'px' } },
ports: [],
children: [
{
id: '331e1282-31df-46ad-73ca-69ba2b03f4bc',
type: 'net.noodl.controls.radiobutton',
x: -143,
y: -18,
parameters: { alignY: 'center' },
ports: [],
children: [],
},
],
},
{
id: 'b62673b4-2625-d20c-f473-81b537b7cc54',
type: 'Model2',
x: -575,
y: -92,
parameters: { properties: 'country,city', idSource: 'foreach' },
ports: [],
children: [],
},
{
id: '65ebe7fd-a47d-9edc-8e6d-dac435066879',
type: 'String Format',
x: -343,
y: 67,
parameters: { format: '{city}, {country}' },
ports: [],
children: [],
},
],
connections: [
{
fromId: 'b62673b4-2625-d20c-f473-81b537b7cc54',
fromProperty: 'prop-city',
toId: '331e1282-31df-46ad-73ca-69ba2b03f4bc',
toProperty: 'label',
},
{
fromId: 'b62673b4-2625-d20c-f473-81b537b7cc54',
fromProperty: 'prop-city',
toId: '65ebe7fd-a47d-9edc-8e6d-dac435066879',
toProperty: 'city',
},
{
fromId: 'b62673b4-2625-d20c-f473-81b537b7cc54',
fromProperty: 'prop-country',
toId: '65ebe7fd-a47d-9edc-8e6d-dac435066879',
toProperty: 'country',
},
{
fromId: '65ebe7fd-a47d-9edc-8e6d-dac435066879',
fromProperty: 'formatted',
toId: '331e1282-31df-46ad-73ca-69ba2b03f4bc',
toProperty: 'value',
},
],
}}
/>
</div>
The **Object** has two properties `country` and `city` and gets its **Id** from the **Repeater**. Also note that the **Value** of the **Radio Button** is using a **String Format** (with the string `{location}, {city}`) to construct the final location string that matches the data model (for example `Dublin, Ireland`).
Then we go back to `Step 3` component and add in a **Radio Button Group** and a **Repeater** (using our new `Step 3 List Item`) and then feed it with data. With some margin tweaking it will look something like below.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/component-stack/step-3-3.png)
</div>
Ok, we continue by copying and pasting the **Buttons** from the previous step (we should really make these into a re-usable component!). Let's also bring the **Pop Component Stack** node as well (connected to the "Back" button) as we need one in this step as well.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/component-stack/step-3-4.png)
</div>
## Saving the created **Object**
Ok we are almost done with our wizard. We need to save the location to the **Object** containing the info on the new person. We do it in the same way as in the `Step 1` component, we use a **Parent Component Object** to retrieve the **Object** that was created in the **Popup**. This time we store the **Value** of the **Radio Button Group** in a property called `location` We store the value when the user clicks "Next".
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/component-stack/step-3-5.png)
</div>
Finally we need to let the `Create New Person Popup` know that the **Object** is filled out when the user clicks "Next" on the last stage. Again, we do that by sending an **Event** this time with the **Channel Name** `Confirm Add Person`, send it to **Parent**. We do that once the **Set Object Properties** is done.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/component-stack/step-3-6.png)
</div>
## Passing data from the **Popup**
We go back to our `Add New Person Popup` to receive the event. Add in a **Receive Event Node** receiving from the channel `Confirm Add Person`.
### Event logic
We are going to need some extra logic here. Since both the `Cancel Add Person` and `Confirm Add Person` will trigger the close transition of the popup we need to be able to distinguish the two cases one the transition is ready. Because, in the `Confirm Add Person` case we want to pass the created **Object** to the main App, so it can be added to the list of People.
We add in a **Switch** node, that's only set to true if the `Confirm Add Person` event is received. The, once the transition is closed, we use a **Condition** node to test the value of the **Switch**. Depending on the case (i.e. **onTrue** or **onFalse** from the **Condition** node) we trigger two different **Close Actions** on the **Close Popup** node (`Confirm` or `Cancel`). We also make sure to pass the **Id** of the **Object** representing the person in a result called `New Person Object Id`.
Here's how the whole thing looks.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/component-stack/add-new-person-6.png)
</div>
## Adding the **Object** to the **Array**
Now we need to receive the **Object** in our main App. We will get it through the `New Person Object Id` and add it if we get the `Confirm Add Person` signal from the **Popup**. We use the **Insert Object into Array** node to add it.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/component-stack/list-2.png)
</div>
Try it out, we can now add new persons to our list using our little Wizard in a popup!
## Reading which component is on top of the **Component Stack**
In our Wizard we still have our text `Step x of 3`. We want to make that work. Go back to the `Add New Person Popup`. The **Component Stack** has an output called **Top Component Name** that will hold the name of the component that's currently on top Note that it's the name of the component in the **Component Stack**, not the component that's used to represent it. So in out case it will be `Step 1 - Name`, `Step 2 - Country`, `Step 3 - Location`. If we somehow could transfor this to the number 1,2 or 3, we can just feed it into our **String Format**. What might be an even easier solution is to use the **Stack Depth** property. Since we use **Push** rather than **Replace** our **Component Stack** will grow in size from 1 to 2 to 3, which is exactly what we want. So we connect it to the **String Format** node.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/component-stack/stack-depth.png)
</div>
## Making sure the data is filled out
We are almost done. There are many ways to improve this wizard but one obvious thing is that we want users to be forced to select something before moving to the next step. For example not selecting a country will mess up `Step 3`.
In `Step 1` we can look at the length of the **Text** value coming out of the **Text Input** nodes. We use the **String** node which has a **Length** output. We connect these three length values to an expression that makes sure all three of them are greater than zero (using the expression `a>0&&b>0&&c>0`). Then we connect the **Result** of that expression to the **Enabled** input of the "Next" **Button**.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/component-stack/step-1-6.png)
</div>
In `Step 2` and `Step 3` we essentially do the same, but we look at the length of the **Value** of the **Radio Button Group**.
We are done! We have a small wizard in a Popup to fill information when adding new persons!
<ReactPlayer
playing
autoplay
muted
loop
url="/2.9/docs/guides/navigation/component-stack/component-stack-final.mp4"
/>
Click the Import button below to import the final project.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/component-stack/final-1.png)
<ImportButton
zip="/docs/guides/navigation/component-stack/component-stack-1.zip"
name="Wizard in Popup"
thumb="/docs/guides/navigation/component-stack/final-1.png"
/>
</div>

View File

@@ -0,0 +1,371 @@
---
title: Encoding Parameters in URLs
hide_title: true
---
import ImportButton from '../../../src/components/importbutton'
# Encoding Parameters In URLs
## What you will learn in this guide
This guide will teach you how to pass parameters in the URL when you are navigating between pages, either as the final part of a path, e.g. "mysite.com#/path/parameter" or as query strings, "mysite.com#/path?parameter1=val1&parameter2=val2".
Noodl is handling this through its [Page Inputs](/nodes/navigation/page-inputs) node and [Navigate](/nodes/navigation/navigate) node.
The main reason to encode data needed by a **Page Component** in URLs rather than using regular data nodes is that the data becomes independent of the App state. For example
- If a user refreshes the browser running the App, the data will still be available to the **Page Component** in the URL.
- If a user want to be able to share the exact state of an App with another user, you can encode that state in the URL.
## Overview
The guide will cover the following
- Pass data to a **Page Component** as a **Path Parameter**
- Navigate to a path and set the **Path Parameter**
- Pass data to a **Page Component** using a **Query Parameter**
The guide assumes that you are familiar with basic Web Navigation concepts and it's suggested that you have already gone through the [Basic Navigation](/docs/guides/navigation/basic-navigation) and [Multi Level Navigation](/docs/guides/navigation/multi-level-navigation) guides before you go through this guide.
We will also use the example app developed as part of the two guides as a starting point in this guide. If you haven't built it already, you can import it by clicking the button below and follow the instructions in Noodl.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/multi-level-navigation/multi-level-final.gif)
<ImportButton
zip="/docs/guides/navigation/multi-level-navigation/multi-level-navigation.zip"
name="Multi Level Navigation"
thumb="/docs/guides/navigation/multi-level-navigation/multi-level-thumb.png"
/>
</div>
## Path Parameters
To demonstrate a typical use of **Path Parameters** we will have to create a list of products to show in our app.
### Listing Products
The App will focus on Melee weapons.
In the main App component, create a [Static Array](/nodes/data/array/static-array) node. Make sure its set to `CSV` type. Edit the CSV data and paste in the data from below.
```
name,category,description,price,identifyer
Katana,steel,A classic japanese weapon used by the samurais,2000,katana
Broadsword,steel,A favorite among the medieval knights,1800,broadsword
Morning Star,steel,A scary and heavy weapon that require an expert to handle,1200,morning-star
Wooden Club,wood,A cheap and easy to use weapon that's popular among the farmers,50,wooden-club
Bokken,wood,A japanese wooden sword used in Kendo,850,bokken
Fake two handed sword,plastic,Looks real but so much lighter than the real thing,400,fake-sword
Mace for practice,plastic,A plastic mace you can use for training,250,plastic-mace
```
Feel free to add more data to the dataset if you want, as long as the category is one of "steel"/"wood"/"plastic". Also make sure the the "identifyer" value of any new row you add does not contain any whitespaces.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/encoding-parameters-in-urls/static-array.png)
</div>
Ok, now lets list the products under the Products page with their category, i.e. any "steel" products should be listed on the products/steel page.
First we put the Products in an Array that we can find anywhere in the app. Create an [Array](/nodes/data/array/array-node) node next to the **Static Array**. Give it the **Id** `Products`. Then connect the output **Items** on the **Static Array** to the input **Items** on the **Array**.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/encoding-parameters-in-urls/array-1.png)
</div>
Now we will go into the "Steel" Product Page and list the products of the "steel" category. We will use a [Repeater](/nodes/ui-controls/repeater) and create a list item.
In the steel page, add a **Repater** node as the second child of the group. Also add an **Array** node. Make sure its **Id** is `Products`, i.e. the same array that we filled with the product data. Then connect the **Items** output of the **Array** connects to the **Items** input of the **Repeater**.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/encoding-parameters-in-urls/products-page-1.png)
</div>
We will still not see anything because we have no list item that the **Repeater** can use to visualize the items. So lets create a new component, we call it "Product Item". In the new component we add a **Text** node as the only child in the **Group**. Now we need to retrieve the individual **Object** that contains the Product entry. Create an [Object](/nodes/data/object/object-node) node and make sure it gets its **Id** from the repeater node. Also add a property "name" to the **Object**.
<div className="ndl-image-with-background m">
![](/docs/guides/navigation/encoding-parameters-in-urls/id-repeater.png)
</div>
<div className="ndl-image-with-background m">
![](/docs/guides/navigation/encoding-parameters-in-urls/object-1.png)
</div>
Finally hook up the **name** output of the **Object** with the **text** property of the **Text** node. We will have something like image below.
<div className="ndl-image-with-background m">
![](/docs/guides/navigation/encoding-parameters-in-urls/list-item-1.png)
</div>
Now we are ready to go back to the "Steel" page and select our newly created List Item as the List Item of the repeater.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/encoding-parameters-in-urls/repeater-1.png)
</div>
If you navigate to the "Steel" page, you should now see a bunch of items popping up. It should look something like this:
<div className="ndl-image-with-background">
![](/docs/guides/navigation/encoding-parameters-in-urls/list-1.png)
</div>
We immediately see two things
- The list items need to be styled
- We see all products, not only the ones in the "steel" category
To fix the styling we go back into the "Product Item" component, make sure the **Group** node is **Content Height** (so the list gets a bit more compact). We also add a **Hover State** on the **Text** node. There is plenty of more styling that can be done, but lets settle for now.
Secondly, to filter out the products of the "steel" category, we go back to the "Steel Products" Page Component.
We add in a [Array Filter](/nodes/data/array/array-filter) node in between the **Array** and **Repater**.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/encoding-parameters-in-urls/products-page-2.png)
</div>
And finally we configure the **Array Filter** to only show items where `category = "steel"`.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/encoding-parameters-in-urls/array-filter-1.png)
</div>
<div className="ndl-image-with-background">
![](/docs/guides/navigation/encoding-parameters-in-urls/array-filter-2.png)
</div>
Now your Steel Page should look something like the one below.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/encoding-parameters-in-urls/list-2.png)
</div>
Quickly copy-and-paste the **Array**+**Array Filter**+**Repeater** construction to the "Wood" and "Plastic" page, make sure to update the filter, and we are done with listing the products.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/encoding-parameters-in-urls/sorting.gif)
</div>
### Adding the Show Product Page
Next step is to add a new **Page Component** to the main **Page Router**. We want to have it mapped to the URL `/showproduct`. Click the main **Page Router** (in the App component), the click **Add New Page** and **Create New Page**.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/encoding-parameters-in-urls/create-new-page-1.png)
</div>
<div className="ndl-image-with-background">
![](/docs/guides/navigation/encoding-parameters-in-urls/create-new-page-2.png)
</div>
Lets first go to the new page and make sure the path is "showproduct" by clicking the **Page** node and updating the path.
So how do we know which of the Melee Weapon Products to show here? Well, that's where the **Path Parameter** comes in.
### Using a Path Parameter
We want to send the `identifyer` value of the a clicked product as a **Path Parameter** and use that to look up the correct product. I.e. we want to Navigate to this page with the path `/showproduct/<identifyer>`.
We achieve that by clicking the **Page Inputs** node and add a **Path Parameter** called "productIdentifyer". The name doesnt matter except that becomes the name of the output of the **Page Inputs** node that will contain whatever is sent in the last part of the path.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/encoding-parameters-in-urls/path-param-1.png)
</div>
For test purposes, add a **Text** node to the **Page** node and connect the "productIdentifyer" output to the **Text** input of the **Text** node.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/encoding-parameters-in-urls/connect-text.png)
</div>
## Set the Path Parameter when Navigating
Ok, we are almost ready to try the "showproduct" Page. We just need to do the actual navigation.
So lets go back to the "Product Item" component we build earlier. We want to navigate when we click these items.
Add a **Navigate** node and make sure the associated **Page Router** is "Main". Then pick the "Show Product Page" as the target.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/encoding-parameters-in-urls/navigate-1.png)
</div>
As soon as you select it, you will see that there is a new input called "productIdentifyer". This is the one we added on the **Page Inputs** earlier!
We want to set it to the "identifyer" value from our product so lets add the property "identifyer" on our **Object** and connect it to the "productIdentifyer" input of our **Navigate** node. Also, connect the **Click** signal from the **Group** node to the **Navigate** signal on the **Navigate** node.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/encoding-parameters-in-urls/list-item-2.png)
</div>
Now try clicking on different products under the different Products pages. You should see the "Show Product Page" with the corresponding identifyer printed on the screen. Also try it in a regular browser to see how the URL looks.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/encoding-parameters-in-urls/browser-1.png)
</div>
### Prettifying the "Show Product Page"
To finish up the "Show Product Page" we will extract some real data from the product and present in a sligthly prettier way. Go to the "Show Product Page".
Add a new **Group** under the **Page** node and remove the **Text** node that was previously there. Make the **Group** white and give it some margin to give it some space. Add rounded corners and give it some padding for what's going inside it.
Then add three **Text** nodes, one for "name", one for "description" and one for "price". Make the first **Text** node bold. Also, add some nice margins in-between them.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/encoding-parameters-in-urls/show-products-page-1.png)
</div>
Now we need to connect the data. We have the "identifyer" coming in as our **Path Parameter**. We are going to use that to filter out the right **Object** from our "Products Array".
So we connect it to an **Array Filter**, which we filter on "identifyer". We should only get one item out, so we can take the **First Item Id** and connect it to the **Id** of an **Object** node.
The **Object** should have three properties, "name", "description" and "price". The "price" need to be formatted. So add a [String Format](/nodes/string-manipulation/string-format) node with the string `Price: {price} EUR`.
An input "price" should now be available on the **String Format** node. Connect the "price" output from the **Object** to it.
The data extraction should look similar to below:
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/encoding-parameters-in-urls/show-products-page-2.png)
</div>
Then connect the data to the **Text** nodes and we are done with this part.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/encoding-parameters-in-urls/show-products-page-3.png)
</div>
<div className="ndl-image-with-background m">
![](/docs/guides/navigation/encoding-parameters-in-urls/show-products-page-4.png)
</div>
## Using Query Parameters
Another way to send parameters to pages is as _Query Parameters_. While you can only have one **Path Parameter**, you can have as many **Query Parameters** as you want.
Lets add a **Query Parameter** to our **Page Inputs** node. We will call it `showVAT` and we will use it to determine whether the price should include VAT or not in the **Show Products Page**.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/encoding-parameters-in-urls/query-param-1.png)
</div>
Now lets make use of the parameter. We start by going to the "Settings" page component. We will let the user state its preference here, whether they want to include VAT or not in the displayed price.
### Adding a VAT setting
Add a [Radio Button Group](/nodes/ui-controls/radio-button-group) with a **Text** node and two [Radio Buttons](/nodes/ui-controls/radio-button) as children. The text will be the title for the group. Make sure the first **Radio Button** have the value `showVAT` and the second `dontShowVAT`.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/encoding-parameters-in-urls/settings-1.png)
</div>
<div className="ndl-image-with-background">
![](/docs/guides/navigation/encoding-parameters-in-urls/settings-2.png)
</div>
So lets save the VAT state, using a bit of logic and a [Variable](/nodes/data/variable/variable-node) / [Set Variable](/nodes/data/variable/set-variable) node. Note that we connect the **Variable** that holds the current value to the **Value** of the **Radio Group** node to make sure it reflects the current setting when navigating to the **Settings** Page. (Remember that the **Page Component** will be re-instanciated every time you navigate to it so we need to set the initial value every time.)
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/encoding-parameters-in-urls/settings-3.png)
</div>
Now we need to update our navigation to encode the setting in the URL. But maybe we should revisit why we would want to do that. Why not use our **Variable** `Show VAT` directly in the **Show Products** page component? Well, if we want our users to be able to copy the URL they are on when looking at a product, and we want the receiver to see the exact same view as the sender, we need to encode the setting in the URL.
### Sending the Query Parameter
Go to the "Product Item" component where we do the navigation to the "Show Product" Page. If you click the **Navigate** node you will now see that the `showVAT` parameter is availabel as an input.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/encoding-parameters-in-urls/navigate-2.png)
</div>
We simply connect our **Variable** to it and the value will be sent.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/encoding-parameters-in-urls/list-item-3.png)
</div>
### Receiving the Query Parameter
The only thing left is to receive the parameter in the "Show Product" Page and show different prices depending on the parameter. We add a **Variable** that will hold the string to be shown (the price string including the VAT or not). Then we add a [Condition](/nodes/utilities/logic/condition) node to set the correct string to the **Variable**. The nodes look as below:
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/encoding-parameters-in-urls/list-item-4.png)
</div>
If you want to download the complete project then press the "Import" button below and follow the instructions in Noodl.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/encoding-parameters-in-urls/encoding-params-final.gif)
<ImportButton
zip="/docs/guides/navigation/multi-level-navigation/param-encoding-url.zip"
name="Encoding Parameters in URL"
thumb="/docs/guides/navigation/multi-level-navigation/show-products-page-4.png"
/>
</div>

View File

@@ -0,0 +1,218 @@
---
title: Multi Level Navigation
hide_title: true
---
import ImportButton from '../../../src/components/importbutton'
# Multi Level Navigation
## What you will learn in this guide
In this guide we will look at using multiple [Page Routers](/nodes/navigation/page-router) to achieve navigation hierarchies with multiple levels. We will use the [Navigate](/nodes/navigation/navigate) and [Navigate To Path](/nodes/navigation/navigate-to-path) nodes to move between the different **Pages** of the App.
This is used when your **Pages** are routed to URLs where each subroute manages their own routes. For example you may have a section of your app, "Products", that you reach through the URL `https://mydomain.com/products`, which in turn has three subsections:
- `/products/steel`
- `/products/wood`
- `/products/plastics`
There's another section, "Settings", with the URL `https://mydomain.com/settings`. It has two subsections:
- `/settings/company`
- `/settings/user`
## Overview
We will go through the following steps:
1. Add and configure multiple layers of **Page Routers**.
2. Use the **Navigate** node to move between pages within each **Page Router**.
3. Use the **Navigate To Path** to navigate in multiple **Page Routers** in one go.
If you are new to Web Type Navigation you should go through the [Basic Navigation](/docs/guides/navigation/basic-navigation) guide first.
We will build upon the example built in that guide. If needed you can first import the base project by clicking the "import" button below.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/multi-level-navigation/prev-final.gif)
<ImportButton
zip="/docs/guides/navigation/multi-level-navigation/basic-navigation.zip"
name="Basic Navigation"
thumb="/docs/guides/navigation/multi-level-navigation/ui-2.png"
/>
</div>
## Multiple Page Routers
The first thing we want to add to our app is three categories under our "Product" page. We want three categories
- Steel Products - with the path `/products/steel`
- Wood Products - with the path `/products/wood`
- Plastic Products - with the path `/product/plastic`
We also want a Top Bar Menu, only available in the Products Page, to navigate between them.
How you acheive this in Noodl is to add a **Page Router** in the Products Page, and create three new pages for the sub pages. We add the Top Bar directly in the Products Page, so it will be consistent between the three subpages.
So lets start with creating the Top Bar. Go to the Product Page.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/multi-level-navigation/component-list-1.png)
</div>
<div className="ndl-image-with-background">
![](/docs/guides/navigation/multi-level-navigation/products-page-1.png)
</div>
More or less the same way as the sidebar menu was created, we can create the Top Bar. The main difference is that its laid out horizontally and that the buttons has a text instead of an icon. Make sure the Top Bar Group is set to **Content Height** so it doesnt take upp any unnecessary space.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/multi-level-navigation/products-page-2.png)
</div>
<div className="ndl-image-with-background s">
![](/docs/guides/navigation/multi-level-navigation/top-bar-panel.png)
</div>
Now let's add a **Page Router** under the Top Bar. It will take up the rest of the space of the **Page**. We change the name of the **Node** and the name of the **Page Router** (the **Name** Property) to "Products Router".
<div className="ndl-image-with-background">
![](/docs/guides/navigation/multi-level-navigation/page-router-1.png)
</div>
<div className="ndl-image-with-background">
![](/docs/guides/navigation/multi-level-navigation/products-page-3.png)
</div>
The next step is to add three new **Page Components** using the "+" icon on the Components List. To keep things tidy, we first create a new folder, "Products Pages", and add the **Page Components** under that folder, "Steel Page", "Wood Page", "Plastic Page"
As you can see you now have to select which **Page Router** you want to add the **Page Component** to. We choose our newly created "Products Router".
<div className="ndl-image-with-background">
![](/docs/guides/navigation/multi-level-navigation/add-page.png)
</div>
Once each page is created, add a **Group** node with some easy to recognize color and add a **Text** node as a title, centered.
A fast way to create the **Page Components** is to create one, and the use the "Duplicate" component feature.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/multi-level-navigation/duplicate-page.png)
</div>
After creating the pages, changing their color and title, you should now have something that looks like this.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/multi-level-navigation/pages-1.png)
</div>
You can now double check the **Page Router** in the "Products Page". It should have the three newly created pages as their available **Page Components**.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/multi-level-navigation/page-router-2.png)
</div>
We should also check the individual **Page** nodes so the "Url Path" is set correctly. Change it to "steel", "wood", "plastic".
<div className="ndl-image-with-background">
![](/docs/guides/navigation/multi-level-navigation/page-path-1.png)
</div>
With the right URL Paths set in place you should now be able to navigate directly to the inner pages of the "Products Page" by editing the URL in the navigation bar of the browser.
Open your favourite browser and try the three different URLs `http://localhost:8574/#/products/steel`, `http://localhost:8574/#/products/wood`, `http://localhost:8574/#/products/plastic`. They should tell the Main **Page Router** to go to the "Products Page" and then the Products Page **Page Router** to go to the specific pages, "Steel", "Wood" or "Plastic".
## Navigating Specific Routers
Now lets hook up the Top Bar. Create three **Navigate** nodes in the Products Page Component. Set each of them to target the **Products Router** **Page Router** and chose the respective target page.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/multi-level-navigation/topbar-navigate.png)
</div>
<div className="ndl-image-with-background">
![](/docs/guides/navigation/multi-level-navigation/products-router-navigate.png)
</div>
Try clicking the Top Bar. You should now be able between the **Page Components** in the inner **Page Router**.
## Using the Navigate To Path node
Finally, lets add a shortcut to the "Plastic" page to the sidebar. Clicking that button should do two things:
- Navigate to the "Products" **Page Component** in the Main **Page Router**.
- Navigate to the "Plastic" **Page Component** in the Products **Page Router**.
Doing this using the individual **Page Routers** would be a bit messy, so instead we use the **Navigate To Path** node. It will give the App a path to navigate to and let **Noodl** resolve which navigations that need to happen on which **Page Routers** based on the path.
First lets add the shortcut button. Go to the main App component and add a new **Button** to the sidebar. Call it "Shortcut: Plastic Products". Make it red so it stands out, align it to the bottom, and change the Text Style to a smaller font. Something like this:
<div className="ndl-image-with-background">
![](/docs/guides/navigation/multi-level-navigation/shortcut-1.png)
</div>
<div className="ndl-image-with-background">
![](/docs/guides/navigation/multi-level-navigation/main-app-1.png)
</div>
To finish things up, add a [Navigate To Path](/nodes/navigation/navigate-to-path) node, make sure its path is set to `products/plastic`.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/multi-level-navigation/navigate-to-path.png)
</div>
Finally connect the **Click** signal of the **Button** to **Navigate** on the **Navigate To Path** node and we are done.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/multi-level-navigation/main-app-2.png)
</div>
Try clicking the Shortcut Button and make sure it always navigates to the "Products" **Page Component** and the "Plastic" **Page Component** within it.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/multi-level-navigation/multi-level-final.gif)
<ImportButton
zip="/docs/guides/navigation/multi-level-navigation/multi-level-navigation.zip"
name="Multi Level Navigation"
thumb="/docs/guides/navigation/multi-level-navigation/multi-level-thumb.png"
/>
</div>

View File

@@ -0,0 +1,14 @@
---
title: Navigation in Noodl Overview
hide_title: true
---
import ReactPlayer from 'react-player'
# Navigation in Noodl
A central part of any App is a navigation system. Noodl has a super flexible navigation system where you can build both classic Web Style navigation, app style navigation and Popups.
<ReactPlayer playing autoplay muted loop url='overview/signup.mp4' />
### [Start learning about navigation](/docs/guides/navigation/basic-navigation)

View File

@@ -0,0 +1,375 @@
---
title: Popups
hide_title: true
---
import CopyToClipboardButton from '/src/components/copytoclipboardbutton'
import ImportButton from '/src/components/importbutton'
# Popups
## What you will learn in this guide
This guide will teach you how to use Popups in Noodl (represented by the [Show Popup](/nodes/popups/show-popup) and [Close Popup](/nodes/popups/show-popup) nodes). Popups are modal UI components that's put on top of the regular UI flow i.e. you have to close them to return to the main flow. They are typically used to present confirmation dialogies ("Save / Cancel?") or error messages. They can also be used to present inline UI flows, for example wizards and forms, that need to be completed as part of another flow.
## Overview
The guide will cover the following topics
* Simple styling of Popups
* Opening and closing Popups and sending the result of a popup
* Advanced styling of Popups
* Making a reusable, extendible Popup component
The example we are building in the guide will make use of lists so it's recommended to have done the [List Basics](/docs/guides/data/list-basics) guide before starting this guide.
## What's a Popup in Noodl?
Basically any component you create can be opened as a Popup in Noodl. A Popup is layered on top of your regular UI that originates from the root node of your App. Also, when the Popup is open, you cannot interact with any of the control in the regular tree.
Let's try it out.
## Some initial UI
We are going to create a small App to add Popups into. Start a new project in Noodl and select the "Hello World" template. Then remove the **Text** node. We want to create a list of people. Let's start with some static data. Add in a [Static Array](/nodes/data/array/static-array) node. Make sure it's set to `JSON` type and paste in the data below.
<div className="ndl-image-with-background xl">
![](/docs/guides/navigation/popups/list-1.png)
</div>
```json
[
{"firstname":"John", "lastname":"Barry", "age":24, "location":"Bristol, England"},
{"firstname":"Stefani", "lastname":"Worthington", "age":41, "location":"Kilkenny, Ireland"},
{"firstname":"Lisa", "lastname":"Hunter", "age":22, "location":"Wigan, England"},
{"firstname":"Ronan", "lastname":"Holden", "age":56, "location":"Aberdeen, Scotland"},
{"firstname":"Martin", "lastname":"McCarthy", "age":26, "location":"Glasgow, Scotland"},
{"firstname":"Rodney", "lastname":"O'Hara", "age":53, "location":"Dublin, Ireland"},
{"firstname":"Phil", "lastname":"Lineker", "age":21, "location":"Hartlepool, England"}
]
```
Then add in a [Repeater](/nodes/ui-controls/repeater) node under the root **Group** node. Also add an [Array](/nodes/data/array/array-node) node next to the **Static Array**. Give the **Array** the **Id** `People`. Finally connect the **Item** output of the **Static Array** to the same input on the **Array**. Then the **Items** output from the **Array** to the **Items** input of the **Repeater**.
Your main app should now look something like this:
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/popups/list-1.png)
</div>
We need to create a list item as well. Create a new visual component. Call it `List Item`.
Do a simple layout visualizing the data items in the **Static Array**. You can for example copy and paste the nodes below into the list item component.
<div class="ndl-image-with-background l">
![](/docs/guides/navigation/popups/list-item-1.png)
<CopyToClipboardButton json={{"nodes":[{"id":"563a9961-1e16-62e4-10e1-2a97fba28687","type":"Group","x":-251,"y":-58,"parameters":{"sizeMode":"explicit","height":{"value":40,"unit":"px"},"flexDirection":"row"},"ports":[],"children":[{"id":"1ce9f404-33ef-5efc-d514-9c7f7105c321","type":"Text","label":"First Name","x":-231,"y":44,"parameters":{"width":{"value":25,"unit":"%"},"alignY":"center"},"ports":[],"children":[]},{"id":"4f627f54-67be-9f43-de8f-239f7d2cc788","type":"Text","label":"Last Name","x":-231,"y":140,"parameters":{"width":{"value":25,"unit":"%"},"alignY":"center"},"ports":[],"children":[]},{"id":"8b2dd25c-0ff3-f3db-0c2f-8198d0feaece","type":"Text","label":"Age","x":-231,"y":236,"parameters":{"width":{"value":10,"unit":"%"},"alignY":"center"},"ports":[],"children":[]},{"id":"0b069cd5-68c3-1a18-5962-561316fa3390","type":"Text","label":"Location","x":-231,"y":332,"parameters":{"width":{"value":30,"unit":"%"},"alignY":"center"},"ports":[],"children":[]},{"id":"493b675a-33d2-85df-754f-5d540765aa44","type":"Group","x":-231,"y":428,"parameters":{"sizeMode":"explicit","width":{"value":10,"unit":"%"}},"ports":[],"children":[{"id":"8b7ae2ea-9f7e-f3c5-b364-8527bd0928d3","type":"net.noodl.controls.button","label":"Delete button","x":-211,"y":474,"parameters":{"useIcon":true,"sizeMode":"contentSize","alignX":"center","alignY":"center","useLabel":false,"iconIconSource":{"class":"material-icons","code":"delete"},"width":{"value":10,"unit":"px","isFixed":false},"height":{"value":20,"unit":"px","isFixed":false},"paddingLeft":{"value":4,"unit":"px"},"paddingRight":{"value":4,"unit":"px"},"paddingTop":{"value":4,"unit":"px"}},"ports":[],"children":[]}]}]},{"id":"54762794-37ba-54a4-22fc-f33b3b2e4e18","type":"Model2","x":172,"y":28,"parameters":{"idSource":"foreach","properties":"firstname,lastname,age,location"},"ports":[],"children":[]},{"id":"4311a736-5a74-4961-0910-e29325bc4421","type":"Component Outputs","x":318.62192606679525,"y":320.6984274555043,"parameters":{},"ports":[{"name":"Request Delete","plug":"input","type":{"name":"*"},"index":1}],"children":[]},{"id":"09a3e9af-aaf1-3e7d-e293-f9e544a89290","type":"Switch","x":101.58788062761681,"y":250.0541174251976,"parameters":{},"ports":[],"children":[]}],"connections":[{"fromId":"54762794-37ba-54a4-22fc-f33b3b2e4e18","fromProperty":"prop-firstname","toId":"1ce9f404-33ef-5efc-d514-9c7f7105c321","toProperty":"text"},{"fromId":"54762794-37ba-54a4-22fc-f33b3b2e4e18","fromProperty":"prop-lastname","toId":"4f627f54-67be-9f43-de8f-239f7d2cc788","toProperty":"text"},{"fromId":"54762794-37ba-54a4-22fc-f33b3b2e4e18","fromProperty":"prop-age","toId":"8b2dd25c-0ff3-f3db-0c2f-8198d0feaece","toProperty":"text"},{"fromId":"54762794-37ba-54a4-22fc-f33b3b2e4e18","fromProperty":"prop-location","toId":"0b069cd5-68c3-1a18-5962-561316fa3390","toProperty":"text"},{"fromId":"8b7ae2ea-9f7e-f3c5-b364-8527bd0928d3","fromProperty":"onClick","toId":"4311a736-5a74-4961-0910-e29325bc4421","toProperty":"Request Delete"},{"fromId":"563a9961-1e16-62e4-10e1-2a97fba28687","fromProperty":"hoverStart","toId":"09a3e9af-aaf1-3e7d-e293-f9e544a89290","toProperty":"on"},{"fromId":"563a9961-1e16-62e4-10e1-2a97fba28687","fromProperty":"hoverEnd","toId":"09a3e9af-aaf1-3e7d-e293-f9e544a89290","toProperty":"off"},{"fromId":"09a3e9af-aaf1-3e7d-e293-f9e544a89290","fromProperty":"state","toId":"8b7ae2ea-9f7e-f3c5-b364-8527bd0928d3","toProperty":"visible"}]}} />
</div>
Then set the newly created List Item component as the Template in the **Repeater** node.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/popups/repeater-1.png)
</div>
You will now have a list of people that looks something like this:
<div className="ndl-image-with-background">
![](/docs/guides/navigation/popups/app-1.png)
</div>
Now let's implement delete on this list. The delete button is already there. When the user clicks the delete button we want to trigger the [Remove Object From Array](/nodes/data/array/remove-from-array) node, with the **Id** of the `People` **Array** and the id of the **Object** to remove fed into it. There is a [specific guide on Arrays](/docs/guides/data/arrays) if you want to learn more about how to manipulate **Arrays**.
The remove functionality is implemented like below in the main App.
<div className="ndl-image-with-background xl">
![](/docs/guides/navigation/popups/list-2.png)
</div>
Try clicking the delete button on some items. If you refresh the viewer, they will re-appear, since they are stored in a **Static Array**.
## Implementing a Confirm Popup
A typical case to use a Popup is to confirm a destructive action such as a deletion. So let's create a Popup for that.
Create a new visual component. Call it "Confirm Delete Popup". Then, also in the main App, create an **Show Popup** node. Connect the `Request Delete` signal from the **Repeater** to the **Show** signal of the **Show Popup** node. Clicking the delete button will now open our Popup. However we need to tell the **Show Popup** node which component to show. Select the newly createad "Confirm Delete Popup" component.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/popups/popup-panel-1.png)
</div>
<div className="ndl-image-with-background xl">
![](/docs/guides/navigation/popups/list-3.png)
</div>
## Layout and visuals of a Popup
Go into it the Popup component. Popups are layed out in an individual layer on top of everything, so you can more or less see it as a new root node.
### The Popup Background
We start by modifying the root **Group** node. This is typically the background of the whole Popup layer, i.e. you probably would want to make this cover the whole screen and perhaps make semi transparent. We cannot use the **Opacity** to make it transparent, because that will be inherited by all the children of the **Group** as well. Instead we chose a semit transparent color. It will sort of fade out what's behind the popup.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/popups/popup-visuals-0.png)
</div>
### The Popup Dialogue
The next thing to build is the dialogue. We want it to be nice and rounded. Let's add another **Group** node. Give it a white color. Also make the corners rounded, with a grey color and a corner radius of 20 pixels.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/popups/popup-visuals-1.png)
</div>
The size of the popup is interesting. We want the size to be dependen on its content. At the same time we don't want it to be too small or too wide horizontally. So we set its size to `content size` and center it horizontally and vertically.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/popups/popup-visuals-2.png)
</div>
Then and then go ahead set
* **Min width = 200 px**
* **Max width = 400 px**
* **Min height = 100 px**
* **Max height = 100 px**
<div className="ndl-image-with-background">
![](/docs/guides/navigation/popups/popup-visuals-3.png)
</div>
Finally add some padding, 15 px in all directions.
The popup should now look something like this:
<div className="ndl-image-with-background xl">
![](/docs/guides/navigation/popups/popup-visuals-4.png)
</div>
### Adding some content
So let's add some content. A **Text** node which is a title, (i.e. give it a larger font size, perhaps 30 px and with centered text). Then another smaller **Text** with the actual deletion message.
Finally add a **Group** that will contain the two buttons, layed out horizontally.
Add the two buttons (one green and one red). Align them to the left and right. Finally add some margins to make things look less constricted.
You popup will now look something like below. You can also copy and paste the nodes if you want this exact look.
<div class="ndl-image-with-background xl">
![](/docs/guides/navigation/popups/popup-visuals-5.png)
<CopyToClipboardButton json={{"nodes":[{"id":"74711a71-f264-654c-9855-2aa66a25f0e0","type":"Group","x":-336,"y":-142,"parameters":{"backgroundColor":"#FFFFFFB2"},"ports":[],"children":[{"id":"14a19f8f-22f0-5196-9813-c34b47a000d8","type":"Group","label":"Group","x":-316,"y":-96,"parameters":{"sizeMode":"contentSize","alignX":"center","alignY":"center","paddingTop":{"value":15,"unit":"px"},"paddingBottom":{"value":15,"unit":"px"},"paddingRight":{"value":15,"unit":"px"},"paddingLeft":{"value":15,"unit":"px"},"borderRadius":{"value":20,"unit":"px"},"borderStyle":"solid","backgroundColor":"#FFFFFF","borderColor":"#484545","minWidth":{"value":200,"unit":"px"},"maxWidth":{"value":400,"unit":"px"},"minHeight":{"value":100,"unit":"px"},"maxHeight":{"value":400,"unit":"px"}},"ports":[],"children":[{"id":"75b58ecf-48ab-dbad-e4ca-0f6a7e077f23","type":"Text","label":"Title","x":-296,"y":-50,"parameters":{"text":"Confirm Deletion","fontSize":{"value":30,"unit":"px"},"textAlignX":"center","marginBottom":{"value":10,"unit":"px"}},"ports":[],"children":[]},{"id":"3f2cd4e2-40c4-aa93-9296-13a66b3eff4e","type":"Text","label":"Message","x":-296,"y":10,"parameters":{"text":"Are you sure you want to delete this item?","marginBottom":{"value":20,"unit":"px"}},"ports":[],"children":[]},{"id":"78b2871e-ac6c-8ab3-9aab-2c844948bfa9","type":"Group","label":"Button Group","x":-296,"y":70,"parameters":{"flexDirection":"row"},"ports":[],"children":[{"id":"7ff68987-b14b-a7cd-41e9-22f3dc419a72","type":"net.noodl.controls.button","label":"Confirm","x":-276,"y":130,"parameters":{"label":"Confirm","backgroundColor":"#269B2B"},"ports":[],"children":[]},{"id":"35c641a6-bd3a-9801-20a3-b88b38e4f1f9","type":"net.noodl.controls.button","label":"Cancel Button","x":-276,"y":190,"parameters":{"label":"Cancel","alignX":"right","backgroundColor":"#F61934"},"ports":[],"children":[]}]}]}]}],"connections":[]}} />
</div>
## Passing data and signals to and from Popups
Great, we have our first Popup. Now we need to do something with it. We need to know whether the user clicked "Confirm" or "Cancel" and of course close our popup. You can do this using the **Close Popup** node. Add one to your Popup Component. Click on it to see its properties.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/popups/close-popup-1.png)
</div>
As you can see it has two sections: **Results** and **Close Actions**.
In the **Results** section you can add any number of inputs to pass data from the **Popup** back to wherever it was opened. And **Close Actions** is used to pass actions (i.e. signals). Whatever you add here, will be added as output signals to the **Show Popup** node that opened the **Popup**. We need to be know whether the user clicked **Confirm** or **Cancel**. The easiest way to pass this information is as signals. Create two **Close Actions** one called `User Confirmed` and one called `User Cancelled`. Connect the two signals to the respective button.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/popups/popup-visuals-6.png)
</div>
Then go back to the main App to use the signals to handle the delete. We simply hook up the **User Confirmed** signal to the **Do** signal of the **Remove Object From Array** node. Easy!
<div className="ndl-image-with-background xl">
![](/docs/guides/navigation/popups/list-4.png)
</div>
Nice. Let's also pass some data to the **Popup**, the name of the item we want to delete.
First we need to assign the **Id** we get from the **Repeater** to an **Object**. Create and **Object** node and add two properties, `firstname` and `lastname`. Connect the **Item Id** from the **Repeater** to the **Id** of the **Object**.
<div className="ndl-image-with-background xl">
![](/docs/guides/navigation/popups/list-5.png)
</div>
Then we need to go into our confirmation Popup and add in a **Component Inputs**. Add two properties, **firstname** and **lastname**. We want to incorporate the first and last name into our message. So also add a [String Format](/nodes/string-manipulation/string-format) node. Enter the following text in the node:
```
Are you sure you want to delete the item "{firstname} {lastname}"?
```
`firstname` and `lastname` will now become inputs on the **String Format** node and you can connect the outputs `firstname` and `lastname` from the **Component Inputs** node. Finally connect the **Formatted** output from the **String Format** node to the text of the "Message" **Text** node.
<div className="ndl-image-with-background xl">
![](/docs/guides/navigation/popups/popup-visuals-7.png)
</div>
The last step is to connect the `firstname` and `lastname` of the **Object** to our two new inputs on the **Show Popup** node, coming from the **Component Inputs** we just added.
<div className="ndl-image-with-background xl">
![](/docs/guides/navigation/popups/list-6.png)
</div>
## Advanced Styling
Let's make our Popup a little more dynamic by adding some animations to it. We can use the **Did Mount** signal from our root **Group** in the Popup component to trigger a transition when the Popup is opened. We can also use some fancy CSS for our backdrop. Let's start with the latter.
Click on the root node of the Popup Component. Find the attribute **CSS Style**. Set it to `backdrop-filter: blur(5px);`
<div className="ndl-image-with-background">
![](/docs/guides/navigation/popups/css-style.png)
</div>
This adds an extra blurring effect to the background. Note that this CSS-effect is not supported by old browsers.
Then we want to animate the size of the Popup when the it appears and disappears. Start by adding a [States](/nodes/utilities/logic/states) node. Give it two states: `Gone` and `Here`. Create a value called `scale`. Set it to be 0 in the `Gone` state and 1 in the `Here` state. Make sure the initial state is `Gone`.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/popups/animation-1.png)
</div>
Also set the length of each transition to 100 ms - this transition need to go fairly fast to not be annoying.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/popups/animation-2.png)
</div>
Then make the **States** node go to the `Here` state when **Did Mount** trigger. Connect the `scale` output to the **Scale** value of the **Popup** group.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/popups/animation-3.png)
</div>
Try it!
To reverse the transition when closing the Popup is a little more complicated because we need to wait for the transition to end before we send our **Close Popup** signals. So we need to add some logic.
First connect **Click** from both buttons to **To Gone** on our **States** node. That will trigger the transition. Then, we need to store which **Button** that was clicked wo we can send the correct signal to **Close Popup**. We do that using a [Switch](/nodes/logic/switch) that's set to either On or Off (true or false) depending on which button that was pressed.
When the animation is over (the **At Gone** signal will be triggered) we evaluate a [Condition Node](/nodes/utilities/logic/condition) to see which close signal we want to trigger.
<div className="ndl-image-with-background xl">
![](/docs/guides/navigation/popups/animation-4.png)
</div>
## Building a Wizard-type Popup
As you probably know an App typically contain many types of **Popups**, for example Error Messages, confirmation dialogues and maybe multistep wizards. For that reason we want to refactor our Popup and make parts of it reusable and to make all **Popups** consistent in how they look and behave.
## Making a Re-usable Popup Component
First we want to figure out what parts that all **Popups** should have. In this case we want the generic parts to be
* The background and layout of the **Popup** but not the content inside it (e.g. the text message and the **Buttons**)
* The title text
* The animation behavior
Let's start by duplicating our `Confirm Delete Popup` component.
<div className="ndl-image-with-background">
![](/docs/guides/navigation/popups/duplicate-1.png)
</div>
Rename it to `Popup Base`. The idea is that all popups should be based on this component going forward.
We want to remove everything that is specific to the previous delete confirmation popup.
Remove the **String Format** node and the properties from the **Component Inputs** (`firstname`, `lastname`). Instead add a new property called `Title`. Connect it to the **Text** property of the Title **Text** node.
We also need to remove the message, the **Buttons** and the logic connected to the **Close Popup**. Instead we are going to use a [Component Children](/nodes/component-utilities/component-children) node, that will give our component the ability to have nodes as children. The children will be inserted exactly where the **Component Children** resides in the tree.
Left to do is to handle the transition when the **Popup** is shown and closed. The showing part can be handled completely in the `Popup Base`, but the closing transition is different, since the **Close Popup** logic will be different for different Popups. The solution is to let the `Popup Base` component handle the close transition and tell when the transition is done. The user of the component also need to be able to tell the `Popup Base` when the transition should start.
So add a [Component Outputs](/nodes/component-utilities/component-outputs) node and add a property `Close Transition Done` to it. Then connect the **Has Reached Gone** signal from the **States** node to it. Add another property on the **Component Input** called **Start Close Transition** and connect it to the `To Gone` input of the **States** node.
All in all the popup base will now look like this:
<div className="ndl-image-with-background xl">
![](/docs/guides/navigation/popups/popup-base-1.png)
</div>
### Using the Popup Base in the Confirm Delete Popup
Ok, so let's refactor our `Confirm Delete` popup to use our `Popup Base` component. Open it.
The first thing we will do is of course to add in a `Popup Base` node. This will be our new root once we moved all specific nodes to it.
Start by moving the Message **Text** node and the Button Group **Group** node to become children of our `Popup Base` node. Remember, they will be inserted into the tree according to the position of our **Component Children** node.
<div className="ndl-image-with-background xl">
![](/docs/guides/navigation/popups/popup-visuals-8.png)
</div>
It's a bit of a mess now, because we have two root nodes. We will fix that soon.
Also make sure the **Title** input of our `Popup Base` node is correct by clicking on the node. It should be ok, because the default text came from this component when we did the refactoring.
Actually the only thing we need to hook up now is the logic for the closing of the **Popup**. Remove the **States** node, that one is part of the `Popup Base` component now. Reconnect the **Click** outputs of the buttons to the `Start Close Transition` signal on the `Popup Base`. Then connect the outgoing signal **Close Transition Done** to the **Evaluate** input of the **Condition** node. We can now remove the old node tree and only have the one based on the `Popup Base` node.
<div className="ndl-image-with-background xl">
![](/docs/guides/navigation/popups/popup-visuals-9.png)
</div>
Refactoring done! Look, much cleaner looking tree. Make sure the popup still looks and works as before (it should).
You can import the complete project by clicking the Import button below.
<div className="ndl-image-with-background l">
![](/docs/guides/navigation/popups/popup-final.png)
<ImportButton zip="/docs/guides/navigation/popups/popups.zip" name="Popup Example" thumb="/docs/guides/navigation/popups/popup-final.png"/>
</div>
If you want to see how to use **Popups** together with **Component Stack** you can continue building on this example in the [Component Stack Guide](/docs/guides/navigation/component-stack).