Initial commit

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

View File

@@ -0,0 +1,33 @@
---
title: Change nodes at build time
hide_title: true
---
<head>
<meta name="robots" content="noindex,nofollow,noarchive" />
</head>
# Change nodes at build time
:::note
This is recommended to only use in 2.7.x.
If using it in 2.6.x, you will change the current project
which will not be temporary during building.
:::
```js
module.exports = {
async onPreBuild(context) {
// Get all the "Function" nodes
const functionNodes = context.project.getNodesWithType('JavaScriptFunction');
functionNodes.forEach((node) => {
// Replace all "Hello World" to "Hello" in the scripts
node.parameters.functionScript = node.parameters.functionScript
.replace("Hello World", "Hello");
});
},
};
```

View File

@@ -0,0 +1,147 @@
---
title: Create a build script
hide_title: true
---
<head>
<meta name="robots" content="noindex,nofollow,noarchive" />
</head>
# Build scripts
Noodl has a way where you can hook into the different build events that
are triggered from the editor.
:::danger
This is an experimental feature, that might be changed in the future.
:::
### Where to use it?
- [Generate a Sitemap and static pages](/javascript/extending/build-script/sitemap-and-seo)
- [Change nodes at build time](/javascript/extending/build-script/change-nodes-at-build-time)
## Create a build script
To add a build script it has to be placed inside a folder in the project.
As long as the file ends with `.build.js` it will be picked up by Noodl.
The execution order of the build scripts are based on alphabetical order.
```
my-noodl-project/
.noodl/
build-scripts/
[HERE]
```
### Example
Here is an example of what kind of events you can listen to:
```js
module.exports = {
async onPreBuild(context) {
// Occurs before the build.
},
async onPostBuild(context) {
// Occurs after the build.
},
async onPreDeploy(context) {
// Occurs before the build is deployed.
},
async onPostDeploy(context) {
// Occurs after the build is deployed.
},
};
```
## What is Context?
Context is a little different in each method,
but generally have the same methods.
:::note
More documentation to come later!
:::
### General
```ts
workspaceId: string;
project: ProjectModel;
environment: {
name: string;
description: string;
masterKey: string;
appId: string;
} | undefined;
/**
*
* @param options
* @param callback
*/
activity(options: { message: string; successMessage?: string; }, callback: () => Promise<void>): Promise<void>;
/**
*
* @param type
* @param message
*/
notify(type: 'notice' | 'success' | 'error', message: string): void;
/**
* Returns a list of all the pages with absolute paths.
*
* @returns [
* {
* title: "page title",
* path: "/path-1/path-2",
* meta: {}
* },
* // ...
* ]
*/
getPages(options: {
expandPaths?: (route: RouteNode) => Promise<string[]>;
}): Promise<readonly PageObject[]>;
/**
* Create a index.html page similar to the one created for the web app.
*
* @returns a string containg the HTML code.
*/
createIndexPage(options: {
/**
* Override the title from project settings.
*
* Default: undefined
*/
title?: string;
/**
* Append the headcode from the project settings.
*
* Default: undefined
*/
headCode?: string;
}): Promise<string>;
/**
* Returns a traversal graph of the nodes.
*
* @param selector
* @returns
*/
graphTraverse(
selector: (node: NodeGraphModel.Node) => boolean,
options: NodeGraphTraverserOptions
): NodeGraphTraverser;
```

View File

@@ -0,0 +1,196 @@
---
title: Build script that generate Sitemap and static pages
hide_title: true
---
<head>
<meta name="robots" content="noindex,nofollow,noarchive" />
</head>
# Generate a Sitemap and static pages
Having a `sitemap.xml` is very important for SEO,
so that search engines can build up a good map on the website.
With Noodl we can create build scripts that will alter the files after building.
What is really cool with this feature is that you can find all the information on
how the project is built, so we can find all the Pages nodes and generate a nice
sitemap and even create static index.html files to improve the SEO even more!
## Examples
Here are 2 examples of a script that generates a sitemap from all the pages.
### Static pages for Sitemap.xml
In case you don't have any dynamic pages, this script is really easy to use.
```js
const fs = require("fs");
/**
* Sitemap class that helps us build the sitemap XML.
*/
class Sitemap {
constructor() {
this.urls = [];
}
add(entry) {
this.urls.push(entry);
}
toXml() {
let xml = `<urlset
xmlns="http://www.sitemaps.org/schemas/sitemap/0.9"
xmlns:news="http://www.google.com/schemas/sitemap-news/0.9"
xmlns:xhtml="http://www.w3.org/1999/xhtml"
xmlns:image="http://www.google.com/schemas/sitemap-image/1.1"
xmlns:video="http://www.google.com/schemas/sitemap-video/1.1"
>`;
xml += this.urls
.map((item) => {
let entry = `<url>`;
Object.keys(item).forEach((key) => {
entry += `<${key}>${item[key]}</${key}>`;
});
return entry + `</url>`;
})
.join("");
return xml + `</urlset>`;
}
}
module.exports = {
async onPostBuild(context) {
/**
* this.getPages() returns a flat array of all the pages,
* like this:
*
* [
* {
* title: "page title",
* path: "/path-1/path-2",
* }
* ]
*
*/
const pages = await context.getPages();
// Create our Sitemap class
const sitemap = new Sitemap();
// Loop over all the pages
pages.forEach((page) => {
// Add the page to the sitemap with
// the sitemap tags we want to use.
//
// Here is what kind of tags there are:
// https://www.sitemaps.org/protocol.html
sitemap.add({
// NOTE: In this example the path is not an absolute URL, which is required to make sitemaps work correctly.
loc: page.path,
changefreq: "weekly",
priority: 0.5,
});
});
// Write our sitemap.xml to the outputPath
//
// if you are deploying via our cloud service
// this will also upload the file.
await fs.promises.writeFile(
`${context.outputPath}/sitemap.xml`,
sitemap.toXml()
);
},
};
```
### Dynamic pages for Sitemap.xml
To make this one work you have to provide the information required by Noodl
to understand what your dynamic pages have.
```js
// We can do that by sending more information to the "getPages" method.
const pages = await context.getPages({
// Async function that is called for each page that has variables.
async expandPaths(route) {
// Check the current page path.
if (route.current.path === "page-3/{id}") {
// Return a list of our expanded paths.
return [
{
title: "My custom title",
path: "page-3/0",
meta: {
description: "My description",
keywords: "",
},
},
{
path: "page-3/1",
},
{
path: "page-3/2",
},
];
}
// Return the default value.
// this will not be called unless there is a variable in the path.
// So we are returning an invalid path here.
return [
{
path: route.current.path,
},
];
},
});
// ...
// To access the meta data from the pages we can call
// pages[0].meta
```
### Generate static pages
We can expand on the previous example with dynamic pages for Sitemap.xml.
In this case we want to take all the information and write a index.html file
for each page.
```js
const fs = require('fs');
const path = require('path');
// ...
// Loop over all the pages
for (let index = 0; index < pages.length; index++) {
const page = pages[index];
// Create the filepath where we want to save the new index.html file.
const pageDir = path.join(context.outputPath, page.path);
// Generate a index.html file with our custom title and headcode.
const content = await context.createIndexPage({
title: page.title,
headCode: `
<meta name="description" content="${page.meta.description}">
<meta name="keywords" content="${page.meta.keywords}">
`,
});
// Create all the folders to the path.
await fs.promises.mkdir(pageDir, {
recursive: true,
});
// Save our new index.html file.
await fs.promises.writeFile(path.join(pageDir, "index.html"), content);
}
```

View File

@@ -0,0 +1,161 @@
---
hide_title: true
hide_table_of_contents: true
title: Create a new core node
---
# Create a new core node
Noodl is very extensible. As a developer you can add new modules with new capablities, create connections to data or make new visual components in your workspace. This guide gets us started by showing how the Noodl command line tool works and how to create an extension module with a single new node. This node will behave and appear just like the standard core nodes of Noodl.
:::note
This guide requires <a href="https://nodejs.org/en/download/" target="_blank">Node.js</a> and <a href="https://docs.npmjs.com/downloading-and-installing-node-js-and-npm" target="_blank">npm</a> installed.
:::
## Overview
This guide will walk you through how to create a **Noodl Module**. A Noodl Module can contain new core nodes to use in your projects. You can for example wrap an existing JavaScript library and expose it as a node in Noodl.
The general process works like this
* Set up a new **Module Project** where you can write the code for your module.
* Test your module in one of you projects while developing it.
* When you are done, build the module and deploy it to the projects you want to use it in.
## Install the Noodl CLI
First you need to install the Noodl command line interfaces. If you have not previously installed the CLI you can do so via npm.
```bash
npm install -g @noodl/noodl-cli
```
## Create a Module Project
With the CLI tool you can easily create a new Noodl module from a template:
```bash
noodl-cli new lib ./my-noodl-module
```
You need to specify a directory name that will be created. The directory will contain everything you need to get started. Using the command above, the directory _my-noodl-module_ will be created.
The newly created directory has the following structure:
```
my-noodl-module/
module/
project/
tests/
icon.png
module.json
```
First some notes on the content of a library module:
- The **module** directory contains the source code for the module as well as build scripts and any assets you might want, such as fonts, css etc.
- The **project** and **tests** folder can be ignored
First enter the **module** directory and install the needed dependencies:
```bash
cd module
```
```bash
npm install
```
If your module uses any other external libraries through NPM they will be installed as well.
## Developing your module
You develop your module mainly by editing the file ``module/src/index.js``. From start it contains some example code that you can use as a boiler plate. There is currently no official documenation of the Noodl module SDK but you can find the source code to a number of modules [here](https://github.com/noodlapp).
As you are developing your module you would want it packaged up and deployed in a Noodl project where you can test it. To do that you first have to create a new Noodl project that will be your test project. Once you've done that, find the local folder of that project by clickin the cogwheel ("Settings") and "Open project folder".
<div class="ndl-image-with-background m">
![](/javascript/extending/open-project-folder.png)
</div>
Copy the full path to that folder - you will need it in the next step.
Now open the file ``/module/src/webpack.config.js``. Among other things, this file specifies where to deploy the module. We want to make sure its deployed to our test project.
Update the row containing ``var outputPath = ...`` to the following
```javascript
var outputPath = path.resolve('<the absolute path that your project>', 'noodl_modules/' + pjson.name);
```
Now go back to your terminal window (that was located in the ``modules/`` folder) and write the following.
```bash
npm run dev
```
This will enter development mode where your module is automatically rebuilt and redeployed to your project when you make changes in the source code.
If you started from the boiler plate code in ``module/src/index.js`` you will already have a module now in your project. Reload the Noodl project by closing it and opening it again, or simply press ctrl+R (Windows) / cmd+R (Mac) when you are in the Node Editor. Then bring up the Node Picker and you should see your new core node under "External Libraries".
## Overview of the module code
The file _index.js_ contains the code for your nodes. Open it in your favorite editor and have a look. The file contains boilerplate code for a simple new core node, let's look at the different sections:
First you must import the Noodl SDK.
```javascript
const Noodl = require('@noodl/noodl-sdk');
```
Next you will define the code for the new node.
```javascript
const MyFullNameNode = Noodl.defineNode({
category: 'My Utils',
name: 'Full Name',
inputs: {
FirstName: 'string',
LastName: 'string',
},
outputs: {
FullName: 'string',
},
changed: {
FirstName: function () {
this.setOutputs({
FullName: this.inputs.FirstName + ' ' + this.inputs.LastName,
});
},
LastName: function () {
this.setOutputs({
FullName: this.inputs.FirstName + ' ' + this.inputs.LastName,
});
},
},
});
```
- You need to specify the **name** of the node, this is the name that shows up in the list when creating new nodes.
- you can optionally specify a **category**, this will also be used in the new node popup in Noodl.
Finally you need to define the specification of your module.
```javascript
Noodl.defineModule({
nodes: [MyFullNameNode],
setup() {
//this is called once on startup
},
});
```
Again, check out the [Noodl Repo](https://github.com/noodlapp) at GitHub for some module examples.
## Deploying your module
When you are happy with your module you can do a proper deploy. Go back to the terminal window (still in the ``modules/`` folder) and write.
```bash
npm run build
```
This deploys an optimized version of the module. If you want to use the module in a different project, just change the path in ``/module/src/webpack.config.js`` and do ```npm run build``` again.

View File

@@ -0,0 +1,117 @@
---
hide_title: true
hide_table_of_contents: true
title: Create a Visual node with React
---
# Create a Visual node with React
Noodl is built with React which makes it easy for you to add custom or community React components to your workspace. This guide will help you create a React library from scratch and push it to your Noodl workspace.
## Setup
In order to complete this guide you must install the _Noodl CLI_ and learn how to push the module to your workspace. Please review [this guide](/javascript/extending/create-lib) first.
With the CLI tool you can easily create a new react library module from a template:
```bash
noodl-cli new react-lib ./my-react-lib
```
You need to specify a directory name that will be created. The directory will contain everything you need to get started. Using the command above, the directory _my-react-lib_ will be created.
The newly created directory has the following structure:
```
my-react-lib/
module/
project/
tests/
icon.png
module.json
```
Just like in the introductory [guide](/javascript/extending/create-lib) the folder contains the **project** and **tests** subfolders that you may to import into Noodl. Especially the **tests** folder is a good place to play around with your library and create tests to make sure the component is working as expected.
:::note
It is important that you do not change the name of the **project** and **tests** folders. The build scripts in the _module_ folder is dependent on these names or it will not build the module properly and you cannot push to your workspace.
:::
## A tour of the code
Now you have created a new React library module from the template and you have pushed it to your Noodl workspace. Let's review the code a bit to get you up and running.
The react code can be found in the **module** directory.
```
my-react-lib/
module/
src/
index.js
assets/
manifest.json
package.json
webpack.config.js
project/
...
```
Open _index.js_ in your favorite editor. This file contains a simple React component and its export to Noodl. Each component that you want to be exposed in Noodl as a visual component, must be exported.
First a simple React component:
```javascript
function MyCustomReactComponent(props) {
const style = {
color: props.textColor,
backgroundColor: props.backgroundColor,
borderRadius: '10px',
padding: '20px',
marginBottom: props.marginBottom,
}
return (
<div style={style} onClick={props.onClick}>
{props.children}
</div>
)
}
```
Next the export of the component to Noodl:
```javascript
const MyCustomReactComponentNode = Noodl.defineReactNode({
name: 'Custom React Component',
category: 'Tutorial',
getReactComponent() {
return MyCustomReactComponent
},
inputProps: {
backgroundColor: { type: 'color', default: 'white' },
marginBottom: {
type: { name: 'number', units: ['px'], defaultUnit: 'px' },
default: 10,
},
},
outputProps: {
onClick: { type: 'signal', displayName: 'Click' },
},
})
```
In addition to how you would specify a simple Noodl node, as in the introductory [guide](/javascript/extending/create-lib), you must provide the _getReactComponent_ function that retuns the React component. You may also specify _inputProps_ and _outputProps_ that map to the properties of the React node and will become inputs and outputs of your Noodl node.
Outputs in React are typically done via callbacks. You can capture these callbacks and deliver them as outputs in Noodl.
Finally the component is provided as part of your module declaration. Here you need to put it under the _reactNodes_ section to make sure Noodl recognises it as a visual node.
```javascript
Noodl.defineModule({
reactNodes: [MyCustomReactComponentNode],
nodes: [],
setup() {
//this is called once on startup
},
})
```

View File

@@ -0,0 +1,36 @@
# Module Manifest
:::note
WIP
:::
This document is all you need to know about what is required in the manifest.json file.
# React and Node Modules
```json
{
"name": "name",
"main": "index.js",
"dependencies": [
"http ..."
],
"runtimes": ["browser"]
}
```
## Iconset
```json
{
"name": "Material Icons",
"type": "iconset",
"browser":{
"stylesheets": ["https://fonts.googleapis.com/css2?family=Material+Icons"]
},
"iconClass": "material-icons",
"icons": ["10k", ...]
}
```

View File

@@ -0,0 +1,15 @@
---
hide_title: true
hide_table_of_contents: true
title: Extending Noodl
---
# Extending Noodl
One important aspect to improve productivity is extending your Noodl workspace with library modules that are tailored for your organizations needs.
[Create a new core node](/javascript/extending/create-lib)
This guide will show you how to install the Noodl CLI and create a single simple new core node in Noodl. This is a good starting point to cover before diving into the other guides.
[Create a React component](/javascript/extending/create-react-lib)
This guide will show you how to add React components to your Noodl workspace so that you can quickly build great front ends for your applications.

16
javascript/overview.md Normal file
View File

@@ -0,0 +1,16 @@
---
hide_title: true
hide_table_of_contents: true
title: Javascript API
---
# Getting Started
This section contains the Javascript API reference documentation, samples and guides on how to extend Noodl with core nodes. Most Javascript is used in your project as part of nodes, most notably the [Function](/nodes/javascript/function) and [Script](/nodes/javascript/script) nodes. You can learn more about how to use Javascript in your Noodl projects in the [Client side business logic with Javascript guide](/docs/guides/business-logic/client-side-biz-logic-js), or when writing Javascript in cloud functions [Javascript In Cloud Functions](/docs/guides/cloud-logic/javascript).
This section is devided into the following sub sections:
* **References** Here you will find the API reference documentation.
* **Samples** Here you will find small useful samples of how you can use the Javascript API.
* **Extending** Here you will find documentation on how to write your own Noodl modules and create your own core Noodl nodes.

View File

@@ -0,0 +1,40 @@
---
hide_title: true
hide_table_of_contents: true
title: Noodl.Arrays
---
# Noodl.Arrays
**Only available on the frontend**
The third part of the global data model in Noodl are arrays. Each array is reference by its **Id** using the `Noodl.Arrays`prefix, similar to objects and variables. You can learn more about arrays in the [arrays guide](/docs/guides/data/arrays). Changing an array will trigger an update of all **Array** node with the corresponding **Id**.
<div className="ndl-image-with-background xl">
![](/javascript/reference/arrays/arrays.png)
</div>
:::note
Generally arrays in Noodl are expected to contain objects. There is nothing stopping you putting other stuff in arrays but
:::
```javascript
// This will change the array with id MyArray and update all Arrays nodes
// with that id.
Noodl.Arrays.MyArray = [{ Hello: "There" }];
// Use this if you have spaces in your array id
Noodl.Arrays["Recepie List"] = [{ Name: "Fancy Burger" }];
// Reading arrays
console.log(Noodl.Arrays.MyArray);
// WARNING, you can access arrays like this but this will not trigger an update
// in Noodl. You should avoid modifying arrays like this.
Noodl.Arrays.MyArray.push({ Hello: "Again" });
// Instead, create a new array. This will trigger an update
// on all Array nodes with id MyArray
Noodl.Arrays.MyArray = Noodl.Arrays.MyArray.concat([{ Hello: "Again" }]);
```

View File

@@ -0,0 +1,19 @@
---
hide_title: true
hide_table_of_contents: true
title: Noodl.CloudFunctions
---
# Noodl.CloudFunctions
**Only available on the frontend**
The **Noodl.CloudFunctions** service lets you call Noodl cloud functions.
#### **`Noodl.CloudFunctions.run(functionName,parameters)`**
This function will call a cloud function in the backend.
```javascript
const result = await Noodl.CloudFunctions.run("myFunctionName", {
SomeParamater: "yes",
});
```

View File

@@ -0,0 +1,46 @@
---
hide_title: true
hide_table_of_contents: true
title: Component
---
# Component
**Only available on the frontend**
The `Component` object is ony available in [Function](/nodes/javascript/function) and [Script](/nodes/javascript/script) nodes and it contains things related to the component scope where the **Function** or **Script** node is executing.
`Component.Object` is the [Component Object](/nodes/component-utilities/component-object) of the current component and you can use it just like any other [Noodl.Object](/javascript/reference/object). Most commonly this means accessing the properties of the object. When you set a property any **Component Object** node in this component instance will update accordingly.
<div className="ndl-image-with-background xl">
![](/javascript/reference/component/component-object.png)
</div>
In the example above the **Function** node called _Update selection_ is modifying the **Component Object** to create a new array for **Selection**. This is done by accessing the **Checkboxes** array in the component object and filtering and mapping that array.
```javascript
Component.Object.Selection = Component.Object.Checkboxes.filter(
(o) => o.Checked
).map((o) => ({ Value: o.Value }));
```
`Component.ParentObject` is similair but this object is the [Parent Component Object](/nodes/component-utilities/parent-component-object), that is the **Component Object** of the parent component in the visual heirarchy. It is also used like any other [Noodl.Object](/javascript/reference/object).
`Component.RepeaterObject` If this component is the template of a repeater this will contain the object of the items array corresponding to this specific component instance. That is the same object as if you set an object **Id Source** to **From Repeater**, as shown below.
<div className="ndl-image-with-background l">
![](/javascript/reference/component/from-repeater-props.png)
</div>
Below is an example of such an object in a component.
<div className="ndl-image-with-background l">
![](/javascript/reference/component/repeater-object.png)
</div>
If this component is not directly the template used by a repeater but instead a sub component, you can still access the object. The object will be resolved by following the instance parent chain of components until it reaches a component that is a repeater template instance.

View File

@@ -0,0 +1,76 @@
---
hide_title: true
hide_table_of_contents: true
title: Noodl.Events
---
# Noodl.Events
**Only available on the frontend**
This is the Noodl event emitter, you can use it to receive and send events from your scripts. You can learn more about events in the [guide](/docs/guides/business-logic/events).
<div className="ndl-image-with-background xl">
![](/javascript/reference/events/events.gif)
</div>
#### **`Noodl.Events.emit(eventName, data)`**
Send an event. Works well together with _Event Receivers_.
```javascript
Noodl.Events.emit("event name", {
value: "hello",
someOtherValue: 100,
});
```
#### **`Noodl.Events.on(eventName, callback(data))`**
#### **`Noodl.Events.once(eventName, callback(data))`**
Receive an event. Works together with _Event Senders_
```javascript
Noodl.Events.on("event name", function (eventData) {
console.log(eventData.value);
});
```
#### **`Noodl.Events.off(eventName, callback(data))`**
Remove an event handler.
```javascript
function onEventName(eventData) {
console.log(eventData.value);
}
Noodl.Events.on("event name", onEventName);
Noodl.Events.off("event name", onEventName);
```
## Features
### Listen to Page Router navigation
Here is an example of how you can listen to the Page Router navigation events.
```js
Script.Outputs = {
Navigated: "signal",
};
function onNavigated({ routerName, path, title, component }) {
Script.Outputs.Navigated();
}
Script.Signals.DidMount = function () {
Noodl.Events.on("NoodlApp_Navigated", onNavigated);
};
Script.OnDestroy = function () {
Noodl.Events.off("NoodlApp_Navigated", onNavigated);
};
```

View File

@@ -0,0 +1,39 @@
---
hide_title: true
hide_table_of_contents: true
title: Noodl.Files
---
# Noodl.Files
The **Noodl.Files** service lets you access the cloud services files.
#### **`Noodl.Files.upload(file,options)`**
**Only available on the frontend**
This function will upload a file to the backend. You can specify a progress callback using the options.
<div className="ndl-image-with-background xl">
![](/javascript/reference/files/upload.png)
</div>
```javascript
const cloudFile = await Noodl.Files.upload(Inputs.File, {
onProgress: (p) => {
console.log(p.total, p.loaded);
},
});
console.log(cloudFile.name);
console.log(cloudFile.url);
```
#### **`Noodl.Files.delete(fileName)`**
**Only available in cloud functions**
This function will delete a file that has been uploaded to the backend. You need to provide the file name that was returned when the file was uploaded. So not the full `url` but the `hash+filename` returned by the upload function.
```javascript
// Can only be done in cloud functions
await Noodl.Files.delete(filename);
```

View File

@@ -0,0 +1,44 @@
---
hide_title: true
hide_table_of_contents: true
title: Noodl.Navigation
---
# Noodl.Navigation
**Only available on the frontend**
The **Noodl.Navigation** service lets you perform navigation from functions and scripts.
#### **`Noodl.Navigation.showPopup(componentPath,parameters)`**
This function will show the provided visual component as a popup.
```javascript
const result = await Noodl.Navigation.showPopup("#mysheet/mypopupcomponent", {
Message: "hello",
});
console.log(result.action); // The action used to close the popup
console.log(result.parameters); // The close parameters
```
The **parameters** are provided to the component as inputs, and must match the component input names.
#### **`Noodl.Navigation.navigate(routerName,targetPageName,parameters)`**
This function will navigate on a given page router, identified with **routerName**, to a provided page, identified with **targetPageName** (the path to the page component), and give it the parameters provided in **parameters**.
```javascript
Noodl.Navigation.navigate("Main", "#mysheet/DetailsPage", {
ObjectId: theClickedObjectId,
});
```
#### **`Noodl.Navigation.navigateToPath(path,query)`**
This function will navigate to a specific url path. You can provide query parameters as an object. The function will use the current path mode selected in the project, hash or path.
```javascript
Noodl.Navigation.navigateToPath("/main/details/" + theClickedObjectId, {
query: {
filter: true,
},
});
```

View File

@@ -0,0 +1,147 @@
---
hide_title: true
hide_table_of_contents: true
title: Noodl.Object
---
# Noodl.Object
Allows access to [Object](/nodes/data/object/object-node) from Javascript. You can learn more about objects and how you use them in your Noodl applications [here](/docs/guides/data/objects).
#### **`Noodl.Object.get(id)`**
Returns the object with the specified id. This function will return a new empty object if the id is
not previously used. If you want to check if an object exists use the `Noodl.Object.exists` function.
This is the same as accessing the object through `Noodl.Objects`.
#### **`Noodl.Object.create(data)`**
This function will create a new object and return it.
The properties of the object will be that of the supplied data. A special case is
the id attribute that will become the id of the object and not part of the object properties.
E.g. the code below will create an object with two properties and the id set to 'A'.
```javascript
Noodl.Object.create({
id: "A",
myProp1: 10,
myProp2: "Hello",
myProp3: Noodl.Object.create({ anotherProp: 15 }),
});
```
If no id is provided the newly created object will get a unique id assigned.
As illustrated in the example above object properties can contain references to other objects.
#### **`Noodl.Object.exists(id)`**
Returns true if an object with the specified Id has been created with a call to Noodl.Object.get or Noodl.Object.create.
#### **`object.on(event,listener)`**
#### **`object.off(event,listener)`**
Add and remove an event listener from the object.
Supported events:
- `change` - any property of the object is changed
- `add` - the object is added to a array
- `remove` - the object is removed from a array.
Example usage:
```javascript
myObject.on("change", function (args) {
// property with name args.name was changed
// new value in args.value
// old value in args.old
});
myObject.on("add", function (args) {
// object was added to the array args.array
});
myObject.on("remove", function (args) {
// object was removed from the array args.array
});
```
#### **`object.getId()`**
Returns the Id of the object.
#### **`object.set(name,value,options)`**
Sets a property of the object. The name and value of the property should be provided to the function.
`myObject.set('myProp1',44)`
This is equal to just setting the property of the object directly:
`myObject.myProp1 = 44`
Optionally, dot notation can be used to set a sub property.
If a property of the object is another object, then you can set a property of the object
e.g. `myProp3.anotherProp`. To enable this you must supply the options `{resolve:true}`.
`myObject.set('myProp3.anotherProp',50,{resolve:true})`
#### **`object.setAll(data)`**
This function performs a set on all properties of the specified object.
This is equal to calling set for all properties of the data object.
Updating the `id` property is not supported, and will be ignored.
```js
// Create a Noodl object
Noodl.Object.create({
id: "unique",
valueA: 1,
valueB: 2,
});
// This object, Noodl.Objects["unique"]
// will now contain {valueA: 1, valueB: 2}
// When calling setAll
Noodl.Objects["unique"].setAll({
valueA: 3,
});
// The object will now be:
// {valueA: 3, valueB: 2}
```
#### **`object.fill(value)`**
This function sets all the current properties to the new value, except of the id.
```js
// Create a Noodl object
Noodl.Object.create({
id: "unique",
valueA: 1,
valueB: 2,
});
// This object, Noodl.Objects["unique"]
// will now contain {valueA: 1, valueB: 2}
// When calling setAll
Noodl.Objects["unique"].fill(5);
// The object will now be:
// {valueA: 5, valueB: 5}
```
#### **`object.get(name,options)`**
Returns the value of the property with the specified name.
As in the set function the dot notation can be used if the object has another object as a property,
if the options {resolve:true} is supplied.
`myObject.get('myProp3.anotherProp',{resolve:true})`
This is equal to reading the property directly:
`myObject.myProp3.anotherProp`

View File

@@ -0,0 +1,41 @@
---
hide_title: true
hide_table_of_contents: true
title: Noodl.Objects
---
# Noodl.Objects
One step above **Variable**s are **Object**s,
this is a global data model of Noodl objects.
Each object is referenced with an **Id** and can contain a set of properties.
You can access alla objects in your workspace through their **Id** and the `Noodl.Objects` prefix.
Change a property of an object will trigger all connections from object nodes with the corresponding **Id** and property.
You can learn more about objects and how you use them in your Noodl applications [here](/docs/guides/data/objects).
<div className="ndl-image-with-background xl">
![](/javascript/reference/objects/objects.png)
</div>
```javascript
// This will change the property MyProperty
// of object with id MyObjectId and trigger
// all object nodes (with that id) in your project
Noodl.Objects.MyObjectId.MyProperty = "Hello";
// Use this notation of that object id contains spaces
Noodl.Objects["Form Values"].input_text = "Whoops";
Noodl.Objects["Form Values"]["A property with spaces"] = 20;
// Reading an object property
console.log(Noodl.Objects.CurrentUser.Name);
// This will set all properties of the object you assign with
// to the object with id "SomeId"
// You cannot set the id property this way,
// that property will be ignored if part of the object you assign
Noodl.Objects.SomeId = { ... }
```

View File

@@ -0,0 +1,15 @@
---
hide_title: true
hide_table_of_contents: true
title: Reference Documentation
---
# Reference Documentation
Here you will find the reference documentation for the Noodl Javascript API that you can use in [Function](/nodes/javascript/function) and [Script](/nodes/javascript/script) nodes but also in other nodes such as the [REST](/nodes/data/rest) node, or when creating your own modules and code Noodl nodes.
You can always reach the API using the `Noodl.` prefix, such as:
```javascript
Noodl.Variables.MyVariable = 10;
```

View File

@@ -0,0 +1,313 @@
---
hide_title: true
hide_table_of_contents: true
title: Noodl.Records
---
# Noodl.Records
With **Records** you can query, read and write records to the cloud database. All functions are **async** and will throw an exception if they fail.
```javascript
try {
await Noodl.Records.delete(myRecord);
} catch (e) {
console.log(e);
}
```
#### **`Noodl.Records.query(className,query,options)`**
This is an **async** function that will query the database using the query that you provide and return the result or throw an exception if failed. The **query** parameter has the same format as the [advanced query](/nodes/data/cloud-data/query-records#advanced-filters) of the **Query Records** node.
```javascript
const results = await Noodl.Records.query("myClass", {
Completed: { equalTo: true },
});
```
The result is an array of **Noodl.Object**. The **options** can be used to specify sorting, it also follows the same pattern as the advanced filters of the **Query Records** node.
```javascript
const results = await Noodl.Records.query(
"myClass",
{
Completed: { equalTo: true },
},
{
sort: ["createdAt"],
}
);
```
You can also specify the limit for how many records to return as a maximum (defaults to 100) with the **limit** option, and if you want the returned records to start from a given index specify the **skip** option.
```javascript
const results = await Noodl.Records.query(
"myClass",
{
Completed: { equalTo: true },
},
{
sort: ["-createdAt"], // use - to sort descending
skip: 50,
limit: 200,
}
);
```
If you are limiting your results or if you have more records that the maximum allowed returned count then you might want to know the total record count to enable pagination. You can do this with the **count** option. Note that this changes the returned value to an object with **results** and **count**.
```javascript
const res = await Noodl.Records.query(
"myClass",
{
Completed: { equalTo: true },
},
{
sort: ["-createdAt"], // use - to sort descending
skip: 50,
limit: 200,
count:true,
}
);
console.log(res.results); // This is the array with results
console.log(res.count); // This is the total count that matches the query
```
If you have properties in the returned records that are pointers they will by default returned as a string containing the **Id** of the record that they are pointing to (if any). You can hovever choose to include the entire record by using the `include` option. In the example below immagine that we have a property that is a pointer called **Customer** that points to a customer record. By using the `include` options as shown below the returned records will now include the entire object and all properties instead of just the pointer value.
```javascript
const results = await Noodl.Records.query(
"myClass",
{},
{
include: "Customer",
}
);
```
You can also use dot notation to recursively include points wihtin included objects. In the example below we also include records pointed to by the `Author` property and within those objects also any records pointed to by the `Location` property.
```javascript
const results = await Noodl.Records.query(
"myClass",
{},
{
include: "Customer,Author.Location",
}
);
```
By default all properties are returned for all records in the result. This is sometimes not the preferred way since it can result in big responses if you are only looking for a few properties. You can use the `select` option to specify the names of the properties you want included in the result. The below example will only return the `Title`and `Body` properties in the result.
```javascript
const results = await Noodl.Records.query(
"myClass",
{
Completed: { equalTo: true },
},
{
select: "Title,Body",
}
);
```
:::note
One important note, even though only a select few properties are returned from the cloud service they are merged with any existing properties in the record data model that have previously been fetched. Since all record objects are global to the Noodl application.
:::
#### **`Noodl.Records.count(className,query)`**
This function returns the count of the number of records of a given class, optionally matching a query filter.
```javascript
// The number of records of myClass in the database
const count = await Noodl.Records.count("myClass");
// The number of myClass records in the database that match a query
const completedCount = await Noodl.Records.count("myClass", {
Completed: { equalTo: true },
});
```
#### **`Noodl.Records.distinct(className,property,query)`**
**Only available in cloud functions**
This function returns an array of unique values for a given propery or all records in the database of a given class. Optionally you can suppoly a query filter.
```javascript
// The unique values of the "category" property of all records of myClass in the database.
const categories = await Noodl.Records.distinct("myClass", "category");
// The unique values of the "category" property of all records of myClass in the database that matches the query, e.g. that have the property Completed set to true.
const completedCount = await Noodl.Records.distinct("myClass", "category", {
Completed: { equalTo: true },
});
```
#### **`Noodl.Records.fetch(objectOrId,options)`**
Use this function to fetch the latest properties of a specific record from the cloud database. It will return the object / record.
```javascript
// If you use the a record ID you must also specify the class
const myRecord = await Noodl.Records.fetch(myRecordId, {
className: "myClass",
});
// You can also fetch a record you have previously fetched or received from a
// query, to get the latest properties from the backend
await Noodl.Records.fetch(myRecord);
```
By default fetch will return pointer properties as the string **Id** of the object pointed to. But you can use the `include` option to specify that you want the content of the object to be returned instead.
```javascript
// By using include the request will return the pointed to object with all properties instead of
// just the string Id
await Noodl.Records.fetch(myRecord,{
include:["Customer","Author"]
});
```
#### **`Noodl.Records.save(objectOrId,properties,options)`**
Use this function to write an existing record to the cloud database. It will attempt to save all properties of the record / object if you don't specify the optional properties argument, if so it will set and save those properties.
```javascript
Noodl.Objects[myRecordId].SomeProperty = "hello";
// If you use the record id to save, you need to specify the classname explicitly
// by specfiying null or undefinded for properties it will save all proporties in
// the record
await Noodl.Records.save(myRecordId, null, { className: "myClass" });
// Or use the object directly
await Noodl.Records.save(Noodl.Objects[myRecordId]);
// Set specified properties and save only those to the backned
await Noodl.Records.save(myRecord, {
SomeProperty: "hello",
});
```
#### **`Noodl.Records.increment(objectOrId,properties,options)`**
This function will increment (or decrease) propertis of a certain record saving it to the cloud database in a race condition safe way. That is, normally you would have to first read the current value, modify it and save it to the database. Here you can do it with one operation.
```javascript
// Modify the specified numbers in the cloud
await Noodl.Records.increment(myRecord, {
Score: 10,
Life: -1,
});
// Like save, you can use a record Id and class
await Noodl.Records.save(myRecordId, { Likes: 1 }, { className: "myClass" });
```
Using the options you can also specify access control as described in this [guide](/docs/guides/cloud-data/access-control), this let's you control which users can access a specific record. The access control is specified as below:
```javascript
await Noodl.Records.save(myRecord, null, {
acl: {
"*": { read: true, write: false }, // "*" means everyone, this rules gives everyone read access but not write
"a-user-id": { read: true, write: true }, // give a specific user write access
"role:a-role-name": { read: true, write: true }, // give a specific role write access
},
});
```
#### **`Noodl.Records.create(className,properties,options)`**
This function will create a new record in the cloud database and return the **Noodl.Object** of the newly created record. If it's unsuccessful it will throw an exception.
```javascript
const myNewRecord = await Noodl.Records.create("myClass", {
SomeProperty: "Hello",
});
console.log(myNewRecord.SomeProperty);
```
You can use the **options** agrument to specify access control rules as detailed under **Noodl.Records.save** above.
#### **`Noodl.Records.delete(objectOrId,options)`**
Use this function to delete an existing record from the cloud database.
```javascript
// If you specify the id of a record to be deleted, you must also provide the
// class name in the options
await Noodl.Records.delete(myRecordId, { className: "myClass" });
// Or use the object directly (provided it was previously fetched or received via a query)
await Noodl.Records.delete(Noodl.Objects[myRecordId]);
```
#### **`Noodl.Records.addRelation(options)`**
Use this function to add a relation between two records.
```javascript
// You can either specify the Ids and classes directly
await Noodl.Records.addRelation({
className: "myClass",
recordId: "owning-record-id",
key: "the-relation-key-on-the-owning-record",
targetRecordId: "the-id-of-the-record-to-add-a-relation-to",
targetClassName: "the-class-of-the-target-record",
});
// Or if you already have two records that have been previously fetched or returned from a
// query
await Noodl.Records.addRelation({
record: myRecord,
key: "relation-key",
targetRecord: theTargetRecord,
});
```
#### **`Noodl.Records.removeRelation(options)`**
Use this function to remove a relation between two records.
```javascript
// You can either specify the Ids and classes directly
await Noodl.Records.removeRelation({
className: "myClass",
recordId: "owning-record-id",
key: "the-relation-key-on-the-owning-record",
targetRecordId: "the-id-of-the-record-to-remove-a-relation-to",
targetClassName: "the-class-of-the-target-record",
});
// Or if you already have two records that have been previously fetched or returned from a
// query
await Noodl.Records.removeRelation({
record: myRecord,
key: "relation-key",
targetRecord: theTargetRecord,
});
```
#### **`Noodl.Records.aggregate(className,aggregates,query)`**
**Only available in cloud functions**
This function will compute a set of aggregates based on properties in the records. It can be limited with a query. You can use the following aggregate functions:
- `sum` Compute the sum of a number property access matching records.
- `min` Compute the minimum value of a number property access matching records.
- `max` Compute the maximum value of a number property access matching records.
- `avg` Compute the average value of a number property access matching records.
```javascript
// This will compute two averages on all records of myClass, TotalCost will be the sum of all Cost properties, and AverageSalary will be the average of all Salary properties
const categories = await Noodl.Records.aggregate("myClass", {
TotalCost: { sum: "Cost" },
AverageSalary: { avg: "Salary" },
});
// This will compute and return the maximum and minimum age for records with Category equal to Remote
const completedCount = await Noodl.Records.aggregate(
"myClass",
{
MinAge: { min: "Age" },
MaxAge: { max: "Age" },
},
{
Category: { equalTo: "Remote" },
}
);
```

View File

@@ -0,0 +1,155 @@
---
hide_title: true
hide_table_of_contents: true
title: Noodl.Users
---
# Noodl.Users
The **Noodl.Users** object let's you access the current session user.
#### **`Noodl.Users.logIn(options)`**
This function will attempt to login to create a user session. After a successful login you can access the user object with `Noodl.Users.Current`
```javascript
// On the frontend you log in and access the user via Noodl.Users.Current
await Noodl.Users.logIn({
username: "my-username",
password: "my-password",
});
console.log(Noodl.Users.Current.UserId);
// When running in a cloud function it also returns the user object
const user = await Noodl.Users.logIn({
username: "my-username",
password: "my-password",
});
console.log(user.sessionToken);
```
#### **`Noodl.Users.signUp(options)`**
**Only available on the frontend**
This function will attempt to sign up a new user, and if successful that user will become the current user session. Username, email and password are required options and properties is optional.
```javascript
await Noodl.Users.signUp({
username: "my-username",
email: "my-email",
password: "my-password",
properties: {
SomeProperty: true,
},
});
```
#### **`Noodl.Users.become(sessionToken)`**
**Only available on the frontend**
This function will attempt to login a user with an existing session token. Session tokens can be created in cloud functions e.g. using the `impersonate` function. After a successful login you can access the user object with `Noodl.Users.Current`
```javascript
// Call this from a function node with Inputs.SessionToken
try {
await Noodl.Users.become(Inputs.SessionToken);
} catch (e) {
Outputs.Failure();
throw e;
}
// You can now access the user
const userId = Noodl.Users.Current.UserId;
Outputs.Success();
```
#### **`Noodl.Users.impersonate(username,options)`**
**Only available in cloud functions**
With this function you can get a session token for a user that you can later send to the client to log that user in. This does not require a password and must be run on a cloud function (since they all have full access to the database). You can provide a duration for the session, or it will expire after 24 hours as default. If successful this call will return a user object that contains a session token that you can return to the client and use with the `become` function.
**installationId** is an optional that is a unique id for the client if you don't want to share sessions between different clients. Most common is to generate a random id on the client and pass to the cloud function when you are logging in.
```javascript
try {
const user = await Noodl.Users.impersonate("test@email.com", {
duration: 1 * 60 * 60 * 1000, // have the session last 1 hour
installationID: "xyz",
});
Outputs.SessionToken = user.sessionToken;
Outputs.Success();
} catch (e) {
Outputs.Failure();
throw e;
}
```
#### **`Noodl.Users.Current`**
This function will return the current user object and properties if one exists.
```javascript
const user = Noodl.Users.Current;
if (user) {
// A user is logged in
console.log(user.UserId); // the user id of the current user
console.log(user.Properties.SomeProperty);
} else {
// No user session
}
```
#### **`Noodl.Users.Current.fetch()`**
This function will fetch that laters properties of the user object from the cloud database. It will throw an exception if the user session has expired.
```javascript
await Noodl.Users.Current.fetch();
```
#### **`Noodl.Users.Current.save()`**
This function will attempt to save the properties of the current user. If you have made changes to the **current()** user object you will need to call this function to write the changes to the backend.
If the `password` has been updated it will terminate all the sessions so the user has to login again.
```javascript
await Noodl.Users.Current.save();
```
#### **`Noodl.Users.Current.logOut()`**
**Only available on the frontend**
This function will log out the current user and terminate the session.
```javascript
await Noodl.Users.Current.logOut();
```
#### **`Noodl.Users.on(eventName,callback)`**
**Only available on the frontend**
You can use this function to listen for events related to the user service.
```javascript
Noodl.Users.on("sessionLost", () => {
// This is called when the session has expired
});
Noodl.Users.on("loggedIn", () => {
// This is called when a user has successfully logged in
});
Noodl.Users.on("loggedOut", () => {
// This is called when a user has successfully logged out
});
```
#### **`Noodl.Users.off(eventName,callback)`**
**Only available on the frontend**
You use this function to remove an event listener from a specific event.

View File

@@ -0,0 +1,29 @@
---
hide_title: true
hide_table_of_contents: true
title: Noodl.Variables
---
# Noodl.Variables
Variables are the simplest form of global data model in Noodl. You can learn more about variables in the [guide](/docs/guides/data/variables). You can access all variables in your application trough the `Noodl.Variables` object. Changing a variable will trigger all connections to be updated for all **Variable** nodes in your project with the corresponding variable name.
<div className="ndl-image-with-background xl">
![](/javascript/reference/variables/variables.png)
</div>
```javascript
// This will change the variable named MyVariable
// and trigger all variable nodes in your project
Noodl.Variables.MyVariable = "Hello";
// Use this if you have spaces in your variable name
Noodl.Variables["My Variable"] = 10;
Noodl.Variables.userName = "Mickeeeey";
// Reading variables
console.log(Noodl.Variables.userName);
```

View File

@@ -0,0 +1,47 @@
---
hide_title: true
hide_table_of_contents: true
title: Get DOM element
---
import CopyToClipboardButton from '/src/components/copytoclipboardbutton'
# Get DOM element
<div class="ndl-image-with-background xl">
<CopyToClipboardButton json={{"nodes":[{"id":"da9a319e-46ae-0bba-f9f0-64115fd8326a","type":"Group","x":-26,"y":-78.00000000000003,"parameters":{},"ports":[],"children":[]},{"id":"432255e5-cf33-ab78-5dfc-c468fa2d35f0","type":"Javascript2","label":"Get DOM Element","x":-266,"y":-87,"parameters":{"code":"Script.Inputs = {\n group:\"reference\"\n}\n\nScript.Signals = {\n didMount() {\n const domElement = Script.Inputs.group.getDOMElement();\n //domElement.addEventListener(...)\n },\n willUnmount() {\n // const domElement = Script.Inputs.group.getDOMElement();\n // domElement.removeEventListener(...)\n }\n}\n","scriptInputs":[]},"ports":[],"children":[]}],"connections":[{"fromId":"da9a319e-46ae-0bba-f9f0-64115fd8326a","fromProperty":"didMount","toId":"432255e5-cf33-ab78-5dfc-c468fa2d35f0","toProperty":"didMount"},{"fromId":"da9a319e-46ae-0bba-f9f0-64115fd8326a","fromProperty":"willUnmount","toId":"432255e5-cf33-ab78-5dfc-c468fa2d35f0","toProperty":"willUnmount"},{"fromId":"da9a319e-46ae-0bba-f9f0-64115fd8326a","fromProperty":"this","toId":"432255e5-cf33-ab78-5dfc-c468fa2d35f0","toProperty":"group"}]}} />
![](/javascript/samples/get-dom-element/get-dom-element.png)
</div>
Sometimes you need to get access to the underlying DOM element and use the browser APIs directly.
To get from a visual Noodl node to a DOM element we'll connect a visual node to a Script node. The input type should be `"reference"` (or `"*"` to accept all types). This example uses a Group, but any visual node will work.
```js
Script.Inputs = {
group: 'reference',
}
```
Once we have the reference to a Noodl node we can get the DOM element reference with `getDOMElement()` on the Noodl node.
A visual node might be unmonted, or haven't had a chance to render yet. To make sure there's a DOM element we'll use the **Did Mount** output from the Noodl node. This makes sure we'll get the latest DOM reference, and that our code won't run until the visual node is actually rendered.
```js
Script.Inputs = {
group: 'reference',
}
Script.Signals = {
didMount() {
const domElement = Script.Inputs.group.getDOMElement()
//domElement.addEventListener(...)
},
willUnmount() {
// const domElement = Script.Inputs.group.getDOMElement();
// domElement.removeEventListener(...)
},
}
```

View File

@@ -0,0 +1,60 @@
---
hide_title: true
hide_table_of_contents: true
title: Pointer position example
---
import CopyToClipboardButton from '/src/components/copytoclipboardbutton'
# Pointer position example
<div class="ndl-image-with-background xl">
<CopyToClipboardButton json={{"nodes":[{"id":"0f09b7cb-50ab-74b8-52c1-7058a0769284","type":"Javascript2","label":"Pointer Position","x":-497.981910869314,"y":389.26538248207396,"parameters":{"code":"function setPosition(e) {\n Script.Outputs.PointerX = e.clientX;\n Script.Outputs.PointerY = e.clientY;\n}\n\nScript.OnInit = function() {\n document.body.addEventListener(\"mousemove\", setPosition);\n\t document.body.addEventListener(\"mousedown\", setPosition);\n}\n\nScript.OnDestroy = function() {\n\t document.body.removeEventListener(\"mousedown\", setPosition);\n\t document.body.removeEventListener(\"mousemove\", setPosition); \n}"},"ports":[],"children":[]},{"id":"bdc3ce12-5c23-27ee-7918-ad154b20e668","type":"Number","label":"X","x":-249.18868850167155,"y":333.4616732156818,"parameters":{},"ports":[],"children":[]},{"id":"efbbb196-1b60-27f2-26c6-e7fc0d69d23d","type":"Number","label":"Y","x":-256.3223936828915,"y":481.4925282575018,"parameters":{},"ports":[],"children":[]}],"connections":[{"fromId":"0f09b7cb-50ab-74b8-52c1-7058a0769284","fromProperty":"PointerX","toId":"bdc3ce12-5c23-27ee-7918-ad154b20e668","toProperty":"value"},{"fromId":"0f09b7cb-50ab-74b8-52c1-7058a0769284","fromProperty":"PointerY","toId":"efbbb196-1b60-27f2-26c6-e7fc0d69d23d","toProperty":"value"}]}} />
![](/javascript/samples/pointer-position/pointer-position.png)
</div>
This example will attach an event listener to the body element of the web page, and listen for `mousemove`.
```js
Script.OnInit = function () {
document.body.addEventListener('mousemove', (e) => {
Script.Outputs.PointerX = e.clientX
Script.Outputs.PointerY = e.clientY
})
}
```
Now lets extend this to also include the `mousedown` event, so it updates directly when a pointer event starts, not just when it moves. Since we need the same code twice we can add it to a function.
```js
function setPosition(e) {
Script.Outputs.PointerX = e.clientX
Script.Outputs.PointerY = e.clientY
}
Script.OnInit = function () {
document.body.addEventListener('mousemove', setPosition)
document.body.addEventListener('mousedown', setPosition)
}
```
We can extend this even further by removing the event listener when the JavaScript node is destroyed. This can happen when a user navigates from a page that runs this code, or when a component with this code is generated by a **Repeater** node.
```js
function setPosition(e) {
Script.Outputs.PointerX = e.clientX
Script.Outputs.PointerY = e.clientY
}
Script.OnInit = function () {
document.body.addEventListener('mousemove', setPosition)
document.body.addEventListener('mousedown', setPosition)
}
Script.OnDestroy = function () {
document.body.removeEventListener('mousedown', setPosition)
document.body.removeEventListener('mousemove', setPosition)
}
```