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

691
docs/guides/data/arrays.mdx Normal file
View File

@@ -0,0 +1,691 @@
---
title: Arrays
hide_title: true
---
import ReactPlayer from 'react-player'
import ImportButton from '/src/components/importbutton'
# Arrays
## What you will learn in this guide
In this guide we will look more closely on how to use **Arrays** in Noodl. **Arrays** are fundamental for lists in Noodl and useful for many other things as well.
Noodl have a number of nodes related to Arrays. For accessing and modifying there are:
- [Array](/nodes/data/array/array-node)
- [Create New Array](/nodes/data/array/create-new-array)
- [Insert Object Into Array](/nodes/data/array/insert-into-array)
- [Remove Object From Array](/nodes/data/array/remove-from-array)
- [Clear Array](/nodes/data/array/clear-array)
For mapping and filtering an **Array** the [Array Filter](/nodes/data/array/array-filter) and [Array Map](/nodes/data/array/array-map).
You will also learn to use the [Static Array](/nodes/data/array/static-array) node which is very useful for handling static list data in your App.
## Overview
This guide will walk through the following topics
- What's a Noodl **Array**
- Creating **Arrays**, statically and dynamically
- Adding and Removing objects from an **Array**
- Using **Static Arrays**
- Using Arrays in Lists and Dropdowns
- Filtering Arrays
Arrays are used all the time in Noodl. Hence they occur in many guides. It's suggested to check out the [List Basics](/docs/guides/data/list-basics) guide to learn more about how to display **Arrays** as lists. Also when querying data from a **Backend Database** the data is returned in an **Array** hence it could be interesting to check out some fo the [Cloud Data](/docs/guides/cloud-data/overview) guides in conjunction with this guide.
Before reading this guide, it's a good idea to have a basic understanding of what an [Object](/nodes/data/object/object-node) is, which is described in [this](/docs/guides/data/objects) guide.
## What's a Noodl Array
A Noodl **Array** is an ordered list of items, **Objects** or (if the array is the result of a database query) [Records](/nodes/data/cloud-data/record). An **Array** i Noodl is represented with an **Array** node.
:::note
Note that a Noodl **Array** is not the same as a JavaScript array. They are easy to convert between but you cannot access a Noodl **Array** using JavaScript Array functions. Instead there is a Noodl Array API that will be covered later in the guide.
:::
### The Id of an **Array**
As with the [Variable](/nodes/data/variable/variable-node) and **Object**, an **Array** is uniquly identified by its **Id**. Any node referencing an **Array**, through an **Array** node or **Insert Object Into Array** etc, do it through an **Id**.
Therefore it's quite common to connect the **Id** from one node, e.g. an **Array** node, to the **Id** of another node, for example an **Insert Object Into Array** to make sure they are operating on the same **Array**. You can also just write the **Id** into the nodes if the **Id** is static.
Below are three typical cases of how to use the **Id** of an **Array** and make sure a **Clear Array** node is acting on the same **Array**.
<div className="ndl-image-with-background xl">
![](/docs/guides/data/arrays/id-1.png)
</div>
In the first case the **Id** of an **Array** is stored in a **String** node (or for example a **Variable**) which is then used to refer to the **Array** in different node.
In the second case the **Id** is set directly in the **Array** node, and then that **Id** is used to point the **Clear Array** to the correct **Array**. The **Id** is set in the property panel.
<div className="ndl-image-with-background">
![](/docs/guides/data/arrays/id-2.png)
</div>
In the last case the **Id** is generated from a **Create New Array** below (more on that later) and then used to make sure the **Clear Array** node operates on the correct node.
### The Items of an **Array**
The **Items** of an **Array** are either **Objects** or **Records**. An item can be a member of multiple **Arrays**. This means that two different **Arrays** (i.e. with different **Ids**) can have exactly the same items, but they are still different **Arrays**. An item can only occur once in an **Array**.
When you hover over an **Array** node in the node canvas you will see how many items the **Array** currently has.
<div className="ndl-image-with-background s">
![](/docs/guides/data/arrays/items-1.png)
</div>
You can easily assign items to an **Array** by setting the **Items** input of an **Array** node. This is often done used for example when you do a query to the database through a [Query Records](/nodes/data/cloud-data/query-records), or handling the result of a [Array Filter](/nodes/data/array/array-filter) or [Static Array](/nodes/data/array/static-array). See three examples below.
<div className="ndl-image-with-background l">
![](/docs/guides/data/arrays/items-2.png)
</div>
Most often you assign items to an **Array** because you want to process them (perhaps add or remove some more items) or keep track of them to be used later.
## Creating Arrays
As you saw in the example above there are two main ways of creating an **Array**.
1. **Static creation** - You simply add an **Array** node in you node graph. You can give the **Array** an **Id** (as discussed above) or simply leave it blank, which means an **Id** will be assigned automaticallt to the Array.
2. **Dynamic creation** - You create a new **Array** on a certain event, for example using a **Did Mount** signal or a **Click** signal.
:::note
If you give your **Array** a static **Id** and if you have multiple instances of the component with the **Array** node (for example a couple of list items), the same **Array** will be referenced in all those components. This may or may not be what you want, so keep that in mind.
:::
## Creating a Recipe App
Now let's start playing around with **Arrays**. Let's create a small recipe app. The overall idea would be to have a list of Recipes (stored in an **Array**) and each recipe have a bunch of ingredients, also stored in an **Array**. So in the end we'll have an **Array** with **Arrays**.
Let's start with some sample data! Create a new project, using the "Hello World"-template. Then create a **Static Array** node. Make sure it's set to `JSON` format.
<div className="ndl-image-with-background s">
![](/docs/guides/data/arrays/static-array-1.png)
</div>
Then copy and paste the following into the JSON data.
```json
[
{
"name": "Swedish Pancakes",
"description": "A simple tasty recipe of classic Swedish pancakes (8-10 pcs)",
"containsMeat": false,
"ingredients": [
{ "amount": "150 g", "name": "Wheat flower" },
{ "amount": "1/2 tsp", "name": "Salt" },
{ "amount": "3", "name": "Eggs" },
{ "amount": "600 g", "name": "Milk" },
{ "amount": "1 tbsp", "name": "Butter" }
],
"instructions": "Mix flower, salt, milk and eggs well. Fry thin pancakes on medium heat in butter, 3-4 minutes on each side"
},
{
"name": "Swedish Meatballs",
"description": "A swedish classic. Eat with potatoes, gravy and lingonberries.",
"containsMeat": true,
"ingredients": [
{ "amount": "500 g", "name": "Minced meat" },
{ "amount": "1", "name": "Egg" },
{ "amount": "1 clove", "name": "Garlic" },
{ "amount": "3 tbsp", "name": "Bread crumbs" },
{ "amount": "1/2", "name": "Yellow Onion" },
{ "amount": "2 tsp", "name": "Concentrated beef stock" },
{ "amount": "2 tbsp", "name": "Full cream" },
{ "amount": "1 tsb", "name": "Salt" },
{ "amount": "1/2 tbsp", "name": "Ground pepper" },
{ "amount": "2 tbsp", "name": "Butter" }
],
"instructions": "Chop the onion finely. Put in a bucket. Press the garlic into the bucket. Add egg, bread crumbs, cream, beef stock, salt and pepper and mix. Let it rest for 10 mins. Mix in the minced meat. Make small balls and fry (8-10 at a time) in butter until golden brown and cooked through."
},
{
"name": "Kalops",
"description": "This Swedish stew should be eaten with potatoes and pickled red beets.",
"containsMeat": true,
"ingredients": [
{ "amount": "1 kg", "name": "Boneless Beef" },
{ "amount": "2 tbsp", "name": "Butter" },
{ "amount": "2", "name": "Yellow onions" },
{ "amount": "4", "name": "Carrots" },
{ "amount": "3", "name": "Bay leafs" },
{ "amount": "30", "name": "Allspice grains" },
{ "amount": "1 litre", "name": "Water" },
{ "amount": "2 tbsp", "name": "Concentrated beef stock" },
{ "amount": "1 1/2 tbsp", "name": "Corn starch" },
{ "amount": "1 tsb", "name": "Salt" },
{ "amount": "1/2 tbsp", "name": "Ground pepper" }
],
"instructions": "Cut the meat into 3x3 cm pieces. Peel and cut onions and carrots into 3cm pieces. Fry the meat in a pan with butter until it has a nice surface. Add onion, carrots, bay leafs and allspice. Pour over the water and beef stock. Bring to boil and leave until the meat is tender (2-3 hours depending on meet). Thicken the stew with the corn starch. Add salt and pepper."
}
]
```
Now we have some data to play with. Let's start by taking a look at it. We want to look at both the outer list (the recipes) and one of the inner lists (a specific) recipe, just to get a feel for it. Connect the **Items** output of the **Static Array** to an **Array Filter** node. Then take the **First Item Id** of the **Array Filter** and connect it to an **Object** node. Add a property to the **Object** node called `ingredients` and connect that one to an **Array** node. Also open up the debug popup on the connections to see what's going on.
<div className="ndl-image-with-background xl">
![](/docs/guides/data/arrays/debug-1.png)
</div>
<div className="ndl-image-with-background xl">
![](/docs/guides/data/arrays/debug-2.png)
</div>
## Filtering out the first item in an **Array**
To explain in more on what's going on. We take the items from the **Static Array** and feed them into a **Filter Array** node. The filter actually doesn't have any filters enabled but we can make use of its **First Item Id** output which holds the Id of our first **Object** in the **Array** (the Pancake recipe). In that Object we look at the **ingredients** property which contains an **Array** of items. These items are set as **Items** for another **Array** which now will contain the ingredients of the Pancake recipe.
Phew! The **Array Filter** / **First Item Id** output is very useful if you want to get a sample of what's in a list.
Let's create some simple UI around our recipes. To make the app a little easier to interact with, we need a simple navigation system. You can learn more about how to do navigation in [this](/docs/guides/navigation/basic-navigation) guide. In this guide we will not go into details.
## Making a list to show the **Array**
Start by adding a **Page Router** in the root component. Also get rid of the **Text** node.
<div className="ndl-image-with-background m">
![](/docs/guides/data/arrays/page-router.png)
</div>
Click on the **Page Router** and add a new page called `Recipe List`.
<div className="ndl-image-with-background m">
![](/docs/guides/data/arrays/create-page-1.png)
</div>
You will now have a new **Page** component in your component tree.
<div className="ndl-image-with-background m">
![](/docs/guides/data/arrays/recipe-list.png)
</div>
Create yet another component. This time a visual component called `Recipe List Item`. This will be a list item for a list we soon are going to build.
<div className="ndl-image-with-background m">
![](/docs/guides/data/arrays/vis-comp.png)
![](/docs/guides/data/arrays/recipe-list-item.png)
</div>
In that component, set the size of the list item to be **width** 100% and **height** 30 px. Also add a [Text](/nodes/basic-elements/text) node to the root **Group**. Center the text vertically and left align it.
<div className="ndl-image-with-background m">
![](/docs/guides/data/arrays/recipe-list-item-2.png)
</div>
Now go to the newly created `Recipe List` page. Add a text node that will be the title. Maybe increase the font size to 24 px. Give the Page some white space by adding 10 px padding top and left direction.
<div className="ndl-image-with-background m">
![](/docs/guides/data/arrays/recipe-list-page-1.png)
</div>
Then add a [Repeater](/nodes/ui-controls/repeater) node under the title **Text** node. Make sure the newly created `Recipe List Item` is used as a template.
<div className="ndl-image-with-background m">
![](/docs/guides/data/arrays/repeater-item.png)
</div>
Now move (for example using cut/paste) the **Static Array** node from the main `App` to this page and connect the **Items** output to the **Repeater**.
<div className="ndl-image-with-background l">
![](/docs/guides/data/arrays/recipe-list-2.png)
</div>
The final step to get the list working is to connect the correct data point in the **Object** to the **Text** in the list item. Go to `Recipe List Item` and add a new **Object** node. Make sure its **Id** comes from the repeater.
<div className="ndl-image-with-background s">
![](/docs/guides/data/arrays/id-from-repeater.png)
</div>
Then add a property on the **Object** called `name` and connect it to the **Text** node.
<div className="ndl-image-with-background l">
![](/docs/guides/data/arrays/recipe-list-3.png)
</div>
This simple list will do for now. If you want to learn more about how to make lists it's recommended to go through the list guides, starting with [List Basics](/docs/guides/data/list-basics).
## Removing an Object from an **Array**
So let's add some functionality to the list. Let's make sure we can remove some recipes. We begin by adding an button for it on our list item. Go into the `Recipe List Item` component.
Change the layout of the root **Group** to `horizontal`. Then add a new **Group** as a sibling to the **Text** node and in that **Group** node an [Icon](/nodes/basic-elements/icon) node. Find a good icon for delete and also make sure it is black, so you can see it properly. Also, change the sizing of the newly added **Group** node to `Content Width and Height`. Tweak the size, margins and padding so it looks ok.
<div className="ndl-image-with-background l">
![](/docs/guides/data/arrays/recipe-list-item-3.png)
</div>
We only want the delete button to show up when you hover the list item. We achieve this by adding a [Switch](/nodes/logic/switch) and hooking it up to **Hover Start** and **Hover End** and feeding its state to the **Mounted** input of the inner **Group** see below.
<div className="ndl-image-with-background l">
![](/docs/guides/data/arrays/recipe-list-item-4.png)
</div>
Finally we want to send a signal out of the list when the user clicks the delete icon. So add a [Component Output](/nodes/component-utilities/component-outputs) node. Make and input called `Request Delete` and connect the **Click** signal from the inner **Group** to it.
<div className="ndl-image-with-background l">
![](/docs/guides/data/arrays/recipe-list-item-5.png)
</div>
We are now ready to go back to the `Recipe List` page component and deal with the actual deletion. In that component we first need to add an Array that will hold the recipe items. Before we fed the **Items** directly from the **Static Array**. Now, since we need to modify the contents, we need to first feed the **Items** into an **Array**. Give the new **Array** the name `Recipe List`. The node construct should now look like below.
<div className="ndl-image-with-background l">
![](/docs/guides/data/arrays/recipe-list-4.png)
</div>
Now we can do the removal. Create a **Remove Object from Array** node. This node takes two inputs. The **Array Id** and the **Object to Remove Id**. The **Array Id** is the same as the **Array** we just created (`Recipe List`) so let's just connect the two Ids. The **Object to Remove Id** comes from the repeater. The **Id** of the object that was clicked will forwarded by the **Repeater**. Also, our output from the `Recipe List Item` component will come out of the repeater as a signal and we can use that as the signal for triggering the delete by connecting it to the **Do** signal of the **Remove Object From Array** node.
<div className="ndl-image-with-background l">
![](/docs/guides/data/arrays/recipe-list-5.png)
</div>
Try it out! Don't be afraid to delete recipies. If you refresh your viewer they will all be back since they are not coming from a database but from a **Static Array**.
## Using **Count**
If you remove all recipes, it might be a good idea to show a text, `The recipe list is empty`. Lets build that! Start by adding a **Text** node to the `Recipe List` page. Center the text (using the `Text Alignment` property).
We only want to show this text when the **Array** is empty. So add an [Expression](/nodes/math/expression) node. Type the following into the expression node
```javascript
count === 0
```
This will make the expression return `true` if the input `count` equals 0, otherwise `false`. Connect the output **Result** of the expression to the **Mounted** input of the new **Text** node. Then finally connect the **Count** output from the **Array** to the `count` input of the expression.
<div className="ndl-image-with-background l">
![](/docs/guides/data/arrays/recipe-list-6.png)
</div>
Try removing all recipes and make sure the text is visible once **Count** equals 0.
## Showing the recipe
Ok, now it's time to show the recipes once you click on them. So let's start by adding another page. As before, click the **Page Router** and then click **Add Page**. Add a page name `Recipe`.
In the new **Page** start by adding a title text. We can copy the **Text** node that was used as a title in the `Recipe List`. Below that we want another **Text** node which will hold the description. Let's add that as well.
<div className="ndl-image-with-background m">
![](/docs/guides/data/arrays/recipe-1.png)
</div>
### Passing on the Recipe to the **Page**
We now need to pass on the **Id** of the **Recipe** we clicked to the new **Page** so we can show it. Step 1 is to catch when we click the recipe. Remember, we only catch the case when we click the delete button so far. So go back to the `Recipe List Item`. Add another output on the **Component Outputs** node called `Request View`. Then connect the **Click** output on the root **Text** node to it. This will forward the signal to the component containing the list.
<div className="ndl-image-with-background l">
![](/docs/guides/data/arrays/recipe-list-item-6.png)
</div>
Now go back to the `Recipe` Page. We want to add a new **Path Parameter** in the **Page Inputs** component. Click the **Page Inputs** node and then add a new **Path Parameter** called `RecipeId`. This will mean that we can send the **Id** of the clicked recipe is part of the url path that we navigate to.
<div className="ndl-image-with-background">
![](/docs/guides/data/arrays/page-param-1.png)
</div>
Assuming that the **Id** is this **Path Parameter**, we can connect the **Object** to it and get the name, description and other properties from the **Object**. Let's set that up. We are still in the `Recipe` page.
<div className="ndl-image-with-background">
![](/docs/guides/data/arrays/recipe-2.png)
</div>
Now we are ready to implemnt the navigation. In the `Recipe List` page we add a [Navigate](/nodes/navigation/navigate) node. Select the `Recipe` page to navigate to. This will add an input to the **Navigate** node which is our **Path Parameter** `RecipeId`. Connect the **Item Id** output of the **Repeater** to it and finally the **Request View** signal to the **Do** signal of the **Navigate** node. Voila!
<div className="ndl-image-with-background l">
![](/docs/guides/data/arrays/recipe-list-7.png)
</div>
Note that there is no Back-button in the Noodl viewer so you either have to use your browser if you want to go back and forth, or simply refresh to get back to the list. That's a little bit annoying but we'll fix it later. We want to show our ingredients first.
## Showing the recipe ingredients
So let's build the list that shows our ingredients. Create a new list item, a visual component named `Ingredient Item`. We can actually duplicate our previous list item and modify it.
<div className="ndl-image-with-background m">
![](/docs/guides/data/arrays/duplicate.png)
</div>
Start with removing the `Request View` input in the **Component Output** since we are not going to view individual ingredient. Also remove the delete action and the hover state. Looking at the data, the ingredient have two properties. `name`, which we already have connected. The other one is `amount`. Let's add a text node for that and connect it.
<div className="ndl-image-with-background m">
![](/docs/guides/data/arrays/ingredient-list-item-1.png)
</div>
Now we can add the **Repeater** node that will display the ingredients. Go to the `Recipe` Page. Add a **Repeater** node and select the newly created `Ingredient List Item` as the component template. Now we need to find the list of ingredients. Looking at the data model we see that the property is called `ingredients`. Add that property to the **Object**. Then add an **Array** node. That's the **Array** that will hold the ingredients. Connect the `ingredients` property of the **Object** to the **Array**. Then connect the **Items** output of the **Array** to the **Repeater**. You now have a list of ingredients!
<div className="ndl-image-with-background xl">
![](/docs/guides/data/arrays/recipe-3.png)
</div>
We might as well add the instructions at the end to make the view complete. Add another **Text** node at the bottom. Call it `Instructions`. Then add the property `instructions` on the **Object** and connect it to the **Text** node. Maybe add some margin to seperate it a bit from the list.
<div className="ndl-image-with-background xl">
![](/docs/guides/data/arrays/recipe-4.png)
</div>
Finally, let's add that back button. At the bottom, add a **Button** that triggers a navigation to the `Recipe List` **Page**. Connect the button to a **Navigate** node that takes the user back to `Recipe List` **Page**.
<div className="ndl-image-with-background xl">
![](/docs/guides/data/arrays/recipe-5.png)
</div>
## Feeding a Dropdown with an **Array**
We are making progress with our recipe App. Next step is to add a simple filtering functionality to filter out vegetarian / non vegetarian options. Remember, in our data model we have the property `containsMeat` that is either `true` or `false`.
We go back our our `Recipe List` page. In here we want to add a [Dropdown](/nodes/ui-controls/dropdown) with three options `all`,`meat` or `vegetarian`.
If you read up on the **Dropdown** node, you can see that to figure out its options it takes items from an **Array** as an input. The **Array** should contain items of the following type:
```json
{
"Label": "a label",
"Value": "a value"
}
```
The `Label` will be shown as a text in the **Dropdown** and the `Value` is the value that the **Dropdown** will have on its output if the associated item is selected.
We can easily feed the **Dropdown** using a **Static Array**.
Start by adding a **Static Array** in the `Recipe List` page and make sure it's set to JSON format. Name it `Dropdown Options` for clarity and add in the following data
```json
[
{ "Label": "Show all recepies", "Value": "all" },
{ "Label": "Show recepies with meat", "Value": "meat" },
{ "Label": "Show vegetarian recepies", "Value": "veg" }
]
```
<div className="ndl-image-with-background xl">
![](/docs/guides/data/arrays/dropdown-options.png)
</div>
Then add in a **Dropdown** node, just after the title. Enable the `label` on it and give it the label `Filter`. Also give it a bottom margin to seperate it a little from the list items. Then connect the **Items** from the **Static Array** to the Dropdown and you should see the options in the **Dropdown**.
<div className="ndl-image-with-background xl">
![](/docs/guides/data/arrays/recipe-list-8.png)
</div>
## Filtering an **Array**
We can now proceed with the filtering of the recipe. In our case we have three filtering states:
- No filtering, i.e. we show all recipes
- Show only the meat recipes, i.e. `containsMeat == true`
- Show only the vegetarian recipes, i.e. `containsMeat == false`
We can encode these in a [States](/nodes/utilities/logic/states) node. Add in a **States** node. Give it a name, `Filter States`. Then create three states. To match up with our dropdown values we will call them `all`, `meat` and `veg`. By calling them exactly what the `Value` of the dropdown will give us will make it possible to let the **Dropdown** set the state without any intermediary logic.
Then create two **Values** for the **States** node. Call one, `Filter Enabled`, one `containsMeat`. Both should be of the type `Boolean` We will now assign these **Values** for each state.
- `all` - `Filter Enabled` should be `false`, `containsMeat` can be either `true` or `false` (doesn't matter).
- `meat` - `Filter Enabled` should be `true`, `containsMeat` should also be `true`.
- `veg` - `Filter Enabled` should be `true`, `containsMeat` should be `false`.
Now add in a **Array Filter** node. This should be in between the **Static Array** that contains the recipes and the **Recipe List** that holds the items to be presented in the list.
Create a new filter for the property `containsMeat`.
<div className="ndl-image-with-background m">
![](/docs/guides/data/arrays/array-filter-1.png)
</div>
Set the filter to be of `Boolean` type. We now are able to control this filter through an input, i.e. weather the filter should filter for `containsMeat == false` or `containsMeat == true`.
<div className="ndl-image-with-background s">
![](/docs/guides/data/arrays/array-filter-2.png)
</div>
So hook up the **Items** from the **Static Array** containing the recipes to the **Items** of the **Array Filter**. Then from the **Array Filter** to the **Array**.
<div className="ndl-image-with-background xl">
![](/docs/guides/data/arrays/recipe-list-9.png)
</div>
Now we need to control the filter depending on state. So first connect **Value** from the **Dropdown** to our states node. Changing the **Dropdown** will now change the state of the **States** node. Then we connect the **Values** of the **States** node to the inputs on the **Array Filter**, **Enabled** and `containsMeat Filter Value`.
<div className="ndl-image-with-background xl">
![](/docs/guides/data/arrays/recipe-list-10.png)
</div>
You can now filter the recipes.
<ReactPlayer playing autoplay muted loop url="arrays/recipe-list-1.mp4" />
## Creating New Recipes
### Make a simple form
As a final feature we also want to add in the option to create new recipes. It will involve two things:
- Create a new recipe, including data for the recipe, for example, name and description
- Create a new ingredients array for the new recipe and fill with ingredients
Let's start by creating some UI for this functionality.
Start by adding a [Button](/nodes/ui-controls/button) at the bottom of the `Recipe List` page. Give it the label `Add new recipe`. Center it and give it some margin.
<div className="ndl-image-with-background xl">
![](/docs/guides/data/arrays/recipe-list-11.png)
</div>
Clicking this **Button** should take us to a new **Page** where we can add in the information about the recipe. So let's create that page. Find the **Page Router** in the main App and click **Add new page**. Call the new **Page** `New Recipe`. Open the new **Page** component. Copy and paste a title **Text** node from the `Recipe List` page and change the title to `New Recipe`.
Add a new **Navigate** node in the `Recipe List` **Page** so we can get to our new **Page**.
<div className="ndl-image-with-background xl">
![](/docs/guides/data/arrays/recipe-list-12.png)
</div>
In our new form we need controls to store the following in our new recipe.
- A [Checkbox](/nodes/ui-controls/checkbox) to store the `containsMeat` value
- A [Text Input](/nodes/ui-controls/text-input) to store the `name` of the recipe
- A **Text Input** to store the `description`
- A **Text Input** to store the `instructions`
- Two **Buttons**, one for Save and one for Cancel
Add those controls to the new **Page** and make sure the labels are properly set to describe what the user should fill out in each control. Tweak padding, margins and layout to make it look ok. Set the `description` and `instructions` **Text Inputs** as **Text Areas**.
<div className="ndl-image-with-background l">
![](/docs/guides/data/arrays/new-recipe-1.png)
</div>
### Hooking up the data
Looking at the **Page** from a data perspective, this is what we need to do:
1. Once we enter the new page, we should create a new **Object** that will hold the recipe data.
2. After creating the **Object** we also need to create a new **Array** that will hold the ingredients. The items of this **Array** should later be stored in the **Object**.
3. If the user clicks `Save` we should add this **Object** as a new item in our recipe **Array**.
## Creating an **Array** dynamically
Let's orchestrate this based on the **Did Mount** event of the **Page**.
Create a **Create New Object** node. Add the property `containsMeat` and make sure it's set to `false` by default to make sure our filter works. Trigger the **Create New Object** node on **Did Mount**. Once the **Object** is created, we should create the **Array** for the ingredients. When the user clicks `Save` we will store the items in the `ingredients` **Array** in the **Object** representing the recipe.
Then create an **Object** and hook up the controls (**Checkbox** and **Text Inputs**) to the properties they are associated with. Note that we are not using a **Set Object Properties** because we want the **Object** to be updated at any change on the inputs, i.e. not at a specific time.
Here's how the flow looks. Note that it's important to use the **Done** signals to make things happen sequentially, as well as passing on the **Id** of the newly created **Object** to the **Object** node to make it act on the correct **Object**.
<div className="ndl-image-with-background xl">
![](/docs/guides/data/arrays/new-recipe-2.png)
</div>
### Add ingredients to the **Array**
Now we need to add some UI to make it possible to add ingredients. The idea is to have two **Text Inputs** (one for amount and one for the name of the ingredient) and a **Button** that adds the ingredient. We also need a **Repeater** that shows the ingredients as you add them. That repeater can reuse our previous `Ingredient List Item` component. Of course, the **Repeater** gets its items from the **Array** we created that holds the ingredients. Let's also add in a **Text** as a title for the ingredients section to make it easier to understand the form.
All the components are put in a **Group** called `Ingredient Section` to make them easy to handle as a compound.
<div className="ndl-image-with-background xl">
![](/docs/guides/data/arrays/new-recipe-3.png)
</div>
## Adding **Objects** to an **Array**
Now we need to create a new **Object** (representing the ingredient) when the user clicks `Add` and then add that new **Object** to the **Array** of ingredients. We use the node **Insert Object Into Array** for the latter. It takes one **Id** for the **Array** to add to and one **Id** for the **Object** to add. We hook everything up as below.
<div className="ndl-image-with-background xl">
![](/docs/guides/data/arrays/add-ingredient-1.png)
</div>
Try adding a few ingredients. They should show up in the ingredient list.
### Adding the new recipe to the Recipe **Array**
Finally, when the user presses `Save` we want to add the ingredients to the recipe and then add the new recipe to our main recipe **Array** - the one we create in the `Recipe List` **Page** that has an **Id** `Recipe List`.
First we store the **Items** of our ingredients in the `ingredients` property using the **Set Object Properties**. Then, once again we use the **Insert Object Into Array** node to save our recipe in the recipe list. This time we hardcode the **Id** to `Recipe List` and make sure our recipe **Object** is the object to add.
:::note
Another option would have been to store the **Id** of the **Array** instead of its items and then use the **Id** to identify the **Array** when showing the recipe. However, since the **Static Array** don't have an internal **Array** holding its ingredients, this would not work in this case. That's why we store the **Items** rather than the **Id**.
:::
We also make sure the user navigates back to the `Recipe List` once the recipe is added. Same goes for if the user presses **Cancel**, but of course in that case, nothing is saved.
This is how that part is done.
<div className="ndl-image-with-background xl">
![](/docs/guides/data/arrays/save-recipe.png)
</div>
### A minor refactoring
We now expect our newly created recipes to be visible in the Recipe List. However, we can see that's not the case. It's the same old list. What's going on? The reason is that since the `Recipe List` **Page** is created as we nevigate, the items are overwritten with the items of our **Static Array**. See image below.
<div className="ndl-image-with-background l">
![](/docs/guides/data/arrays/static-array-2.png)
</div>
We also see that the filtering happens at the wrong place - we are only filtering what's coming out of the **Static Array**.
To solve this we need to move the assignments of items out, outside the **Page** (that is being re-created) to the main App that's not being recreated when a navigation happens. This is easy since the **Array** can easily be referred to globally through its **Id**.
We simply reorganize our **Arrays** and filters according to the following. We move the **Static Array** and assignment out to the App as below.
<div className="ndl-image-with-background m">
![](/docs/guides/data/arrays/assignment.png)
</div>
And we use the same **Array** (identified by the **Id**) and make sure to filter the items before we feed them into our **Repeater**
<div className="ndl-image-with-background xl">
![](/docs/guides/data/arrays/recipe-list-13.png)
</div>
Try it out, now it should work!
<ReactPlayer playing autoplay muted loop url="arrays/final-1.mp4" />
<div className="ndl-image-with-background m">
![](/docs/guides/data/arrays/thumbnail-1.png)
<ImportButton zip="/docs/guides/data/arrays/arrays.zip" name="Recipe App" thumb="/docs/guides/data/arrays/thumbnail-1.png" />
</div>

View File

@@ -0,0 +1,263 @@
---
title: External Data
hide_title: true
---
import useBaseUrl from '@docusaurus/useBaseUrl'
import ImportButton from '/src/components/importbutton'
# External Data
## What you will learn in this guide
So far we have been working exclusively with local data, often coming from a **Static Array**. This is great for building prototypes and getting started quickly but in real life we will need to integrate with external data. This guide shows you how to do that using REST APIs, the most common type of APIs for web applications.
## Airtable
For this guide we are going to use the [Airtable](https://airtable.com) REST API to read and write to an **Airtable** table. This guide assumes that you know a little bit about REST APIs, and to follow along you need to create an account for **Airtable**. You can read more about the **Airtable** REST API [here](https://support.airtable.com/hc/en-us/articles/203313985-Public-REST-API). Most REST APIs work in similar ways so the knowledge gained here should be applicable on most other APIs.
In this guide we are going to extend the CRUD example from the [UI Controls and data](/docs/guides/data/ui-controls-and-data) guide by connecting it to an **Airtable** table to create, update, read and delete records. To do this I have create a new airtable base and added a table called **Members**, I have also created three fields corresponding to the member object of the guide.
* **Full Name**, a string that contain the name of the member.
* **Receive Marketing Emails**, a checkbox (boolean) that indicated if the member should receive marketing emails
* **Awesomeness**, a number between 0 and 100 indicating the general awesomeness of the member
<div className="ndl-image-with-background l">
![](/docs/guides/data/external-data/airtable.png)
</div>
There is a nice documentation for the different **Airtable** REST API operations that you can perform on a table, but we will review them briefly here before we use them.
Let's get started. We are going to start where we left off in the [UI Controls and data](/docs/guides/data/ui-controls-and-data) guide, if you don't have ready you can import the finished components here.
<div className="ndl-image-with-background xl">
<video width="100%" autoPlay muted loop src={useBaseUrl("/docs/guides/data/external-data/final-crud.mp4")}/>
<ImportButton zip="/docs/guides/data/external-data/final-crud-1.zip" name="CRUD Example" thumb="/docs/guides/data/external-data/final-crud-thumb.png" />
</div>
## The REST node
To do REST API calls we are going to use the built in [REST](/nodes/data/rest) node. First the let's go over some of the properties that are important to know about:
<div className="ndl-image-with-background l">
![](/docs/guides/data/external-data/rest-props.png)
</div>
* **Resources** this is the REST API endpoint and resource, i.e. the URI that will be requested.
* **Method** is the HTTP method that will be used, it can be any of **GET**, **POST**, **PUT**, **DELETE**, **PATCH**
Once you have specified the resource as well as the method there are two small scripts that you will need to use to the first script prepares the request before it is sent, and the second script parses the response.
<div className="ndl-image-with-background l">
![](/docs/guides/data/external-data/rest-props-2.png)
</div>
First, let's take a look at the request script. Using this script you will prepare the request, this is done be modifying the **Request** object. You can change the **resource** (the URI) and the **method** from before.
```javascript
Request.resource = "https://example.org"
Request.method = "POST"
```
You can also modify the HTTP headers.
```javascript
Request.headers['authorization'] = "shhh it's a secret"
```
The ```Reuqest.parameters``` object can be used to set query parameters for the URI. The script below would add ```?search=find-this``` to the URI before the request is made.
```javascript
Request.parameters['search'] = "find-this"
```
Finally you can modify the ```Reuqest.content``` object. This should be a JSON compatible object that is sent if the method is **PUT**, **POST** or **PATCH**. As in many scripts in Noodl you can use the ```Inputs``` object to specify any custom inputs you would like for the node.
```javascript
Request.content = {
something:Inputs.MyInput
}
```
The script above will create a custom input called **MyInput** on the Rest node. Any value connected to that input will be available in the request script using ```Inputs.MyInput``` and in the script above it is used as part of the request content.
Second, we have the **response** script. This script will take the reponse from the REST API call (if it is successful), parse it and expose it as custom outputs on the **REST** node. You have two important objects ```Response.status``` which will contain the HTTP status of the response, and ```Response.content``` which will contain the content parsed as JSON. Just like the request script used the ```Inputs``` object to create custom inputs, this script can also use the ```Ouputs``` object to create custom outputs to expose the response. Let's take a look at a way to use this with the **Airtable** API.
This is a brief introduction to how the REST node works. You can read more in the reference documentation for the [REST](/nodes/data/rest) node. Now let's continue with connecting the CRUD example to **Airtable** using the **REST** node.
## Listing all members
First we're going to use a **REST** node to list all members from our **Airtable** table and put in the **Array** node we use to store members. To do this we need to perform the following REST API call (you need to replace the **id** of the **Airtable** base with your own, and make sure your table is called **Members**).
```javascript
GET https://api.airtable.com/v0/appGx6whFNzxu54eP/Members
```
We will simplye create a **REST** node and change the **Resource** property to the URI above (with your own base id). Then we will connect the **Fetch** input of the **REST** node to the **Did mount** signal output of the root **Group** node. This will make sure that we perform the request as soon as the **Group** node becomes visible. I gave the node a new label "List all members", just to keep track of what it is doing.
<div className="ndl-image-with-background xl">
![](/docs/guides/data/external-data/list-all-members.png)
</div>
We also need to modify the **Request** script to provide the **Airtable API Token**, otherwise the requrest will not go through. Change the request script of the **REST** node to the following, providing your own **API token**.
```
Request.headers['Authorization'] = 'Bearer your-api-token'
```
Now the request should go through if you refresh the viewer in the Noodl editor so that the **Did mount** signal is sent. A sample response from **Airtable** will look something like below. We are going to need to parse this response for it to be useful in our app.
```
{
"records": [
{
"id": "rec0Qw9FdqjcaDF2W",
"createdTime": "2022-04-14T08:40:28.000Z",
"fields": {
"Full Name": "Lisa Simpson",
"Awesomeness": 80
}
},
{
"id": "recMbwuixscqMroQn",
"createdTime": "2022-04-14T14:26:42.000Z",
"fields": {
"Full Name": "Marge Simpson",
"Awesomeness": 0
}
},
{
"id": "recScNKnPqO3xHd4c",
"createdTime": "2022-04-14T08:40:28.000Z",
"fields": {
"Receive Marketing Emails": true,
"Full Name": "Homer Simpson",
"Awesomeness": 50
}
}
]
}
```
We will do that in the response script of the **REST** node. We will need to create a simple array with objects with the **id** and fields directly as properties, then we will return it as an output of the **REST** node. The following small script will do the trick. This should go into the **Response** script.
```
Outputs.Members = Response.content.records.map((r) => ({
id:r.id,
'Full Name':r.fields['Full Name'],
'Receive Marketing Emails':r.fields['Receive Marketing Emails'] == true,
'Awesomeness':r.fields['Awesomeness']
}))
```
We use the ```Outputs``` object to specify a new custom output on the **REST** node that will contain an array of all member objects. It's important that we keep the **id** (it should be lowercase) as it will become the **id** of our **Object**s and we need to use it later. With that script in place, we will have a new output on our **REST** node that we can connect to the **Array** where we keep our members.
<div className="ndl-image-with-background xl">
![](/docs/guides/data/external-data/list-all-members-2.png)
</div>
That's great, now we can list all members and show in the user interface. With that in place, let's move onto to editing and deleting.
## Creating a new member
Now we will repeat the same process for doing a REST API call to create a new member in the **Airtable** table. For that we will use a **REST** node and provide the following resources and method.
```javascript
POST https://api.airtable.com/v0/appGx6whFNzxu54eP/Members
```
Here we need to modify the request script to take an **Object Id** as input and put the properties of that object in the ```Request.content``` object. The following script will do the trick:
```
Request.headers['Authorization'] = 'Bearer your-api-key'
const obj = Noodl.Object.get(Inputs.ObjectId)
Request.content = {
records: [
{
fields: {
"Full Name": obj["Full Name"],
"Receive Marketing Emails": obj["Receive Marketing Emails"],
"Awesomeness": obj["Awesomeness"]
}
}
]
}
```
First set the **Authorization** header with the **API token** just as before. Then we use a custom input ```Inputs.ObjectId``` that will contain the **Id** of the Noodl object we want to send as part of the REST API call. We use the ```Noodl.Object.get``` function to get the object from it's **Id**. With the object at hand we can format the content in a way that the **Airtable** API is expecting. With the script above in place, we can hook the **REST** node up as follows.
<div className="ndl-image-with-background xl">
![](/docs/guides/data/external-data/create-new-member.png)
</div>
This is, when the newly created member object have been added to the **Array** we will trigger the **REST** request by connecting the **Done** output signal from the **Insert Object Into Array** node to the **Fetch** input on the **REST** node. We will also make sure to connect the **Id** of the newly created object to the custom **ObjectId** input that we are using in our request script in the **REST** node.
:::note
One small caveat, when you create a new object in Noodl using the **Create New Object** node it will be assigned a new random **Id** (you can see it by inspecting the connection). But when we create the member in **Airtable** using the request above it will get a new **Airtable** internal id. We need this to perform edits and deletes. The simplest way to solve this is to issue a new "list all members" request. Simply connect the **Success** signal from the "Create new member" REST node to the **Fetch** input of the "List all members" REST node.
:::
Sweet, now we can list members, we can look at the details of a member, and we can even create new members. Give it a try, you should see the new member in the **Airtable** table. Now let's move on to editing a member.
## Editing a member
Editing a member is very similair to creating a new member. We need to use a different method but the same resource. We also need to provide the **Object Id** as part of the URI. As this is a very common pattern for REST APIs there is a neat trick. You can specify a custom input directly in the URI using the ```{MyInput}``` notation. So we will use the following resource and method on this **REST** node. This will automatically create the correct URI for us if we provide the **ObjectId** as input.
```javascript
PATCH https://api.airtable.com/v0/appGx6whFNzxu54eP/Members/{ObjectId}
```
We still need to format the ```Request.content``` object in a way that the **Airtable** API can consume. This is done with the following request script, very similair to the script we used when creating new members.
```
Request.headers['Authorization'] = 'Bearer your-api-token'
const obj = Noodl.Object.get(Inputs.ObjectId)
Request.content = {
fields: {
"Full Name": obj["Full Name"],
"Receive Marketing Emails": obj["Receive Marketing Emails"],
"Awesomeness": obj["Awesomeness"]
}
}
```
With that in place we can hook it up to the output signal **Save** that we send from the edit form component. We also need the **Object Id** as before, and this we can get from the **Edit** event, that is the **Receive Event** node. That is the same **Object Id** that we pass to the edit form component when it is shown. It will look something like this:
<div className="ndl-image-with-background xl">
![](/docs/guides/data/external-data/edit-member.png)
</div>
## Deleting a member
And finally, we need to add support for deleting a member. This we will do, very similair to before, but this time in the **Member Item** component. For that we will use the following resource and method.
```javascript
DELETE https://api.airtable.com/v0/appGx6whFNzxu54eP/Members/{ObjectId}
```
The request script simply needs the API access token.
```
Request.headers['Authorization'] = 'Bearer your-api-token'
```
And then we can hook it up as shown below in the **Member Item** component.
<div className="ndl-image-with-background xl">
![](/docs/guides/data/external-data/delete-member.png)
</div>
That's it. This guide has shown how to use the **REST** node to hook up our CRUD example from the previous [guide](/docs/guides/data/ui-controls-and-data) to and external REST API. With the **REST** node you can connect to most types of APIs that use JSON to send and receive data.

View File

@@ -0,0 +1,283 @@
---
title: List Basics
hide_title: true
---
import ImportButton from '../../../src/components/importbutton'
# List Basics
## What you will learn in this guide
This guide will teach you how to create basic lists in Noodl using the [Repeater](/nodes/ui-controls/repeater) node that is being fed through an [Array](/nodes/data/array/array-node). It will show how to create a simple list item and how to detect that the user clicks it.
## Overview
The guide will walk you through the following steps.
- The **Repeater** node
- Creating a simple **List Item** component
- Connecting the data to the **List Item**
- Detecting when the user clicks an item
## The Repeater Node
The main node for making lists is the **Repeater** node. The **Repeater** node takes an **Array** as an input (the **Items** property). It then and creates a copy of the provided **List Item** template as a component and connects the specific **Object** (or **Record**) in the **Array** with its respective **List Item**.
The **Array** could for example be coming from an [Array](/nodes/data/array/array-node) node, from a database through a [Query Records](/nodes/data/cloud-data/query-records) or a [Static Array](/nodes/data/array/static-array).
Let's get started by starting a new project using the "Hello World" template. Remove the **Text** node and add in a **Repeater** node instead.
<div className="ndl-image-with-background">
![](/docs/guides/data/list-basics/repeater-1.png)
</div>
The **Repeater** needs two things to work:
1. Some **Array** data that can be represented as _List Items_
2. A _Template component_ that will be repeated per item in the **Array**
We will start with the **Array**. In this case we will use the **Static Array** might as well be some other node that outputs an **Array** for example a [Query Records](/nodes/data/cloud-data/query-records) node that queries records from a database. There is a specific guide for working with **Query Records** [here](/docs/guides/cloud-data/quering-records-from-database).
## Providing Items
So create a **Static Array** node. Change its type to `JSON` and paste in the following data.
```json
[
{
"name": "Peter Reid",
"age": 29,
"team": "Everton",
"position": "Midfield"
},
{
"name": "Chris Waddle",
"age": 25,
"team": "Tottenham",
"position": "Midfield"
},
{
"name": "Mark Hateley",
"age": 24,
"team": "Milan",
"position": "Forward"
},
{
"name": "Peter Beardsley",
"age": 25,
"team": "Newcastle",
"position": "Forward"
},
{
"name": "Kenneth Sansom",
"age": 27,
"team": "Arsenal",
"position": "Defender"
}
]
```
<div className="ndl-image-with-background l">
![](/docs/guides/data/list-basics/static-array-1.png)
</div>
Connect the **Items** output of the **Static Array** to the **Items** input of the **Repeater** node.
<div className="ndl-image-with-background l">
![](/docs/guides/data/list-basics/repeater-2.png)
</div>
Now we have items flowing into the repeater.
## Create a List Item Template
Next step is to create a component that can serve as a template for the **Repeater**. One component will be created per item in the **Array** that's feeding the **Repeater**.
Create a new Visual Component. Call it "Player Item".
<div className="ndl-image-with-background">
![](/docs/guides/data/list-basics/list-item-1.png)
</div>
<div className="ndl-image-with-background">
![](/docs/guides/data/list-basics/list-item-2.png)
</div>
In the new component we will add in three [Text](/nodes/basic-elements/text) nodes that will present the information about each player. Make sure to change the layout of the root node to `Horizontal` and add in the three **Text** nodes. Call the **Text** nodes, `name`, `age`, `team` so we can keep track of them. Note that you can use the Canvas view to get a first view of how your list item will look, even before using it in your **Repeater**.
<div className="ndl-image-with-background">
![](/docs/guides/data/list-basics/list-item-3.png)
</div>
## Set the List Item Template
Go back to the main component. We now need select our newly created component as our template. Click the **Repeater** and set the template to "Player Item".
<div className="ndl-image-with-background">
![](/docs/guides/data/list-basics/repeater-3.png)
</div>
You should now see the first view of you data and list item, namely something like the screen below.
<div className="ndl-image-with-background">
![](/docs/guides/data/list-basics/result-1.png)
</div>
The repeater have created an instance of the "Player Item" component for each item in the incoming array. But we immediately see two things to fix:
1. The layout is weird. The **Repeater** takes up the full screen. It then divides the space equally among each list item. We should probably fix the layouting of our listitem a bit.
2. There is no data coming in from the list item. Our **Text** nodes are not connected to anything.
## Fixing the layout of the List Item
We deal with the layout first. In the "Player Item" component, click on the root **Group** and change the sizing to "Explicit width & content height". This will make the vertical size of the list item be as large as the **Texts** they contain, while still taking upp all horizontal space.
<div className="ndl-image-with-background">
![](/docs/guides/data/list-basics/list-item-layout-1.png)
</div>
Lets also add a border around each item and some rounded corners. Change "Border Style" and "Border Radius" to your liking. Finally add in some padding in all four direction (to give some air within the list item) and a margin below the list item to get some air between items.
<div className="ndl-image-with-background">
![](/docs/guides/data/list-basics/list-item-layout-3.png)
</div>
<div className="ndl-image-with-background">
![](/docs/guides/data/list-basics/list-item-layout-2.png)
</div>
This looks better even if much more can be done. It will have to do for now.
## Connecting List Items to Data
Now we want to get the data from each item into the List Item. In the "Player Item" component, add in an [Object](/nodes/data/object/object-node) node. The **Object** node is one of the central data holding nodes in Noodl. Read more about them in a dedicated guide [here](/docs/guides/data/objects).
Click the **Object** and set the **"Get Id From"** property to `From Repeater`.
<div className="ndl-image-with-background">
![](/docs/guides/data/list-basics/object-1.png)
</div>
You have now told the **Object** that the **Repeater** will assign its **Id**, meaning each list item will have its **Object** node point to the respective **Object** in the **Array** that feeds the repeater.
If you hover over your newly created **Object** you will actually see that it contains the data of our last item in the **Array**.
<div className="ndl-image-with-background">
![](/docs/guides/data/list-basics/object-2.png)
</div>
?> Note that since there are several instances of the same List Item in memory at the same time you have to be a bit careful when reading the debug output in the node graph. Generally _the last value_ in a connection or component is going to be shown as debug data. In our case, the last list item data ("Kenneth Sansom") is shown even though all the other **Objects** in the **Array** is in memory as well.
Now we are ready to hook up the **Text** nodes to our data. Create three properties on the **Object** matching the ones we have in the static array, namely `name`, `age` and `team`.
<div className="ndl-image-with-background">
![](/docs/guides/data/list-basics/object-3.png)
</div>
Connect the **Object** properties to the **Text** input on the respective **Text** nodes.
<div className="ndl-image-with-background l">
![](/docs/guides/data/list-basics/connect-1.png)
</div>
While you are connecting the data to the **Text** nodes you should see the **List Items** being populated.
<div className="ndl-image-with-background l">
![](/docs/guides/data/list-basics/result-2.png)
</div>
## Detecting which List Item that was clicked
Finally, we also want to know when the user clicks an item and of course which item it was. Of course we could handle all business logic related to a **List Item** within it, but it's often better to let the **List Items** be pretty simple and handle any complex logic outside the **Repeater**. In short, we want the **Repeater** to be able to report when an item was clicked and which item was clicked.
You do this by letting the List Item component having [Component Outputs](/nodes/component-utilities/component-outputs) that are signals. You can have any number of signals coming out of your **List Item**. For example you may have a complex structure with buttons for changing and removing List Items and you want to know which one the user clicked. However in our simple case we only want to know if the **List Item** was clicked.
Go into the "Player Item" component. Add a **Component Outputs** node. In it, add a property "Click". Then connect the "Click" signal from the root **Group** to the newly created "Click" property on the **Component Outputs**.
<div className="ndl-image-with-background l">
![](/docs/guides/data/list-basics/list-item-4.png)
</div>
Now we are letting the **Repeater** know whenever a **List Item** is clicked.
Go back to the main app. We want to capture the outgoing **Click** signal together with the **Id** of the **Object** associated with the **List Item** that was clicked.
So create an **Object** node and connect the **Item Id** output that is now available on the **Repeater** to its **Id**.
<div className="ndl-image-with-background l">
![](/docs/guides/data/list-basics/connect-3.png)
</div>
This **Object** now will now point to the clicked item. To show that we can add a new **Text** node below the **Repeater**. Align it to the bottom and center and make sure its size is decided by its content, i.e. the **Text** node will only take up as much space as it needs, and it will end up in the center. Also make it larger, e.g. font size 24.
<div className="ndl-image-with-background">
![](/docs/guides/data/list-basics/text-props.png)
</div>
<div className="ndl-image-with-background l">
![](/docs/guides/data/list-basics/repeater-4.png)
</div>
Add the property **name** on the **Object** and connect it to the **Text** node.
We are almost done, we actually need to capture the **Click** event from the **Repeater** for the **Item Id** to update when the user clicks. We anyway only want to show the text for a short while when the user clicks.
To achieve this we add in a [Switch](/nodes/logic/switch) node. This will keep the state weather the **Text** is shown or not so call it "Selected Text Visible". Connect the **Current State** to the **Mounted** attribute of the **Text**. Then add a **Delay** node. Set the duration to 2000 milli seconds (2 seconds).
Let the **Clicked** signal coming from the **Repeater** start the **Delay** by connecting it to **Restart**. As soon as the **Delay** is started we want to show the **Text** so connect **Started** to **On** on the **Switch** and the **Finished** signal to **Off**.
<div className="ndl-image-with-background l">
![](/docs/guides/data/list-basics/repeater-5.png)
</div>
If everything works as expected you will now see the name of the player you click show up for two seconds at the bottom of the screen.
To import the project from the guide click "Import" on the image below.
<div className="ndl-image-with-background">
![](/docs/guides/data/list-basics/final-1.png)
<ImportButton zip="/docs/guides/data/list-basics/list-part-1.zip" name="List Basics" thumb="/docs/guides/data/list-basics/final-1.png" />
</div>

View File

@@ -0,0 +1,59 @@
---
title: Making Connections
hide_title: true
---
import useBaseUrl from '@docusaurus/useBaseUrl'
# Making Connections
## What you will learn in this guide
How to make data connections between nodes to start creating data driven user interfaces.
## Data connections
All nodes have inputs and outputs. Most of the properties of a node (that you can edit in the property panel) are also available as inputs. Many nodes also have outputs that provide some sort of data value. The most common example is the **Text Input** node that provides (amonng other things) the typed text as an output value. Using connections you can ensure that an output from one node is written to an input of another node when it is updated. Take a look at the very simple interface below:
<div className="ndl-image-with-background xl">
![](/docs/guides/data/making-connections/making-connections-ui.png)
</div>
Here you have a **Text Input** and a **Text** UI Element. Now let's say we want the **Text** element to show whatever we type into the **Text Input**. For that we can connect the two nodes.
<div className="ndl-image-with-background xl">
<video width="100%" autoPlay muted loop src={useBaseUrl("/docs/guides/data/making-connections/making-connection.mp4")}/>
</div>
Now when we type something in the **Text Input**, it will output that on the **Text** output. This is connected to the apptly named **Text** input of the **Text** node.
<div className="ndl-image-with-background xl">
<video width="100%" autoPlay muted loop src={useBaseUrl("/docs/guides/data/making-connections/testing-connection.mp4")}/>
</div>
As you can see above, when you start typing in the **Text Input** you can see the **Text** is also updated. You can also see in the node graph editor that the connection lights up briefly when data is updated on the output and written to the input.
If you hover the connection in the node graph editor you can also view the latest value that have been sent over the connection, and if you click the little inspection popup you can pin it. Click it again to unpin it.
Making direct connections is fun an all, but using the many utility nodes available you can convert and augment the data on it's way from the output to the final input. First, delete the connection you just made. Deleting a connection is done by clicking it once to reveal the **Delete Icon** and clicking it again to delete.
<div className="ndl-image-with-background xl">
<video width="100%" autoPlay muted loop src={useBaseUrl("/docs/guides/data/making-connections/delete-connection.mp4")}/>
</div>
Now, here you can see how we use a **String Format** node to make a nice greeting.
<div className="ndl-image-with-background xl">
![](/docs/guides/data/making-connections/string-format.png)
</div>
Now you when you type in the **Text Input** box you can see how the data is first passed to the **String Format** node that then augments the data and passes it along on it's **Formatted** output. Learn more about how the [String Format](/nodes/string-manipulation/string-format) node work in the node reference documentation.
<div className="ndl-image-with-background xl">
<video width="100%" autoPlay muted loop src={useBaseUrl("/docs/guides/data/making-connections/testing-connection-2.mp4")}/>
</div>
We have covered a very important concept that is used for making data driven reactive user interfaces. But we won't get far just connecting different UI Controls together, most often we need to present data from a database, or an external API. For that we will introduce the data nodes (the green ones), and we'll start with the **Variable** in the next guide.

View File

@@ -0,0 +1,299 @@
---
title: Using Objects
hide_title: true
---
import ImportButton from '../../../src/components/importbutton'
# Using Objects
## What you will learn in this guide
Objects is a concept of the global application data model in Noodl, an object is a data structure with properties and a string identifier that can be used to access the object globally in your Noodl application. In this guide you will learn how to use the [Object](/nodes/data/object/object-node) node, the [Create New Object](/nodes/data/object/create-new-object) and the [Set Object Properties](/nodes/data/object/set-object-properties) node to create and store **Objects** in your app. Objects are _local_ meaning they will not be stored in a cloud database by default. They are very useful to store data and states that applies to a usage session of an app or a screen. They are also essential when working with **Arrays** in Noodl since only **Objects** and **Records** can be stored in a Noodl Array.
## Overview
The guide covers the following topics
- Objects in Noodl
- Creating **Objects**
- Setting Properties in **Objects**
- Dynamically creating Objects using the **Create New Object** node.
- **Objects** in **Arrays**
- Using **Objects** in Lists with the **Repeater** node
**Objects** are very similar to **Variables** in Noodl and it's recommended to go through the [Variable guide](/docs/guides/data/variables) before reading this guide.
## What's an Object in Noodl?
**Objects** in Noodl are used to hold data. **Objects** are _local_ meaning they only exist while the App is running. This is the main difference between **Objects** and [Records](/nodes/data/cloud-data/record) - **Records** are stored in a database.
Another related node in Noodl is the [Variable](/nodes/data/variable/variable-node) node. It's also local, but can only store one value, while an **Object** can store a number of values, each in a _property_. So essentially an **Object** holds a couple of data points (properties) that belong together.
Some typical cases could be information about a person (for example `First Name`, `Last Name`, `Address`, `Age`), all the information about a football match (`Home Team`, `Away Team`, `Match Date`, `Score`), etc, etc. There is no limitation on how many properties you can have.
### The `id` of an Object
All **Objects** in Noodl have an **id**. This is the unique identifyer of the **Object** and if you have two **Object** nodes in your app, and you give them the same **id** they will point to the same data. Changing properties on one of the nodes will immediately change the data any other **Object** node with the same id.
In Noodl, when a **Node** need to refer to an **Object** you use the **id** to identify it. For example, an **Array Filter** have an output called **First Item Id** which will hold the **id** of the first object in the resulting array of object after a filter action have been done. By connecting that **First Item Id** to the **Id** of an **Object** you can access its properties.
<div className="ndl-image-with-background l">
![](/docs/guides/data/objects/object-1.png)
</div>
### The properties of an Object
An Object typically have a number of properties. On the **Object** node they have to be explicitly created to be able to access them as outputs and inputs by clicking the `+` in the properties panel.
<div className="ndl-image-with-background">
![](/docs/guides/data/objects/object-properties-1.png)
</div>
Note that just because an **Object** node doesn't have a property added, it doesn't mean that the underlying **Object** don't have it. It's just that this particular node won't have the property available as input and output until you add them.
## Creating Objects
Let's get started and create some objects!
Objects are typically created in two different ways. Either you add in an [Object](/nodes/data/object/object-node) node and give it an **Id** then the object will be created (if it doesn't exists already) when used. The other way is to use the [Create New Object](/nodes/data/object/create-new-object) node and trigger its **Do** signal. That will create a unique new **Object** with a unique **Id**.
So to summarize the two main ways of creating **Objects**
- Statically create by adding an **Object** node give it an **Id**, properties and connect to the input properties.
- Dynamically create by triggering the **Do** signal in a **Create New Object** node.
Let's start with the former.
Start a new project using the "Hello World"-template. Then add in two [Buttons](/nodes/ui-controls/button) and a [Text Input](/nodes/ui-controls/text-input) node right under the root node. Remove the **Text** node and fix the layout slightly by adding som margins and padding. Change the text on the **Buttons** to "Toggle" and "Increment".
<div className="ndl-image-with-background">
![](/docs/guides/data/objects/step-1.png)
</div>
## Setting Properties
We want the "Toggle" button to toggle a [Switch](/nodes/logic/switch) from `false` to `true`, the "Increment" button to increase a [Counter](/nodes/math/counter), and the **Text Input** to generate a text. This will allow us to generate some data to put in an **Object**
So add in the **Switch** and the **Counter** and hook them up.
<div className="ndl-image-with-background xl">
![](/docs/guides/data/objects/step-2.png)
</div>
Now let's save the values in an **Object**. So create an **Object** node, make sure the **Id** is set to `Specify explicitly`. This allows us to set an **Id** of the **Object**. The other option, `From repeater`, is used if the component with this **Object** would be a list item in a [Repeater](/nodes/ui-controls/repeater) node, which we will look at later in this guide.
<div className="ndl-image-with-background">
![](/docs/guides/data/objects/object-properties-2.png)
</div>
Give it the **Id** `Form Values`. Then add three properties: `toggle_state`, `counter_value` and `input_text`.
<div className="ndl-image-with-background">
![](/docs/guides/data/objects/object-properties-3.png)
</div>
Finally, connect the outputs to the **Object** as below.
<div className="ndl-image-with-background l">
![](/docs/guides/data/objects/step-3.png)
</div>
Now, whenever any of these outputs are changed, the new value is stored in the **Object**. This might be exactly what you want - or you might want to control much more specifically when the value is store. For example, maybe you only want to store the text from the **Text Input** when the user hits enter.
To do this more controlled set, we can use the [Set Object Properties](/nodes/data/object/set-object-properties) node. So let's add one in, make sure it's pointing to the same underlying **Object** by giving it the same **Id**, i.e. `Form Values`. You will actually see it suggested when clicking the **Id** input property.
Now you can add in the `input_text` property on the **Set Object Properties** node. You can also see that in the **Set Object Properties** node you can force what type the value should be when setting it. For example, forcing a Number, would store the number `1` rather than the string `"1"` if you type it in the **Input Text** node. However, we want it to always be strings in this case, so pick `String`.
<div className="ndl-image-with-background">
![](/docs/guides/data/objects/object-properties-4.png)
</div>
Now connect the **On Enter** signal of the **Text Input** to the **Do** input signal on the **Set Object Properties** and make sure the **Text** output is hooked up to the `input_text` property. The nodes should look like below.
<div className="ndl-image-with-background xl">
![](/docs/guides/data/objects/step-4.png)
</div>
By hovering on the **Object** node and pinning the debug box by clicking on it, you will be able to see that the `input_text` property is only set when hitting enter.
Note the following:
- Not all properties need to be created on the **Object** or **Set Object Properties** node if you are not using them.
- You can mix and match "lazy" setting directly on the **Object** node and controlled setting using the **Set Object Properties** node depending on need.
## Using Objects as global holders of data
So let's use our **Object** in another component to show that it's available anywhere, as long as you know the **Id**. Create a new visual component and call it `Data Presentation`.
Add three [Text](/nodes/basic-elements/text) nodes to the root **Group**.
<div className="ndl-image-with-background">
![](/docs/guides/data/objects/step-5.png)
</div>
Now add om an **Object** node, give it the **id** `Form Values` and hook it up to the **Text** nodes. Don't forget to add the properties to the **Object**.
<div className="ndl-image-with-background l">
![](/docs/guides/data/objects/step-6.png)
</div>
Finally we add the new component to our main component.
<div className="ndl-image-with-background l">
![](/docs/guides/data/objects/step-7.png)
</div>
Now when changing the values you will see them updating immediately in the `Data Presentation` component.
## Dynamically creating **Objects**
We will change it up a bit and create more **Objects** - actually we will create one every time the user hits enter. So lets change our nodes. In the `Main` component, remove the **Object** and **Set Object Properties** node. Instead add in a [Create New Object](/nodes/data/object/create-new-object) node. Add our properties to the node, i.e. `toggle_state`, `counter_value` and `input_text`.
<div className="ndl-image-with-background">
![](/docs/guides/data/objects/object-properties-5.png)
</div>
Then hook up the output values to the properties as before. Also connect **onEnter** to **Do** on the **Create New Object**.
<div className="ndl-image-with-background l">
![](/docs/guides/data/objects/step-8.png)
</div>
Now whenever we hit enter we create a new **Object** with the properties set according to the **Counter**, **Switch** and **Text Input**. However the **Objects** are left somewhere in memory where we cant reach them. Each new **Object** have a new unique **Id** but we don't save that **Id** anywhere. Let's fix that! Let's put the **Objects** in an **Array**.
## Storing Objects in an Array
Add in an **Array** node. Give the **Array** the **Id** `Form Objects`. Also add a [Insert Object Into Array](/nodes/data/array/insert-into-array) node.
If you check the documentation of the **Insert Object Into Array** node, it needs the **Id** of the **Object** that should be inserted. Conveniently there is an output from our **Create New Object** that holds the **Id** of the newly created object.
To insert the **Object** into the **Array** we need to trigger the **Do** signal on **Insert Object Into Array**. Also, we should not forget to specify the **Id** of the **Array** we want to insert it into, namely the `Form Objects`.
The construct can be seen below.
<div className="ndl-image-with-background xl">
![](/docs/guides/data/objects/step-9.png)
</div>
Now if you bring up the debug info on the **Array** by hovering on it and clicking the debug box, you will see the number of items in the **Array**. Everytime you hit enter in your App it should be growing as we are creating **Objects** and inserting into the **Array**.
## Using Objects in list with the Repeater node
To wrap up this guide, let's also show all the **Objects** we are creating. We want to put them in a list, so we are going to need a **Repeater** node and turn our `Data Presentation` component into a list item.
So start with removing the `Data Presentation` node from our main tree. Replace it with a **Repeater** node. Then connect the **Items** output of the **Array** to the **Repeater** input also called **Items**. Now we are feeding the repeater from the **Array**.
<div className="ndl-image-with-background l">
![](/docs/guides/data/objects/step-10.png)
</div>
Click on the **Repeater** node and make sure you select the `Data Presentation` node as the List Item template.
<div className="ndl-image-with-background">
![](/docs/guides/data/objects/repeater-properties-1.png)
</div>
Now if you enter som data in your app and press enter, you will actually see new items pop up in your list, but they will be empty, or contain your old data if you didnt refresh the viewer recently.
<div className="ndl-image-with-background">
![](/docs/guides/data/objects/result-1.png)
</div>
The reason is that the `Data Presentation` component is still looking for data in our old object called `Form Values` and we removed that **Object**.
Go into the `Data Presentation` component. We need to change how the **Object** there gets its **Id**.
<div className="ndl-image-with-background">
![](/docs/guides/data/objects/object-properties-6.png)
</div>
Change it to `From repeater` and you should now be able to see the correct values in the list. Clean up the layout of the list items by setting their size to content height and add some margins.
Now you know how to use **Objects** in Noodl. Import the final project by clicking the "Import" button below.
<div className="ndl-image-with-background">
![](/docs/guides/data/objects/result-2.png)
<ImportButton
zip="/docs/guides/data/objects/using-objects.zip"
name="Using Objects"
thumb="/docs/guides/data/objects/result-2.png"
/>
</div>
### Noodl Objects and Javascript
Noodl objects can be accessed from Javascript using the [Objects API](/javascript/reference/objects). A Noodl object can be accessed like a normal Javascript object but there are a few important differences. Essentially a Noodl object is like a Javascript object with a string identified, the `id` of the object which can be used to access this object anywhere in your application.
If you have the **Id** of an object you can access it like this in Javascript, e.g. in a function node:
```javascript
const obj = Noodl.Objects["Form Values"];
console.log(obj.input_text);
console.log(obj.id); // This will output "Form Values"
obj.input_text = "Something"; // This will trigger a change on the object nodes with "Form Values" id
```
Sometimes you can end up having properties in a Noodl Object that are references to other Noodl Objects, like this example:
```javascript
const post = Noodl.Objects["Post"];
post.auhtor = Noodl.Object.create({
name:"Lisa Simpson"
})
```
To access nested objects like this you can use this pattern, that is you don't have to first extract the **Id** of the nested object with something like an Expression node you can just connect it directly to the **Id** input of another Object node like this:
<div className="ndl-image-with-background xl">
![](/docs/guides/data/objects/object-nested-1.png)
</div>

View File

@@ -0,0 +1,14 @@
---
title: Working with Data in Noodl
hide_title: true
---
# Working with Data in Noodl
Noodl contains a number of nodes that help you fill your UI with data and read and process the data that the user enters. This is achieved by connecting the visual nodes to **Variables**, **Objects** and **Arrays**.
* **Variables** are singular values
* **Objects** are a collection of different values that belong together, for example data about an order or a customer
* **Arrays** are a list of **Objects** for example a list of orders or customers
These nodes are all _client side_ only, meaning they are not stored in a database in the cloud. They are also only valid during a singular session in an App. For example if the user refresh the browser this data is cleared. Storing this data in the cloud is easy however and you can learn more about that in the [Cloud Data Guides](/docs/guides/cloud-data/overview).

View File

@@ -0,0 +1,173 @@
---
title: UI Controls and data
hide_title: true
---
import useBaseUrl from '@docusaurus/useBaseUrl'
import ImportButton from '/src/components/importbutton'
# UI Controls and data
<div className="ndl-image-with-background xl">
<video width="100%" autoPlay muted loop src={useBaseUrl("/docs/guides/data/ui-controls-and-data/final-crud.mp4")}/>
<ImportButton zip="/docs/guides/data/ui-controls-and-data/final-crud-1.zip" name="CRUD Example" thumb="/docs/guides/data/ui-controls-and-data/final-crud-thumb.png" />
</div>
## What you will learn in this guide
In the guides up to this point we have often connected UI Controls, such as **Text Inputs**, to data. In this guide we will present a few patterns that is commonly used to connect data to UI controls.
## Connecting UI Controls to data
A very common pattern is that we want a UI Control to write to some sort of data node (**Variable**, **Object** etc) when it is updated. All UI Controls that allow for some sort of data input has both an input and an output for the data. For instance, the [Text Input](/nodes/ui-controls/text-input) has both an input and output called **Text**, and the [Slider](/nodes/ui-controls/slider) has an input and output called **Value**.
The output is used to write the data to some sort of data node. Most UI Controls have a **Changed** signal output that is triggered when the user has interacted with the control. This should be used as a trigger to an action that writes data. The most simple one being shown below. When the slider is updated we write the current value to a **Variable**.
<div className="ndl-image-with-background l">
![](/docs/guides/data/ui-controls-and-data/slider-set-variable.png)
</div>
This is the most basic pattern at it will make sure the **Variable** named *Volume* is updated when the slider is dragged. However, if the *Volume* variable is updated somewhere else, the slider is not updated to refelect that change. For that we can connect the **Variable** to the slider **Value** input as well. Like below:
<div className="ndl-image-with-background l">
![](/docs/guides/data/ui-controls-and-data/slider-hooked-up.png)
</div>
This can be useful to create truly reactive user interfaces. Take the simple example below, where you have both a **Text Input** and a **Slider** that are both controlling the volume. The slider updates as soon as the user interacts with it, and the text input when it's blurred (looses focus) or when you press enter.
<div className="ndl-image-with-background l">
![](/docs/guides/data/ui-controls-and-data/slider-and-text-input.png)
</div>
Hooking you controls up likes this will keep them in sync when interacting with them. Check out the result below:
<div className="ndl-image-with-background xl">
<video width="100%" autoPlay muted loop src={useBaseUrl("/docs/guides/data/ui-controls-and-data/slider-and-text-input.mp4")}/>
</div>
## Forms
Many times you want to use a form, or simply a set of UI Controls, to view and edit the data in an **Object**. You could create a component that has an input for the **object id** of the object it should edit and then use the pattern above, but instead we want to save when a button is clicked. Let's say we want to create the simple form below:
<div className="ndl-image-with-background l">
![](/docs/guides/data/ui-controls-and-data/form-1.png)
</div>
We will keep the information about a user (full name etc) in an **Object** and we can edit the object using the following simple component. Here you can see that the **Object Id** is provided as an input to this component. An **Object** node is used to "read" the data and connect it to the UI Controls, this will populate them with the correct data for the object when the **Object Id** changes. The properties are then written to the object when the **Save** button is clicked using the **Set Object Properties** node.
<div className="ndl-image-with-background l">
![](/docs/guides/data/ui-controls-and-data/form-2.png)
</div>
## Create, Read, Update & Delete
A very common pattern in web apps is CRUD (Create, Read, Update & Delete) user interfaces. This is you have a list of objects and you want to be able to create, read, update and delete these objects. Let's extend out form into a full CRUD user interface.
To begin with, we'll add a few small things to our **Form** component that we created above. First we will expose the **Mounted** property on the root **Group** node as an input. This will allow us to control the visibility of the form when using it in our CRUD interface.
<div className="ndl-image-with-background l">
![](/docs/guides/data/ui-controls-and-data/crud-1.png)
</div>
Secondly, we will add a cancel button and send both the save button and cancel button **Clicked** signals as outputs from this component.
<div className="ndl-image-with-background l">
![](/docs/guides/data/ui-controls-and-data/crud-2.png)
</div>
Then we end up with this. This is now our **Form** visual component. It will be used both for editing and creating new objects.
<div className="ndl-image-with-background l">
![](/docs/guides/data/ui-controls-and-data/crud-3.png)
</div>
Now lets start on a new component, let's call this one **Members List**. Imaging the we want to manage a list of members with their name, if they want our marketing email and their general awesomeness. First we create the members list. We won't cover things like repeaters in detail in this guide, check out the [list](/docs/guides/data/list-basics) guide.
<div className="ndl-image-with-background l">
![](/docs/guides/data/ui-controls-and-data/crud-4.png)
</div>
We have a [Repeater](/nodes/ui-controls/repeater) node that is connected to an [Array](/nodes/data/array/array-node) that we have given the **Id** "Members". This array will be empty to start with. We also have a button that we will later use to show the create form. We also need a **Member Item** that the repeater will use as template.
In the **Member Item** component, we simply show the full name of the object, and we include a delete button.
<div className="ndl-image-with-background l">
![](/docs/guides/data/ui-controls-and-data/crud-5.png)
</div>
Now, let's add the form component to our **Members List** component to support creating new members. This is what that looks like:
<div className="ndl-image-with-background l">
![](/docs/guides/data/ui-controls-and-data/crud-6.png)
</div>
Let's review the different parts here:
* We created a [States](/nodes/utilities/logic/states) node and gave it three different states, **Create**, **Update** and **List**. These states will be used to toggle the visibility of our different parts of the user interface. We also made sure it starts in the **List** state.
* When the **Add member** button is clicked, a **Create New Object** node is triggered. In this node we set the default properties of the new member object. That means that the newly created object will start with these properties.
* When the **Create New Object** is done the states node is switched to **Create** state and the **Id** of the newly created object is passed to the **Object Id** input of the **Form** component that we created before.
* The **At List** and **At Create** outputs from the states node is used to control the visibility of the member list and the create form. So that changing the state of the **States** node will show the correct component.
* Finally, when the **Save** signal is sent from the **Form** component (the user has clicked the save button) the object is inserted into the **Members** array.
Phew, now we have the create part in place. Let's add the read and update part as well. For that we will just first add a small thing to the **Member Item** component. We will send an event with the [Send Event](/nodes/events/send-event) node when the text label is clicked, and we will pass along the object **Id** of the clicked object.
<div className="ndl-image-with-background l">
![](/docs/guides/data/ui-controls-and-data/crud-7.png)
</div>
We simply called the event **Edit**, now we can receive the event in the **Members List** using the [Receive Event](/nodes/events/receive-event) node component and use it to toggle the **States** node to **Edit** state. As you can see we also added another instance of the **Form** component at the bottom of the **Members List** component and we hooked it up to the **States** node to control it's visibility.
<div className="ndl-image-with-background l">
![](/docs/guides/data/ui-controls-and-data/crud-8.png)
</div>
Now that we have both create, read and update in place this is what we end up with in the **Members List** component.
<div className="ndl-image-with-background l">
![](/docs/guides/data/ui-controls-and-data/crud-9.png)
</div>
Now we just have one final thing left, and that is to hook up the delete button in the **Member Item** component. That is pretty easy to do, we simple connect the **Click** signal to an **Remove Object From Array** making sure the we specify the array id **Members** that we used in the **Members List** component (so we are changing the right array) and then provide the object **Id** to remove as well.
<div className="ndl-image-with-background l">
![](/docs/guides/data/ui-controls-and-data/crud-10.png)
</div>
And there we go, now we have create the a fully functional CRUD user interface:
<div className="ndl-image-with-background xl">
<video width="100%" autoPlay muted loop src={useBaseUrl("/docs/guides/data/ui-controls-and-data/final-crud.mp4")}/>
<ImportButton zip="/docs/guides/data/ui-controls-and-data/final-crud-1.zip" name="CRUD Example" thumb="/docs/guides/data/ui-controls-and-data/final-crud-thumb.png" />
</div>

View File

@@ -0,0 +1,308 @@
---
title: Using Variables
hide_title: true
---
import ImportButton from '../../../src/components/importbutton'
# Using Variables
## What you will learn in this guide
In this guide you will learn how to use the [Variable](/nodes/data/variable/variable-node) node and the [Set Variable](/nodes/data/variable/set-variable) node to store data in your application for later use. Variables are _local_ meaning they will not be stored in a persistent database. They are very useful to store data and states that applies to a usage session of an app or a screen.
## Overview
The guide covers the following topics
- Setting variables
- Reading variables
- Accessing variables globally
- Using variables with conditions
## When to use Variables?
When developing an app, you often run into the situation that you need to hold on to a value - a piece of data - and use it at a different place in your app.
For example, you may have a [TextInput](/nodes/ui-controls/text-input) where the user can input their name. You want the name first letter of the name to be always capitalized, no matter what the user inputs it. This name should be used through out the App. Hence you would like to store it so you can retrieve it on various places in the app.
You cannot really make a connection directly from the **TextInput** node to all the places where the name will be used. First of all, there might be too many of those connections. Second of all, the nodes that need the name are maybe not in the same component so you can't really reach them. And lastly, you don't want the intermittent name, while the user is entering it, you only want it when the user is done, and then you want it with a capital first letter and not exactly how the user wrote it. This is use one case of many, where a Variable comes in handy.
## Storing and reading variables
Let's start by opening a new project. You can base it on the Hello World template. Then add a **TextInput** node. Change its' label to "Name". You may want to add a little bit of padding to the parent container as well, to make sure the **TextInput** get some air around itself.
<div className="ndl-image-with-background">
![](/docs/guides/data/variables/nodes-1.png)
</div>
<div className="ndl-image-with-background">
![](/docs/guides/data/variables/screen-1.png)
</div>
Now we are going to do two things. 1. We want to store the value of the TextField in a **Variable** node when the user hits _Enter_. 2. We also want to capitalize the first letter of the name, before we store it.
Let's start with the capitalization. Add a [Function](/nodes/javascript/function) node. Edit the script and paste the following:
```javascript
if (Inputs.name !== undefined) {
Outputs.capitalizedName =
Inputs.name.substr(0, 1).toUpperCase() + Inputs.name.substr(1)
}
```
<div className="ndl-image-with-background xl">
![](/docs/guides/data/variables/function-1.png)
</div>
It's not important right now that you understand exactly how the Javascript works, but it will capitalize the first letter of what you feed into the **Function** node and set it on the output named `capitalizedName`.
Connect the **Text** output from the **TextInput** node to the **name** input on the newly created **Function** node.
<div className="ndl-image-with-background l">
![](/docs/guides/data/variables/nodes-2.png)
</div>
### Setting a Variable
Ok so let's store the capitalized name in a **Variable**. To do this, add a **Set Variable** node.
<div className="ndl-image-with-background l">
![](/docs/guides/data/variables/nodes-3.png)
</div>
### The Variable Name
Each variable need a **Name**. Any **Variable** or **Set Variable** node anywhere in your App that has the same **Name** will refer to the same actual data. The **Name** is case sensitive, and we are going to pick the name `userName`.
We will also set the type of the **Variable** to a **String**. This is important, for example, if the user chooses a username which are only numbers. With the **String** type set, we know even in that case, what's stored in this **Variable** is a **String**.
<div className="ndl-image-with-background">
![](/docs/guides/data/variables/panel-1.png)
</div>
Then connect the `capitalizedName` output from the **Function** node to the **Value** input of the **Set Variable** node.
<div className="ndl-image-with-background xl">
![](/docs/guides/data/variables/nodes-4.png)
</div>
<div className="ndl-image-with-background xl">
![](/docs/guides/data/variables/nodes-5.png)
</div>
We have one more thing we need to do to actually store the value in the **Variable**. We need to trigger the **Do** signal in the **Set Variable** node. This makes it possible for us to control exactly what we save. Again, as the user is typing in the **TextInput** the `capitalizedName` output will keep updating (try it!) and we only want to save once the user press _Enter_.
So, connect the **onEnter** output on the **TextField** to the **Do** input signal on the **Set Variable** and we are done with storing data in a **Variable**.
<div className="ndl-image-with-background l">
![](/docs/guides/data/variables/nodes-6.png)
</div>
### Reading a Variable
Ok, now we want to use the value we just stored. We want to include the name in the `Hello World` greeting. So lets first add a [String Format](/nodes/string-manipulation/string-format/) node. Make sure the text in the node is "Hello World {name}".
<div className="ndl-image-with-background xl">
![](/docs/guides/data/variables/nodes-7.png)
</div>
<div className="ndl-image-with-background">
![](/docs/guides/data/variables/panel-2.png)
</div>
The **String Format** node now should have an input called **name**.
Create a **Variable** node. Make sure its' name is `userName` (it should pop up as a suggestion when you click the **Name** input in the panel), just as before. This **Variable** node will now refer to the same **Variable** as we were storing before.
Connect the **Value** output of the **Variable** to the **name** input of the **String Format** node. Finally, connect the **Formatted** output from the **String Format** node to the **Text** input on the **Text** node.
<div className="ndl-image-with-background xl">
![](/docs/guides/data/variables/nodes-8.png)
</div>
Now test your app. Start writing a name in the **Text Input**, press _Enter_ and you should see the "Hello World"-text changing.
<div className="ndl-image-with-background">
![](/docs/guides/data/variables/screen-2.png)
</div>
## Accessing Variables Globally
Now let's change the structure a little. We will create a **Popup** and present the "Hello World" message in it only after the user have pressed _Enter_.
:::note
If you want to learn more about popups, take a look at the [Popups](/docs/guides/navigation/popups) guide.
:::
Start by creating a new visual component, call it "Hello Popup".
<div className="ndl-image-with-background">
![](/docs/guides/data/variables/create-popup-component-1.png)
</div>
This will become our popup. In the new component. Change the size so it takes up 80% of the width and 50% of the height. Make it's layout "Absolute". Align it in the center. Also change the color to a nice color, perhaps blue.
<div className="ndl-image-with-background">
![](/docs/guides/data/variables/popup-panel-1.png)
</div>
<div className="ndl-image-with-background">
![](/docs/guides/data/variables/popup-panel-2.png)
</div>
Now, we want to move the **Text** node and the nodes feeding it with the text from the App component to the popup. Go back to the "App" component and selecte all nodes. Then copy them (Ctrl+C/Cmd+C). Go back to the "Hello Popup" component and paste the nodes.
Remove all nodes except the **Text** node, the **String Format** node and the **Variable** node. Then drag in the **Text** node so it becomes the only child of the **Group** node.
<div className="ndl-image-with-background xl">
![](/docs/guides/data/variables/nodes-9.png)
</div>
Then remove the same nodes from the "App" component.
<div className="ndl-image-with-background xl">
![](/docs/guides/data/variables/nodes-10.png)
</div>
We have now moved the "Hello World" text into a Popup. So let's open the Popup using the [Show Popup](/nodes/popups/show-popup) node. Create the node and connect the **Done** signal from the **Set Variable** node to the **Show** signal on the **Show Popup** node.
<div className="ndl-image-with-background xl">
![](/docs/guides/data/variables/nodes-11.png)
</div>
Also make sure the component that's being shown as a popup is the "Hello Popup".
<div className="ndl-image-with-background l">
![](/docs/guides/data/variables/popup-panel-3.png)
</div>
To back up for a second; what we have done now is to set the Variable `userName` when the user hits _Enter_. When the Variable have been set (the **Done** signal) we open a Popup with a new component. The component access the same Variable and shows the "Hello Message".
Before trying it, we should probably make it possible to close the popup.
In the "Hello Popup" component. Add a [Close Popup](/nodes/popups/show-popup) node and connect the **Click** signal from the parent group to the **Close** signal.
<div className="ndl-image-with-background l">
![](/docs/guides/data/variables/nodes-12.png)
</div>
As you can see from this example, Variables are available in any component in your App.
<div className="ndl-image-with-background l">
![](/docs/guides/data/variables/screen-3.png)
</div>
## Using Variables with Conditions
A very useful way to use Variables is when you want your App to show different things depending on certain conditions. For example, if the user enters a name that's too short, you want the text to ask them to pick another name. Otherwise you want to show the Hello message.
The general pattern is the following: Have a Variable that holds the final value to be used by a component (in this case a text to be shown by a **Text** node). Then use a [Condition](/nodes/utilities/logic/condition) to trigger two different **Set Variable** nodes, depending on the condition. The pattern is outlined below.
<div className="ndl-image-with-background l">
![](/docs/guides/data/variables/pattern-1.png)
</div>
So let's try it on our example. We want to check for the case when the user name is less than 6 characters and ask the user to enter a new and longer name.
So using the pattern above we introduce a new **Variable** in the "Hello Popup". Let's call it `popupText`. We connect its value to the text node and remove the old connection.
<div className="ndl-image-with-background l">
![](/docs/guides/data/variables/nodes-13.png)
</div>
Now we need to check the length of the provided name. This is easy using a combination of a [String](/nodes/data/string) node, an [Expression](/nodes/math/expression) node and a [Condition](/nodes/utilities/logic/condition) node.
Connect the **value** output from the **Variable** `userName` to the a **String** node. There is an output from the **String** called **Length** which holds the length (i.e. number of characters) of the string.
Then create an **Expression** node. We want to check that the length of the name is 6 characters or more. So edit the expression and write
```
length >= 6
```
<div className="ndl-image-with-background">
![](/docs/guides/data/variables/expression-1.png)
</div>
The expression node should now have an input called **length**. Connect the **Length** output from the **String** node to that input.
Finally connect the **Result** output to the **Condition** input of a **Condition** node.
<div className="ndl-image-with-background l">
![](/docs/guides/data/variables/nodes-14.png)
</div>
The **Condition** node has two signal outputs, **on True** and **on False**. We will use these signals to either set the previous "Hello World" string, or the warning string about the too short name. Let's set it up using a **String** node with a predefined error string and the **Set Variable** nodes we used before.
<div className="ndl-image-with-background l">
![](/docs/guides/data/variables/nodes-15.png)
</div>
Try it out with a few different names and see that it works. You may have to tweak the layout of the text to make it look nicer.
You can import the whole project in Noodl by clicking import below.
<div className="ndl-image-with-background l">
![](/docs/guides/data/variables/total-1.png)
<ImportButton
zip="/docs/guides/data/variables/using-variables.zip"
name="Using Variables"
thumb="/docs/guides/data/variables/screen-3.png"
/>
</div>