mirror of
https://github.com/noodlapp/noodl-docs.git
synced 2026-01-11 23:02:54 +01:00
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:
@@ -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");
|
||||
});
|
||||
},
|
||||
};
|
||||
```
|
||||
147
javascript/extending/build-script/overview.md
Normal file
147
javascript/extending/build-script/overview.md
Normal 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;
|
||||
```
|
||||
196
javascript/extending/build-script/sitemap-and-seo.md
Normal file
196
javascript/extending/build-script/sitemap-and-seo.md
Normal 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);
|
||||
}
|
||||
```
|
||||
161
javascript/extending/create-lib.md
Normal file
161
javascript/extending/create-lib.md
Normal 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">
|
||||
|
||||

|
||||
|
||||
</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.
|
||||
117
javascript/extending/create-react-lib.md
Normal file
117
javascript/extending/create-react-lib.md
Normal 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
|
||||
},
|
||||
})
|
||||
```
|
||||
36
javascript/extending/module/manifest.md
Normal file
36
javascript/extending/module/manifest.md
Normal 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", ...]
|
||||
}
|
||||
```
|
||||
15
javascript/extending/overview.md
Normal file
15
javascript/extending/overview.md
Normal 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
16
javascript/overview.md
Normal 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.
|
||||
|
||||
40
javascript/reference/arrays.md
Normal file
40
javascript/reference/arrays.md
Normal 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">
|
||||
|
||||

|
||||
|
||||
</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" }]);
|
||||
```
|
||||
19
javascript/reference/cloudfunctions.md
Normal file
19
javascript/reference/cloudfunctions.md
Normal 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",
|
||||
});
|
||||
```
|
||||
46
javascript/reference/component.md
Normal file
46
javascript/reference/component.md
Normal 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">
|
||||
|
||||

|
||||
|
||||
</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">
|
||||
|
||||

|
||||
|
||||
</div>
|
||||
|
||||
Below is an example of such an object in a component.
|
||||
|
||||
<div className="ndl-image-with-background l">
|
||||
|
||||

|
||||
|
||||
</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.
|
||||
76
javascript/reference/events.md
Normal file
76
javascript/reference/events.md
Normal 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">
|
||||
|
||||

|
||||
|
||||
</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);
|
||||
};
|
||||
```
|
||||
39
javascript/reference/files.md
Normal file
39
javascript/reference/files.md
Normal 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">
|
||||
|
||||

|
||||
|
||||
</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);
|
||||
```
|
||||
44
javascript/reference/navigation.md
Normal file
44
javascript/reference/navigation.md
Normal 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,
|
||||
},
|
||||
});
|
||||
```
|
||||
147
javascript/reference/object.md
Normal file
147
javascript/reference/object.md
Normal 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`
|
||||
41
javascript/reference/objects.md
Normal file
41
javascript/reference/objects.md
Normal 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">
|
||||
|
||||

|
||||
|
||||
</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 = { ... }
|
||||
```
|
||||
15
javascript/reference/overview.md
Normal file
15
javascript/reference/overview.md
Normal 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;
|
||||
```
|
||||
313
javascript/reference/records.md
Normal file
313
javascript/reference/records.md
Normal 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" },
|
||||
}
|
||||
);
|
||||
```
|
||||
155
javascript/reference/users.md
Normal file
155
javascript/reference/users.md
Normal 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.
|
||||
29
javascript/reference/variables.md
Normal file
29
javascript/reference/variables.md
Normal 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">
|
||||
|
||||

|
||||
|
||||
</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);
|
||||
```
|
||||
47
javascript/samples/get-dom-element.mdx
Normal file
47
javascript/samples/get-dom-element.mdx
Normal 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"}]}} />
|
||||
|
||||

|
||||
|
||||
</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(...)
|
||||
},
|
||||
}
|
||||
```
|
||||
60
javascript/samples/pointer-position.md
Normal file
60
javascript/samples/pointer-position.md
Normal 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"}]}} />
|
||||
|
||||

|
||||
</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)
|
||||
}
|
||||
```
|
||||
Reference in New Issue
Block a user