{"id":"docs/getting-started","section":"docs","kind":"guide","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/docs/getting-started/","markdown_url":"https://forge.totaljs.es/en/docs/getting-started.md","tags":["guide","install","first app"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"40d133372a5663e7be3d49ab546ad4ff21aaa3b1ff7ac6d2aff0a654635128cd","checked_at":"2026-10-20T12:17:23.435Z","framework":"total5@0.0.19","node":"v24.14.1","browser":null,"test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Get started with Total.js 5","description":"Install Total.js 5, create the start script and the first route, action and view, and run the application.","messages":[{"role":"user","content":"Get started with Total.js 5\n\nInstall Total.js 5, create the start script and the first route, action and view, and run the application."},{"role":"assistant","content":"Total.js 5 is the npm package `total5`. You need Node.js (the examples of this site run on Node.js 24) and an empty folder. Create the project and install the package:\n\n```bash\nmkdir hello && cd hello\nnpm init -y\nnpm install total5\n```\n\nThe start script is a file named `index.js` in the root of the project. It loads the framework and starts the server:\n\n```js\nrequire('total5');\n\nTotal.run({ port: 8000 });\n```\n\nThat is all the code you write to start. The files in the folders of the project (see the project structure page) are loaded automatically. Create these three files.\n\nA **controller** declares the routes. `ROUTE('GET /')` with no handler would show the view with the same name; here the handler renders `views/index.html` with a model:\n\n```js controllers/index.js\nexports.install = function() {\n\tROUTE('GET /', home);\n\tROUTE('POST /greet/ --> *Greet/say');\n};\n\nfunction home($) {\n\t$.view('index', { title: 'Hello Total.js' });\n}\n```\n\nAn **action** has its own validation. The route above runs `Greet/say` with the JSON body of the request, and the framework rejects a body that does not match `input`:\n\n```js definitions/greet.js\nNEWACTION('Greet/say', {\n\tname: 'Say hello',\n\tinput: '*name:String',\n\taction: function($, model) {\n\t\t$.callback({ message: 'Hello, ' + model.name + '!' });\n\t}\n});\n```\n\nA **view** is HTML with `@{...}` blocks. `views/layout.html` wraps every view and `@{body}` is the place of the view:\n\n```html views/layout.html\n<!DOCTYPE html>\n<html>\n<head><meta charset=\"utf-8\" /><title>Hello</title></head>\n<body>@{body}</body>\n</html>\n```\n\n```html views/index.html\n<h1>@{model.title}</h1>\n```\n\nStart the application with `node index.js`. In `debug` mode (the default) the application restarts when you change a file and the address is printed in the terminal. To run it like on a server use `node index.js --release`, and read the `release` mode page for what changes."}],"knowledge":{"content_sha256":"5f7bf99c3dc733b1a14f49c26a40d678238c8c9e2e60d732ca14763a7eab43e7","source_sha256":"40d133372a5663e7be3d49ab546ad4ff21aaa3b1ff7ac6d2aff0a654635128cd","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["DOCTYPE","GET","HTML","JSON","NEWACTION","POST","ROUTE","Total.js","Total.run"],"language":"en"},"files":[{"path":"controllers/index.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /', home);\n\tROUTE('POST /greet/ --> *Greet/say');\n};\n\nfunction home($) {\n\t$.view('index', { title: 'Hello Total.js' });\n}"},{"path":"definitions/greet.js","language":"js","content":"NEWACTION('Greet/say', {\n\tname: 'Say hello',\n\tinput: '*name:String',\n\taction: function($, model) {\n\t\t$.callback({ message: 'Hello, ' + model.name + '!' });\n\t}\n});"},{"path":"views/layout.html","language":"html","content":"<!DOCTYPE html>\n<html>\n<head><meta charset=\"utf-8\" /><title>Hello</title></head>\n<body>@{body}</body>\n</html>"},{"path":"views/index.html","language":"html","content":"<h1>@{model.title}</h1>"}],"test":"const page = await get('/');\nassert(page.status === 200 && /<h1>/.test(page.body), 'the view is rendered with the layout, got: ' + page.status + ' ' + page.body);\n\nconst ok = await post('/greet/', { name: 'Louis' });\nassert(ok.status === 200 && ok.json && ok.json.message === 'Hello, Louis!', 'the action answers, got: ' + ok.status + ' ' + ok.body);\n\nconst invalid = await post('/greet/', {});\nassert(invalid.status === 400, 'a body without the required field is rejected, got: ' + invalid.status);","test_kind":"http"}
{"id":"docs/consulting-forge","section":"docs","kind":"guide","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/docs/consulting-forge/","markdown_url":"https://forge.totaljs.es/en/docs/consulting-forge.md","tags":["guide","source","knowledge","mcp","search","context"],"license":"CC-BY-4.0","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"Consult Forge with versions, sources and complete reads","description":"A reliable search, read and source workflow for Total.js 5 and jComponent 20; coverage and evidence are explicit.","messages":[{"role":"user","content":"Consult Forge with versions, sources and complete reads\n\nA reliable search, read and source workflow for Total.js 5 and jComponent 20; coverage and evidence are explicit."},{"role":"assistant","content":"## Start with the product and version\n\nForge documents backend Total.js 5 and frontend jComponent 20. They share names such as `DEF`, `GET`, `CACHE` and `ON` but run in different environments. Choose the product before applying a contract. The installed backend runtime is `total5@0.0.19`; the pinned framework development reference declares `0.0.20-1`. An upstream source reference is not a test result for the installed package.\n\nRead `/knowledge-sources.json` for exact commits and SHA-256 fingerprints, `/knowledge-coverage.json` for declaration and component coverage, and `/knowledge-licenses.json` for preserved upstream license notices. `/knowledge.json` describes the current published documents, their actual languages, hashes and stable sections. The library version alone is insufficient to identify a jComponent bundle; retain its source fingerprint.\n\n## Search, then read\n\nUse `Forge_search` with an exact symbol such as `Controller.prototype.json`, `DEF.fallback` or `Component.prototype.setter`, or a short English/Spanish task query. Search includes the public document body. Matching excerpts help select a page; they are not the whole page and are not evidence that an example was executed. The response includes the actual content language; an untranslated page remains English.\n\nCall `Forge_page` with the returned section and slug. Pass its `content_sha256` to refuse a changed page. To read just the matching section, pass its `anchor`. If `next_cursor` is present, continue with that cursor until it is null. Cursors bind the document, language, content hash and pinned source snapshot. If the page changes, restart the read. The old `offset` field remains available for existing clients, but a cursor provides revision checking.\n\nCite the page URL, its section anchor and the content hash used. A page found by search has not yet been read. A large page returned in parts has not been fully read until continuation finishes.\n\n## Resolve a contract in the original implementation\n\nUse `Forge_coverage` to resolve an exact symbol and identify its source id and file. Use `Forge_source` to read that file verbatim. Its `path` must match a text file in the pinned source manifest. Source reads are paginated and hash checked. The same bounded readers are available over HTTP at `/api/knowledge-search/`, `/api/knowledge-page/` and `/api/knowledge-source/` for clients that do not use MCP.\n\nSources include the installed runtime, pinned upstream framework, jComponent, components, and AIContext guidance. `aicontext` is architectural guidance, not proof that every example matches the installed runtime. Read a matching implementation and recipe before adopting a signature, lifecycle assumption or dependency. Do not execute a source excerpt as a standalone example.\n\nUse `Forge_test` with the page section and slug to get its example files and the command for running them."}],"knowledge":{"content_sha256":"bc42083bd8e07eb1d3da1da6cbf27c7c425ea21348b2d6fa5badf1df7e2169f6","source_sha256":"f73c584636bfe292bf6b01a94caa21ee093e48812c55f2ca8287b13adb3cff16","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"reviewed-workflow","symbols":["AIC","CACHE","Component.prototype.setter","Controller.prototype.json","DEF","GET","HTTP","MCP","SHA","Total.js","URL"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"docs/project-structure","section":"docs","kind":"guide","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/docs/project-structure/","markdown_url":"https://forge.totaljs.es/en/docs/project-structure.md","tags":["guide","structure","folders"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"dd17157e0d15db978ea3bc5a5be35f23cbc0e415a87c205e7007cebd0aa1b282","checked_at":"2026-10-20T12:17:27.110Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Project structure of a Total.js 5 application","description":"The folders of a Total.js 5 project, what each one contains and which files the framework loads automatically.","messages":[{"role":"user","content":"Project structure of a Total.js 5 application\n\nThe folders of a Total.js 5 project, what each one contains and which files the framework loads automatically."},{"role":"assistant","content":"A Total.js application is a folder with a start script (`index.js`) and a set of folders with fixed names. **You do not import your files**: when the application starts, Total.js reads the folders below and loads every `.js` file in them (in a folder called `plugins/` it loads the `index.js` of every subfolder). A file is a normal Node.js module; if it exports an `install` function, Total.js calls it after loading the file.\n\n| Folder | What goes in it |\n|---|---|\n| `controllers/` | Routes: `exports.install = function() { ROUTE(...) }`. |\n| `definitions/` | Code that runs at start and declares global things: actions with `NEWACTION()`, middleware, `AUTH()`, helpers, your own globals. |\n| `actions/` | Actions (`NEWACTION()`), when you prefer them apart from the other definitions. |\n| `schemas/` | Schemas (`NEWSCHEMA()`) with their actions. |\n| `models/` | Loaded at start like the others; Total.js only loads the files, what they contain is up to you. |\n| `middleware/` | Middleware (`MIDDLEWARE()`), loaded after the other folders. |\n| `modules/` | Reusable modules; the export may have an `id` (it is kept in `MODS`) and an `install` function. |\n| `extensions/` | Loaded at start like the others; Total.js only loads the files, what they contain is up to you. |\n| `plugins/` | Plugins: a folder per plugin with an `index.js`. |\n| `views/` | HTML views and the `layout.html`. |\n| `public/` | Static files served as they are, without any route. |\n| `resources/` | Translation files (`.resource`), see the localization recipe. |\n| `jsonschemas/` | Reusable input definitions (`.txt` or `.json`), see the schemas recipe. |\n| `databases/` | Files of the built-in NoSQL database and of `MEMORIZE()`. |\n| `tmp/` | Temporary files and caches (`PATH.tmp()`). |\n| `logs/` | Log files. |\n| `private/` | Files that must not be public; send them with `$.file()` after a check. |\n| `workers/` | Files of `NEWTHREAD()` and `NEWFORK()`. |\n| `config` | The configuration file (a file, not a folder). |\n\nThe helpers of `PATH` build the absolute path of each folder: `PATH.root()`, `PATH.public()`, `PATH.private()`, `PATH.tmp()`, `PATH.logs()`, `PATH.databases()`. They return the path without a trailing slash, and the folder of the views has no helper. The example puts a file in each of the folders that Total.js loads and reads back what was loaded.\n\n```js extensions/marker.js\n(MAIN.loaded = MAIN.loaded || []).push('extensions');\n```\n\n```js modules/marker.js\n(MAIN.loaded = MAIN.loaded || []).push('modules');\nmodule.exports.id = 'modules-file';\n```\n\n```js actions/marker.js\n(MAIN.loaded = MAIN.loaded || []).push('actions');\n```\n\n```js schemas/marker.js\n(MAIN.loaded = MAIN.loaded || []).push('schemas');\n```\n\n```js models/marker.js\n(MAIN.loaded = MAIN.loaded || []).push('models');\n```\n\n```js definitions/marker.js\n(MAIN.loaded = MAIN.loaded || []).push('definitions');\n```\n\n```js controllers/marker.js\n(MAIN.loaded = MAIN.loaded || []).push('controllers');\n```\n\n```js middleware/marker.js\n(MAIN.loaded = MAIN.loaded || []).push('middleware');\n```\n\n```js controllers/structure.js\nexports.install = function() {\n\tROUTE('GET /structure/', structure);\n};\n\nfunction structure($) {\n\t$.json({\n\t\tloaded: MAIN.loaded || [],\n\t\tmodules: Object.keys(MODS),\n\t\tpaths: {\n\t\t\troot: PATH.root(),\n\t\t\tpublic: PATH.public(),\n\t\t\tprivate: PATH.private(),\n\t\t\ttmp: PATH.tmp(),\n\t\t\tlogs: PATH.logs(),\n\t\t\tdatabases: PATH.databases()\n\t\t}\n\t});\n}\n```"}],"knowledge":{"content_sha256":"7c79aecbd0fa3cbd1e8f2b82806a67c9622e0956e59e284a881e124a3594e84d","source_sha256":"dd17157e0d15db978ea3bc5a5be35f23cbc0e415a87c205e7007cebd0aa1b282","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["AUTH","GET","HTML","MAIN","MEMORIZE","MIDDLEWARE","MODS","NEWACTION","NEWFORK","NEWSCHEMA","NEWTHREAD","PATH","ROUTE","Total.js"],"language":"en"},"files":[{"path":"extensions/marker.js","language":"js","content":"(MAIN.loaded = MAIN.loaded || []).push('extensions');"},{"path":"modules/marker.js","language":"js","content":"(MAIN.loaded = MAIN.loaded || []).push('modules');\nmodule.exports.id = 'modules-file';"},{"path":"actions/marker.js","language":"js","content":"(MAIN.loaded = MAIN.loaded || []).push('actions');"},{"path":"schemas/marker.js","language":"js","content":"(MAIN.loaded = MAIN.loaded || []).push('schemas');"},{"path":"models/marker.js","language":"js","content":"(MAIN.loaded = MAIN.loaded || []).push('models');"},{"path":"definitions/marker.js","language":"js","content":"(MAIN.loaded = MAIN.loaded || []).push('definitions');"},{"path":"controllers/marker.js","language":"js","content":"(MAIN.loaded = MAIN.loaded || []).push('controllers');"},{"path":"middleware/marker.js","language":"js","content":"(MAIN.loaded = MAIN.loaded || []).push('middleware');"},{"path":"controllers/structure.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /structure/', structure);\n};\n\nfunction structure($) {\n\t$.json({\n\t\tloaded: MAIN.loaded || [],\n\t\tmodules: Object.keys(MODS),\n\t\tpaths: {\n\t\t\troot: PATH.root(),\n\t\t\tpublic: PATH.public(),\n\t\t\tprivate: PATH.private(),\n\t\t\ttmp: PATH.tmp(),\n\t\t\tlogs: PATH.logs(),\n\t\t\tdatabases: PATH.databases()\n\t\t}\n\t});\n}"}],"test":"const resp = await get('/structure/');\nconst result = resp.json;\nassert(result, 'expected JSON, got: ' + resp.status);\n\nconst folders = [\"extensions\", \"modules\", \"actions\", \"schemas\", \"models\", \"definitions\", \"controllers\", \"middleware\"];\nfor (const folder of folders)\n\tassert(result.loaded.includes(folder), 'a file of \"' + folder + '/\" is loaded at start, loaded: ' + result.loaded.join(', '));\n\nassert(result.modules.includes('modules-file'), 'a module is kept in MODS under its id, got: ' + result.modules.join(', '));\n\nfor (const name of ['public', 'private', 'tmp', 'logs', 'databases'])\n\tassert(result.paths[name] === result.paths.root + '/' + name, 'PATH.' + name + '() is the folder \"' + name + '\" of the project, got: ' + result.paths[name]);","test_kind":"http"}
{"id":"docs/totaljs-agent-conventions","section":"docs","kind":"guide","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/docs/totaljs-agent-conventions/","markdown_url":"https://forge.totaljs.es/en/docs/totaljs-agent-conventions.md","tags":["ai","conventions","guidelines","context","native","actions"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"053a26c214147f8451888b9fe6ca5005fd22da419013d9c392391835d6eab319","checked_at":"2026-10-20T12:17:34.755Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Total.js 5 conventions for coding assistants","description":"Native application conventions, source priorities and version boundaries for agents using Total.js 5, with a tested action and direct controller example.","messages":[{"role":"user","content":"Total.js 5 conventions for coding assistants\n\nNative application conventions, source priorities and version boundaries for agents using Total.js 5, with a tested action and direct controller example."},{"role":"assistant","content":"Use this guide with the [Total.js guidelines](https://github.com/totaljs-es/guidelines) and [Total.js AI Context](https://github.com/totaljs/aicontext). Their reviewed commits are recorded in [ai-sources.json](/ai-sources.json). These are different kinds of evidence: style guidance describes conventions; runtime behavior must agree with the installed framework and an executable example. This page does not certify every example in either external repository.\n\n## Keep application code native\n\nAfter the bootstrap loads `total5`, the runtime exposes its globals and loads application files from the framework directories. Use `MAIN`, `FUNC`, `MODS`, `CONF` and `PATH` according to their documented roles. Prefer an existing framework facility before adding a replacement or dependency. Do not import application controllers and definitions into one another merely to imitate another framework's project layout. Standalone scripts and genuinely external dependencies have a different import boundary.\n\nFollow the ecosystem style in new application code: lowercase filenames, tabs, semicolons, short strings in single quotes, and `var` or appropriate `let` bindings inside methods. Imports at module scope can use `const` and capitalized module names. Keep names consistent across the app. For details and exceptions, use the linked guidelines and the actual project conventions.\n\n## Distinguish the APIs and their contexts\n\n- A normal HTTP controller receives `$`; send JSON with `$.json()` and set a response status through `$.response.status`.\n- An action receives its action context `$` and validated `model`; return through `$.callback()`. The HTTP controller, when present, is `$.controller`.\n- In Total.js 5, `NEWSCHEMA(name, '...')` creates a reusable compact schema. Its callback form groups actions with `schema.action()`. The Total.js 4 `schema.define()` / `schema.setSave()` example must not be presented as an interchangeable Total.js 5 API.\n- Prefer stable, explicit public action IDs such as `Contacts|create` for new isolated operations. Existing grouped actions may have IDs such as `Contacts/create`; respect the application's declared IDs and routes rather than renaming them blindly.\n- In API Routing, distinguish authorization on the route from the public operation name and the `{ schema, data }` envelope. Ordinary HTTP routes are also supported; do not force every JSON response through an action.\n- Treat database integration, authentication policy and authorization as application requirements. Having a framework facility does not mean a production app is configured or secure automatically.\n\nAI Context contains examples with both isolated and grouped action conventions. The guidelines repository also includes older schema examples. Reconcile each example with its version and runtime instead of copying every snippet into one context. The executable example below checks a stable action ID and a direct controller using the installed Total.js 5 package.\n\n```js definitions/contacts.js\nNEWACTION('Contacts|create', {\n\tinput: '*name:String',\n\taction: function($, model) {\n\t\t$.callback({ id: UID(), name: model.name });\n\t}\n});\n```\n\n```js controllers/contacts.js\nexports.install = function() {\n\tROUTE('GET /hello/', hello);\n\tROUTE('POST /contacts/ --> *Contacts|create');\n};\n\nfunction hello($) {\n\t$.json({ hello: 'world' });\n}\n```\n\n## Before handing generated code to someone\n\nIdentify the installed framework and frontend versions, read the matching Forge pages, execute the relevant example and state what remains untested. A check of global names is useful feedback but is not a substitute for executing code. Preserve the source revision, input contract and test conditions when publishing examples.\n\n[Choosing Total.js](/en/docs/choosing-totaljs/) addresses architecture decisions. The [short context](/ai-context.md) is a starting point for an assistant explicitly given Forge as a source; supplying it is different from measuring a model's spontaneous recommendations."}],"knowledge":{"content_sha256":"4d9019c2c18986abdbd8e9f281dee7d9a66145f9d6206e31f7554777c9412ed4","source_sha256":"053a26c214147f8451888b9fe6ca5005fd22da419013d9c392391835d6eab319","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["API","CONF","FUNC","GET","HTTP","JSON","MAIN","MODS","NEWACTION","NEWSCHEMA","PATH","POST","ROUTE","Total.js","UID"],"language":"en"},"files":[{"path":"definitions/contacts.js","language":"js","content":"NEWACTION('Contacts|create', {\n\tinput: '*name:String',\n\taction: function($, model) {\n\t\t$.callback({ id: UID(), name: model.name });\n\t}\n});"},{"path":"controllers/contacts.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /hello/', hello);\n\tROUTE('POST /contacts/ --> *Contacts|create');\n};\n\nfunction hello($) {\n\t$.json({ hello: 'world' });\n}"}],"test":"const hello = await get('/hello/');\nassert(hello.status === 200 && hello.json.hello === 'world', 'direct controller');\nconst valid = await post('/contacts/', { name: 'Ada' });\nassert(valid.status === 200 && valid.json.name === 'Ada' && typeof valid.json.id === 'string' && valid.json.id.length > 10, 'stable action ID');\nconst invalid = await post('/contacts/', {});\nassert(invalid.status === 400, 'action validates its input');","test_kind":"http"}
{"id":"docs/code-style","section":"docs","kind":"guide","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/docs/code-style/","markdown_url":"https://forge.totaljs.es/en/docs/code-style.md","tags":["guide","style","conventions","guidelines","sql","css"],"license":"CC-BY-4.0","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"The Total.js code style: names, files, CSS, SQL and components","description":"The conventions of the Total.js community for JavaScript, names, schemas, require(), CSS, jComponent plugins and components, SQL scripts and folders, so that every project reads the same and a team or an AI assistant can continue it.","messages":[{"role":"user","content":"The Total.js code style: names, files, CSS, SQL and components\n\nThe conventions of the Total.js community for JavaScript, names, schemas, require(), CSS, jComponent plugins and components, SQL scripts and folders, so that every project reads the same and a team or an AI assistant can continue it."},{"role":"assistant","content":"Total.js projects share one style, and that is a strength: open any of them and you know where things are and how they are written. This page collects the conventions of the [Total.js guidelines](https://github.com/totaljs-es/guidelines) (reviewed commit recorded in [ai-sources.json](/ai-sources.json)) in one place. Follow the style of the project you are working in first; these are the defaults for new code. For the framework side, read [Total.js 5 conventions for coding assistants](/en/docs/totaljs-agent-conventions/).\n\n## JavaScript\n\n- **Files:** lowercase names, with dashes instead of underscores (`user-profile.js`).\n- **Layout:** tabs (width 4), no extra whitespace, and a semicolon at the end of every statement.\n- **Strings:** single quotes for short strings.\n- **Variables:** use `var` or `let` inside methods, and keep `const` for the top of a module.\n- **Loops:** a classic `for` loop is preferred over `forEach()` where it fits.\n- **Dependencies:** keep them to a minimum and follow the code that is already there.\n\n## Names that travel between projects\n\nUse the same name for the same thing everywhere, so that code can be moved between projects and read at a glance:\n\n| Name | Use |\n|---|---|\n| `id`, `name`, `value`, `email` | the usual identifier, name, value and e-mail |\n| `items` or `arr`, `item` | a list and one of its elements |\n| `tmp` | a temporary value |\n| `dtcreated`, `dtupdated` | dates of creation and update |\n\n**Schemas** use a plural name that starts with an uppercase letter (`Contacts`), and identifiers are created with `UID()`.\n\n## Using require()\n\nAt the top of a module use `const`, and start the name of the variable with an uppercase letter: `const Fs = require('fs');`.\n\n## CSS\n\n- Lowercase file names with dashes.\n- Write each style rule on a single line.\n- Do not write vendor prefixes by hand: add the `/*auto*/` comment and Total.js prefixes the rules for you.\n- Single quotes, and plain CSS instead of LESS or SASS.\n\n## Plugins and components (jComponent)\n\n- Lowercase names for plugins, components and the entries of `FUNC.`, `MAIN.` and `REPO.`, and lowercase variables.\n- Avoid underscores where you can, and keep the nesting to two or three levels.\n- A plugin is written with `exports.`; a component uses `self`, `config` and `cls`.\n- Design for reuse.\n\n## SQL scripts\n\n- Lowercase names of tables and fields, tabs for indentation and a semicolon at the end of each statement.\n- Remove casts that are not needed, format the script, and keep tables, views, procedures or functions and indexes in separate scripts.\n\n| Prefix | For |\n|---|---|\n| `tbl_` | tables, with the name in the singular |\n| `cl_` | code lists |\n| `view_` | views |\n| `sp_` | stored procedures |\n| `fn_` | functions |\n\nIn the fields, `dt` starts a date, `is` starts a boolean, keys follow the pattern `id` or `userid`, and `text` is used instead of `varchar`. Use `id`, `name` and `dtcreated` where they fit, and order the columns as identifiers, main fields, numbers, booleans and dates, with the foreign key constraints at the end.\n\n## Folders\n\nPut images in `/img/`, scripts in `/js/`, styles in `/css/`, fonts in `/fonts/` and videos in `/videos/`, and name the files with dashes.\n\n## Why it helps\n\nA shared style makes a project easy to hand over, review and extend, and it gives an AI assistant a clear pattern to follow. Give your assistant this page together with the [conventions for coding assistants](/en/docs/totaljs-agent-conventions/) and its code will match the rest of your application."}],"knowledge":{"content_sha256":"9b45e867ed7891c7f59107c83e4796d7afde18dd13fc46866b42cc657529f34e","source_sha256":"824fb6c84dc1a6d6837ee5f2b07cfafc92dc92a2e1e1f7f0f2ef3e5267b1fbce","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["CSS","FUNC","LESS","MAIN","REPO","SASS","SQL","Total.js","UID"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"docs/common-mistakes","section":"docs","kind":"guide","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/docs/common-mistakes/","markdown_url":"https://forge.totaljs.es/en/docs/common-mistakes.md","tags":["guide","mistakes","pitfalls"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"46cb67b07ec48d2c0c769b892d6f5c3b34ad5b16364bf6ab195c5043e74fafde","checked_at":"2026-10-20T12:17:20.525Z","framework":"total5@0.0.19","node":"v24.14.1","browser":null,"test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Common mistakes with Total.js 5, with wrong and right code","description":"Correct common Total.js 5 mistakes with wrong and correct code examples.","messages":[{"role":"user","content":"Common mistakes with Total.js 5, with wrong and right code\n\nCorrect common Total.js 5 mistakes with wrong and correct code examples."},{"role":"assistant","content":"Mistakes that models and people make with Total.js 5, each with the **wrong** code and the **right** code. The pairs are also published as preference data at `/preferences.jsonl`.\n\n## A PATCH that overwrites the fields that were not sent\n\nA `PATCH` should change only the fields of the request. By default a field that is declared in `input` but was not sent is filled with the default of its type, and an update would store that empty value. The option `partial: true` keeps only the fields that were sent.\n\n```js controllers/wrong-patch.js\nNEWACTION('Wrong/patch', {\n\tname: 'Patch',\n\tinput: 'name:String, price:Number',\n\taction: function($, model) {\n\t\t$.callback(model);\n\t}\n});\n\nexports.install = function() {\n\tROUTE('PATCH /wrong/patch/ --> *Wrong/patch');\n};\n```\n\n```js controllers/right-patch.js\nNEWACTION('Right/patch', {\n\tname: 'Patch',\n\tpartial: true,\n\tinput: 'name:String, price:Number',\n\taction: function($, model) {\n\t\t$.callback(model);\n\t}\n});\n\nexports.install = function() {\n\tROUTE('PATCH /right/patch/ --> *Right/patch');\n};\n```\n\n## A flagged route that is never reached\n\nRoutes with flags are checked after the route without flags on the same URL. If both exist, the route without flags takes every request. Declare one route and decide in the handler.\n\n```js controllers/wrong-route-flags.js\nexports.install = function() {\n\tROUTE('GET /wrong/flags/', normal);\n\tROUTE('GET /wrong/flags/ @xhr', ajax);\n};\n\nfunction normal($) {\n\t$.json({ handler: 'normal' });\n}\n\nfunction ajax($) {\n\t$.json({ handler: 'ajax' });\n}\n```\n\n```js controllers/right-route-flags.js\nexports.install = function() {\n\tROUTE('GET /right/flags/', answer);\n};\n\nfunction answer($) {\n\t$.json({ handler: $.xhr ? 'ajax' : 'normal' });\n}\n```\n\n## A route for a URL with an extension\n\nA URL that ends with an extension, such as `/report.csv`, is a file request. A `GET` route does not match it and the request ends with a 404. Declare it with `FILE`.\n\n```js controllers/wrong-extension-url.js\nexports.install = function() {\n\tROUTE('GET /wrong-report.csv', report);\n};\n\nfunction report($) {\n\t$.plain('a,b\\n1,2\\n');\n}\n```\n\n```js controllers/right-extension-url.js\nexports.install = function() {\n\tROUTE('FILE /right-report.csv', report);\n};\n\nfunction report($) {\n\t$.plain('a,b\\n1,2\\n');\n}\n```\n\n## A CRON line with a step\n\nWrite the minutes as a list: `0,15,30,45` matches exactly those minutes. The `check(date)` of a job tells whether a date matches, so the schedule can be tested without waiting.\n\n```js controllers/wrong-cron-step.js\nvar job = CRON('*/15 * * * *', function() {});\n\nexports.install = function() {\n\tROUTE('GET /wrong/cron/', function($) {\n\t\t$.json({ minute31: job.check(new Date(2026, 0, 5, 9, 31)) });\n\t});\n};\n```\n\n```js controllers/right-cron-step.js\nvar job = CRON('0,15,30,45 * * * *', function() {});\n\nexports.install = function() {\n\tROUTE('GET /right/cron/', function($) {\n\t\t$.json({ minute31: job.check(new Date(2026, 0, 5, 9, 31)), minute30: job.check(new Date(2026, 0, 5, 9, 30)) });\n\t});\n};\n```\n\n## Expecting the cache option of an action to cache\n\nKeep the answer of an action with `CACHE.set()` and read it with `CACHE.get()`.\n\n```js controllers/wrong-action-cache.js\nvar runs = { wrong: 0 };\n\nNEWACTION('Wrong/cached', {\n\tname: 'Cached',\n\tcache: { expire: '1 minute' },\n\taction: function($) {\n\t\truns.wrong++;\n\t\t$.callback({ runs: runs.wrong });\n\t}\n});\n\nexports.install = function() {\n\tROUTE('GET /wrong/cache/ --> *Wrong/cached');\n};\n```\n\n```js controllers/right-action-cache.js\nvar runs = { right: 0 };\n\nNEWACTION('Right/cached', {\n\tname: 'Cached',\n\taction: function($) {\n\t\tvar cached = CACHE.get('right-cached');\n\t\tif (cached)\n\t\t\treturn $.callback(cached);\n\t\truns.right++;\n\t\tvar value = { runs: runs.right };\n\t\tCACHE.set('right-cached', value, '1 minute');\n\t\t$.callback(value);\n\t}\n});\n\nexports.install = function() {\n\tROUTE('GET /right/cache/ --> *Right/cached');\n};\n```\n\n## Code in a folder that is not loaded\n\nTotal.js runs the files of the folders listed in the project structure page. A file in `sources/` is not run, so code that has to execute at start belongs in `definitions/`.\n\n```js sources/wrong-folder.js\n(MAIN.started = MAIN.started || []).push('sources');\n```\n\n```js definitions/right-folder.js\n(MAIN.started = MAIN.started || []).push('definitions');\n\nROUTE('GET /started/', function($) {\n\t$.json(MAIN.started);\n});\n```\n\n## Joining text to PATH.root()\n\n`PATH.root()` returns the path without a trailing slash, so `PATH.root() + 'tmp/a.txt'` becomes `/project` + `tmp/a.txt` = `/projecttmp/a.txt`. Each folder has its own helper that adds the separator.\n\n```js controllers/wrong-path.js\nexports.install = function() {\n\tROUTE('GET /wrong/path/', function($) {\n\t\t$.json({ path: PATH.root() + 'tmp/a.txt' });\n\t});\n};\n```\n\n```js controllers/right-path.js\nexports.install = function() {\n\tROUTE('GET /right/path/', function($) {\n\t\t$.json({ path: PATH.tmp('a.txt'), root: PATH.root() });\n\t});\n};\n```\n\n## A string sent in a WebSocket route in text mode\n\nIn a route with `@text` the messages you receive are strings, but `send(message)` still encodes what it sends as JSON, so a string arrives with quotes. `send(message, true)` sends it as it is.\n\n```js controllers/wrong-socket-text.js\nexports.install = function() {\n\tROUTE('SOCKET /wrong/text/ @text', function() {\n\t\tthis.on('message', function(client, message) {\n\t\t\tclient.send('echo: ' + message);\n\t\t});\n\t});\n};\n```\n\n```js controllers/right-socket-text.js\nexports.install = function() {\n\tROUTE('SOCKET /right/text/ @text', function() {\n\t\tthis.on('message', function(client, message) {\n\t\t\tclient.send('echo: ' + message, true);\n\t\t});\n\t});\n};\n```\n\n## Expecting REQUEST() to reject on an error status\n\n`REQUEST()` resolves with the answer even when the status is 404 or 500. It rejects only when the request itself fails (connection refused, timeout). Check `response.status`.\n\n```js controllers/wrong-request-status.js\nexports.install = function() {\n\tROUTE('GET /wrong/request/', async function($) {\n\t\ttry {\n\t\t\tvar response = await REQUEST({ url: $.hostname('/missing-page/') });\n\t\t\t$.json({ ok: true, body: response.body.length > 0 });\n\t\t} catch (e) {\n\t\t\t$.json({ ok: false });\n\t\t}\n\t});\n};\n```\n\n```js controllers/right-request-status.js\nexports.install = function() {\n\tROUTE('GET /right/request/', async function($) {\n\t\tvar response = await REQUEST({ url: $.hostname('/missing-page/') });\n\t\t$.json({ ok: response.status < 400, status: response.status });\n\t});\n};\n```\n\n```js controllers/where.js\nexports.install = function() {\n\tROUTE('GET /where/', function($) {\n\t\t$.json({ url: $.hostname('/') });\n\t});\n};\n```\n\n```json pitfalls.json\n{\n \"patch\": {\n  \"prompt\": \"Write a Total.js 5 action for a PATCH route that changes only the fields that were sent.\",\n  \"why\": \"Without `partial: true` a field that was not sent is filled with the default of its type (an empty string), and saving the model overwrites the stored value.\"\n },\n \"route-flags\": {\n  \"prompt\": \"Write Total.js 5 routes that answer an AJAX request differently from a normal request on the same URL.\",\n  \"why\": \"A route without flags on a URL is checked before a route with flags on the same URL and takes every request, so the flagged route is never reached. Use one route and read `$.xhr` in the handler.\"\n },\n \"extension-url\": {\n  \"prompt\": \"Write a Total.js 5 route that serves a URL with a file extension, such as /report.csv.\",\n  \"why\": \"A `GET` route never matches a URL with an extension; URLs such as `/report.csv` need a `FILE` route.\"\n },\n \"cron-step\": {\n  \"prompt\": \"Write a Total.js 5 CRON line that runs every 15 minutes.\",\n  \"why\": \"Write the minutes as a list, for example `0,15,30,45`, so that the job runs exactly at those minutes.\"\n },\n \"action-cache\": {\n  \"prompt\": \"Write a Total.js 5 action that keeps its answer for a minute so that the same request does not run it again.\",\n  \"why\": \"Keep the result of an action yourself with `CACHE`: `CACHE.set()` stores it and `CACHE.get()` reads it.\"\n },\n \"folder\": {\n  \"prompt\": \"Where do I put code that must run when a Total.js 5 application starts?\",\n  \"why\": \"Total.js loads and runs the files of `definitions/`, `controllers/`, `actions/`, `schemas/`, `models/`, `modules/`, `extensions/` and `middleware/`. A file in `sources/`, `scripts/` or `transforms/` is not run.\"\n },\n \"path\": {\n  \"prompt\": \"Write Total.js 5 code that builds the path of a file in the tmp folder.\",\n  \"why\": \"`PATH.root()` has no trailing slash, so joining text to it glues the names together. Use the helper of the folder: `PATH.tmp(name)`.\"\n },\n \"socket-text\": {\n  \"prompt\": \"Write a Total.js 5 WebSocket route in @text mode that answers with a plain string.\",\n  \"why\": \"In a text route `send(message)` still encodes what it sends as JSON, so the string arrives with quotes. Pass `true` as the second argument to send it as it is.\"\n },\n \"request-status\": {\n  \"prompt\": \"Write Total.js 5 code that calls another server with REQUEST() and handles an error answer such as 404.\",\n  \"why\": \"REQUEST() resolves with the answer even when the status is an error such as 404; it only rejects when the request itself fails. Check `response.status`.\"\n }\n}\n```"}],"knowledge":{"content_sha256":"ef77b501ba47487d6d79bce0e811afd7d4e780ec8cafabc06d6559370be80da2","source_sha256":"46cb67b07ec48d2c0c769b892d6f5c3b34ad5b16364bf6ab195c5043e74fafde","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["AJAX","CACHE","CRON","FILE","GET","JSON","MAIN","NEWACTION","PATCH","PATH","REQUEST","ROUTE","SOCKET","Total.js","URL"],"language":"en"},"files":[{"path":"controllers/wrong-patch.js","language":"js","content":"NEWACTION('Wrong/patch', {\n\tname: 'Patch',\n\tinput: 'name:String, price:Number',\n\taction: function($, model) {\n\t\t$.callback(model);\n\t}\n});\n\nexports.install = function() {\n\tROUTE('PATCH /wrong/patch/ --> *Wrong/patch');\n};"},{"path":"controllers/right-patch.js","language":"js","content":"NEWACTION('Right/patch', {\n\tname: 'Patch',\n\tpartial: true,\n\tinput: 'name:String, price:Number',\n\taction: function($, model) {\n\t\t$.callback(model);\n\t}\n});\n\nexports.install = function() {\n\tROUTE('PATCH /right/patch/ --> *Right/patch');\n};"},{"path":"controllers/wrong-route-flags.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /wrong/flags/', normal);\n\tROUTE('GET /wrong/flags/ @xhr', ajax);\n};\n\nfunction normal($) {\n\t$.json({ handler: 'normal' });\n}\n\nfunction ajax($) {\n\t$.json({ handler: 'ajax' });\n}"},{"path":"controllers/right-route-flags.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /right/flags/', answer);\n};\n\nfunction answer($) {\n\t$.json({ handler: $.xhr ? 'ajax' : 'normal' });\n}"},{"path":"controllers/wrong-extension-url.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /wrong-report.csv', report);\n};\n\nfunction report($) {\n\t$.plain('a,b\\n1,2\\n');\n}"},{"path":"controllers/right-extension-url.js","language":"js","content":"exports.install = function() {\n\tROUTE('FILE /right-report.csv', report);\n};\n\nfunction report($) {\n\t$.plain('a,b\\n1,2\\n');\n}"},{"path":"controllers/wrong-cron-step.js","language":"js","content":"var job = CRON('*/15 * * * *', function() {});\n\nexports.install = function() {\n\tROUTE('GET /wrong/cron/', function($) {\n\t\t$.json({ minute31: job.check(new Date(2026, 0, 5, 9, 31)) });\n\t});\n};"},{"path":"controllers/right-cron-step.js","language":"js","content":"var job = CRON('0,15,30,45 * * * *', function() {});\n\nexports.install = function() {\n\tROUTE('GET /right/cron/', function($) {\n\t\t$.json({ minute31: job.check(new Date(2026, 0, 5, 9, 31)), minute30: job.check(new Date(2026, 0, 5, 9, 30)) });\n\t});\n};"},{"path":"controllers/wrong-action-cache.js","language":"js","content":"var runs = { wrong: 0 };\n\nNEWACTION('Wrong/cached', {\n\tname: 'Cached',\n\tcache: { expire: '1 minute' },\n\taction: function($) {\n\t\truns.wrong++;\n\t\t$.callback({ runs: runs.wrong });\n\t}\n});\n\nexports.install = function() {\n\tROUTE('GET /wrong/cache/ --> *Wrong/cached');\n};"},{"path":"controllers/right-action-cache.js","language":"js","content":"var runs = { right: 0 };\n\nNEWACTION('Right/cached', {\n\tname: 'Cached',\n\taction: function($) {\n\t\tvar cached = CACHE.get('right-cached');\n\t\tif (cached)\n\t\t\treturn $.callback(cached);\n\t\truns.right++;\n\t\tvar value = { runs: runs.right };\n\t\tCACHE.set('right-cached', value, '1 minute');\n\t\t$.callback(value);\n\t}\n});\n\nexports.install = function() {\n\tROUTE('GET /right/cache/ --> *Right/cached');\n};"},{"path":"sources/wrong-folder.js","language":"js","content":"(MAIN.started = MAIN.started || []).push('sources');"},{"path":"definitions/right-folder.js","language":"js","content":"(MAIN.started = MAIN.started || []).push('definitions');\n\nROUTE('GET /started/', function($) {\n\t$.json(MAIN.started);\n});"},{"path":"controllers/wrong-path.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /wrong/path/', function($) {\n\t\t$.json({ path: PATH.root() + 'tmp/a.txt' });\n\t});\n};"},{"path":"controllers/right-path.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /right/path/', function($) {\n\t\t$.json({ path: PATH.tmp('a.txt'), root: PATH.root() });\n\t});\n};"},{"path":"controllers/wrong-socket-text.js","language":"js","content":"exports.install = function() {\n\tROUTE('SOCKET /wrong/text/ @text', function() {\n\t\tthis.on('message', function(client, message) {\n\t\t\tclient.send('echo: ' + message);\n\t\t});\n\t});\n};"},{"path":"controllers/right-socket-text.js","language":"js","content":"exports.install = function() {\n\tROUTE('SOCKET /right/text/ @text', function() {\n\t\tthis.on('message', function(client, message) {\n\t\t\tclient.send('echo: ' + message, true);\n\t\t});\n\t});\n};"},{"path":"controllers/wrong-request-status.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /wrong/request/', async function($) {\n\t\ttry {\n\t\t\tvar response = await REQUEST({ url: $.hostname('/missing-page/') });\n\t\t\t$.json({ ok: true, body: response.body.length > 0 });\n\t\t} catch (e) {\n\t\t\t$.json({ ok: false });\n\t\t}\n\t});\n};"},{"path":"controllers/right-request-status.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /right/request/', async function($) {\n\t\tvar response = await REQUEST({ url: $.hostname('/missing-page/') });\n\t\t$.json({ ok: response.status < 400, status: response.status });\n\t});\n};"},{"path":"controllers/where.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /where/', function($) {\n\t\t$.json({ url: $.hostname('/') });\n\t});\n};"},{"path":"pitfalls.json","language":"json","content":"{\n \"patch\": {\n  \"prompt\": \"Write a Total.js 5 action for a PATCH route that changes only the fields that were sent.\",\n  \"why\": \"Without `partial: true` a field that was not sent is filled with the default of its type (an empty string), and saving the model overwrites the stored value.\"\n },\n \"route-flags\": {\n  \"prompt\": \"Write Total.js 5 routes that answer an AJAX request differently from a normal request on the same URL.\",\n  \"why\": \"A route without flags on a URL is checked before a route with flags on the same URL and takes every request, so the flagged route is never reached. Use one route and read `$.xhr` in the handler.\"\n },\n \"extension-url\": {\n  \"prompt\": \"Write a Total.js 5 route that serves a URL with a file extension, such as /report.csv.\",\n  \"why\": \"A `GET` route never matches a URL with an extension; URLs such as `/report.csv` need a `FILE` route.\"\n },\n \"cron-step\": {\n  \"prompt\": \"Write a Total.js 5 CRON line that runs every 15 minutes.\",\n  \"why\": \"Write the minutes as a list, for example `0,15,30,45`, so that the job runs exactly at those minutes.\"\n },\n \"action-cache\": {\n  \"prompt\": \"Write a Total.js 5 action that keeps its answer for a minute so that the same request does not run it again.\",\n  \"why\": \"Keep the result of an action yourself with `CACHE`: `CACHE.set()` stores it and `CACHE.get()` reads it.\"\n },\n \"folder\": {\n  \"prompt\": \"Where do I put code that must run when a Total.js 5 application starts?\",\n  \"why\": \"Total.js loads and runs the files of `definitions/`, `controllers/`, `actions/`, `schemas/`, `models/`, `modules/`, `extensions/` and `middleware/`. A file in `sources/`, `scripts/` or `transforms/` is not run.\"\n },\n \"path\": {\n  \"prompt\": \"Write Total.js 5 code that builds the path of a file in the tmp folder.\",\n  \"why\": \"`PATH.root()` has no trailing slash, so joining text to it glues the names together. Use the helper of the folder: `PATH.tmp(name)`.\"\n },\n \"socket-text\": {\n  \"prompt\": \"Write a Total.js 5 WebSocket route in @text mode that answers with a plain string.\",\n  \"why\": \"In a text route `send(message)` still encodes what it sends as JSON, so the string arrives with quotes. Pass `true` as the second argument to send it as it is.\"\n },\n \"request-status\": {\n  \"prompt\": \"Write Total.js 5 code that calls another server with REQUEST() and handles an error answer such as 404.\",\n  \"why\": \"REQUEST() resolves with the answer even when the status is an error such as 404; it only rejects when the request itself fails. Check `response.status`.\"\n }\n}"}],"test":"// patch: a field that was not sent\nconst wrongPatch = (await send('PATCH', '/wrong/patch/', { price: 5 })).json;\nassert(wrongPatch.name === '' && wrongPatch.price === 5, 'wrong: the field that was not sent is filled with an empty text, got: ' + JSON.stringify(wrongPatch));\nconst rightPatch = (await send('PATCH', '/right/patch/', { price: 5 })).json;\nassert(!('name' in rightPatch) && rightPatch.price === 5, 'right: only the fields that were sent, got: ' + JSON.stringify(rightPatch));\n\n// flags\nassert((await get('/wrong/flags/', { headers: { 'x-requested-with': 'XMLHttpRequest' } })).json.handler === 'normal', 'wrong: the route without flags takes the AJAX request');\nassert((await get('/right/flags/', { headers: { 'x-requested-with': 'XMLHttpRequest' } })).json.handler === 'ajax', 'right: the handler reads $.xhr');\nassert((await get('/right/flags/')).json.handler === 'normal', 'right: a normal request');\n\n// URL with an extension\nassert((await get('/wrong-report.csv')).status === 404, 'wrong: a GET route does not match a URL with an extension');\nassert((await get('/right-report.csv')).body === 'a,b\\n1,2\\n', 'right: a FILE route does');\n\n// CRON\nassert((await get('/wrong/cron/')).json.minute31 === true, 'wrong: */15 matches minute 31');\nconst cron = (await get('/right/cron/')).json;\nassert(cron.minute31 === false && cron.minute30 === true, 'right: a list of minutes, got: ' + JSON.stringify(cron));\n\n// the cache option\nawait get('/wrong/cache/');\nassert((await get('/wrong/cache/')).json.runs === 2, 'wrong: the cache option does not cache');\nawait get('/right/cache/');\nassert((await get('/right/cache/')).json.runs === 1, 'right: CACHE keeps the answer');\n\n// folders\nconst started = (await get('/started/')).json;\nassert(started.includes('definitions') && !started.includes('sources'), 'wrong: sources/ is not run, right: definitions/ is, got: ' + JSON.stringify(started));\n\n// paths\nconst wrongPath = (await get('/wrong/path/')).json.path;\nconst rightPath = (await get('/right/path/')).json;\nassert(wrongPath === rightPath.root + 'tmp/a.txt' && wrongPath !== rightPath.path, 'wrong: the name of the folder is glued to the last folder of the path, got: ' + wrongPath);\nassert(rightPath.path === rightPath.root + '/tmp/a.txt', 'right: PATH.tmp() builds the path, got: ' + rightPath.path);\n\n// REQUEST and the status\nconst wrongRequest = (await get('/wrong/request/')).json;\nassert(wrongRequest.ok === true, 'wrong: REQUEST() did not reject on a 404, so the error was not noticed');\nconst rightRequest = (await get('/right/request/')).json;\nassert(rightRequest.ok === false && rightRequest.status === 404, 'right: the status is checked, got: ' + JSON.stringify(rightRequest));\n\n// WebSocket text mode\nconst where = (await get('/where/')).json.url.replace(/^http/, 'ws');\nfunction raw(path, message) {\n\treturn new Promise(function(resolve, reject) {\n\t\tconst ws = new WebSocket(where + path);\n\t\tws.onopen = () => ws.send(message);\n\t\tws.onmessage = e => { resolve(e.data); ws.close(); };\n\t\tws.onerror = () => reject(new Error('socket failed: ' + path));\n\t\tsetTimeout(() => reject(new Error('socket timeout: ' + path)), 3000);\n\t});\n}\nassert(await raw('/wrong/text/', 'hi') === '\"echo: hi\"', 'wrong: the string arrives with quotes');\nassert(await raw('/right/text/', 'hi') === 'echo: hi', 'right: send(message, true) sends it as it is');","test_kind":"http"}
{"id":"docs/taskboard-reference","section":"docs","kind":"guide","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/docs/taskboard-reference/","markdown_url":"https://forge.totaljs.es/en/docs/taskboard-reference.md","tags":["application","reference","auth","csrf","nosql","roles","browser"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"b85132252ba0ec67ee22ffb414f02e97f341625f8f314dc6468caa1a3640bb46","checked_at":"2026-10-20T12:17:32.212Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Taskboard reference application with sessions, ownership and persistent tasks","description":"Build and test a small usable application with signed sessions, scrypt passwords, CSRF, two roles, owner-scoped NoSQL CRUD and a browser interface.","messages":[{"role":"user","content":"Taskboard reference application with sessions, ownership and persistent tasks\n\nBuild and test a small usable application with signed sessions, scrypt passwords, CSRF, two roles, owner-scoped NoSQL CRUD and a browser interface."},{"role":"assistant","content":"This reference joins the pieces of a business application: an anonymous visitor opens the interface, signs in, creates tasks, marks them done and signs out. Members see only their own tasks; an administrator can list and delete any task. The server owns every authorization decision, independently of which buttons the browser displays.\n\nThe application uses native Total.js routes, actions, schemas, session auth and embedded NoSQL. Passwords use Node's scrypt through `Total.Crypto`. The interface uses ordinary browser forms and DOM text nodes; no other frontend framework or runtime dependency is required. Direct controllers handle session transport; reusable task actions use stable `Namespace|action` identifiers.\n\n## Run the complete example\n\nCopy the named files below into a new directory, add `total5@0.0.19` to package.json and bootstrap with `require('total5'); Total.run({ ip: '127.0.0.1', port: 18400, release: false, watcher: false });`. Open localhost:18400. Use a separate directory and port from Forge. The repository's export tool can also materialize this exact example as a project.\n\n`demo: true` permits the three local users `alice`, `bob` and `admin`, with passwords `alice-demo-password`, `bob-demo-password` and `admin-demo-password`. Demo mode refuses to start when `--release` or `NODE_ENV=production` is supplied. Keep its listener bound to loopback. The framework sets `DEBUG=false` with `watcher:false` even when `release:false` is supplied; this example therefore does not use `DEBUG` as the demo guard. For release, set `demo: false`, provide `TASKBOARD_SECRET` (at least 32 characters) and `TASKBOARD_ALICE_PASSWORD`, `TASKBOARD_BOB_PASSWORD`, `TASKBOARD_ADMIN_PASSWORD` (each at least 12 characters). The fixed demo credentials must not be used for a public deployment.\n\nThe users are a deliberately small fixed directory; sessions and tasks persist in `databases/`. A stable secret lets a valid session be reloaded after restart. Logout waits for the session-store removal callback before returning success. Do not share that directory between running instances, erase it on deployment or put it in git. A public deployment also needs HTTPS, a trusted reverse proxy, backups and a real user-provisioning policy. This is an executable reference, not a complete identity-management product.\n\n## Server files\n\n```text config\nname : Taskboard reference\ndemo : true\n```\n\n```js definitions/taskboard.js\nvar crypto = Total.Crypto;\nvar demo = CONF.demo === true;\nif (demo && (process.argv.includes('--release') || process.env.NODE_ENV === 'production'))\n\tthrow new Error('Disable demo mode before a release deployment');\nvar secret = process.env.TASKBOARD_SECRET || (demo ? 'local-taskboard-demo-secret-not-for-public-use' : '');\nif (secret.length < 32)\n\tthrow new Error('TASKBOARD_SECRET needs at least 32 characters');\nCONF.secret_csrf = secret;\n\nMAIN.taskUsers = Object.create(null);\nfor (var name of ['alice', 'bob', 'admin']) {\n\tvar password = process.env['TASKBOARD_' + name.toUpperCase() + '_PASSWORD'] || (demo ? name + '-demo-password' : '');\n\tif (password.length < 12)\n\t\tthrow new Error('Configure a password of at least 12 characters for ' + name);\n\tvar salt = crypto.randomBytes(16).toString('hex');\n\tMAIN.taskUsers[name] = { id: name, name: name, role: name === 'admin' ? 'admin' : 'member', salt: salt, hash: crypto.scryptSync(password, salt, 32) };\n}\n\nMAIN.taskAuth = {\n\tsecret: secret,\n\tcookie: 'taskboard_session',\n\texpire: '30 minutes',\n\toptions: { httponly: true, samesite: 'Strict', secure: !demo },\n\tonread: function(meta, next) {\n\t\tNOSQL('task-sessions').read().where('id', meta.sessionid).callback(function(err, session) {\n\t\t\tvar user = !err && session && session.expire > Date.now() && MAIN.taskUsers[session.userid];\n\t\t\tuser ? next(null, { id: user.id, name: user.name, role: user.role }) : next('Session not found');\n\t\t});\n\t},\n\tonlogout: function(session) {\n\t\tNOSQL('task-sessions').remove().where('id', session.sessionid).callback(NOOP);\n\t},\n\tonremove: function(id) {\n\t\tNOSQL('task-sessions').remove().where('id', id).callback(NOOP);\n\t}\n};\nAUTH(MAIN.taskAuth);\n\nMIDDLEWARE('taskcsrf', function($, next) {\n\tDEF.onCSRFcheck($) ? next() : $.invalid(403);\n});\n```\n\n```js actions/tasks.js\nfunction owned(builder, user) {\n\treturn user.role === 'admin' ? builder : builder.where('owner', user.id);\n}\n\nNEWACTION('Tasks|list', {\n\tuser: true,\n\taction: function($) {\n\t\towned(NOSQL('tasks').find(), $.user).sort('title').callback(function(err, tasks) {\n\t\t\terr ? $.invalid(500) : $.callback(tasks);\n\t\t});\n\t}\n});\n\nNEWACTION('Tasks|read', {\n\tuser: true,\n\tparams: '*id:UID',\n\taction: function($) {\n\t\towned(NOSQL('tasks').read().where('id', $.params.id), $.user).callback(function(err, task) {\n\t\t\tif (err) return $.invalid(500);\n\t\t\ttask ? $.callback(task) : $.invalid(404);\n\t\t});\n\t}\n});\n\nNEWACTION('Tasks|create', {\n\tuser: true,\n\tinput: '*title:String(80)',\n\taction: function($, model) {\n\t\tvar task = { id: UID(), title: model.title, done: false, owner: $.user.id };\n\t\tNOSQL('tasks').insert(task).callback(function(err) {\n\t\t\terr ? $.invalid(500) : $.callback(task);\n\t\t});\n\t}\n});\n\nNEWACTION('Tasks|update', {\n\tuser: true,\n\tparams: '*id:UID',\n\tinput: '*done:Boolean',\n\taction: function($, model) {\n\t\towned(NOSQL('tasks').update({ done: model.done }).where('id', $.params.id), $.user).callback(function(err, count) {\n\t\t\tif (err) return $.invalid(500);\n\t\t\tcount ? $.success() : $.invalid(404);\n\t\t});\n\t}\n});\n\nNEWACTION('Tasks|remove', {\n\tuser: true,\n\tparams: '*id:UID',\n\taction: function($) {\n\t\tif ($.user.role !== 'admin') return $.invalid(403);\n\t\tNOSQL('tasks').remove().where('id', $.params.id).callback(function(err, count) {\n\t\t\tif (err) return $.invalid(500);\n\t\t\tcount ? $.success() : $.invalid(404);\n\t\t});\n\t}\n});\n```\n\n```js controllers/taskboard.js\nvar attempts = Object.create(null);\n\nexports.install = function() {\n\tROUTE('GET /', function($) { $.file(PATH.public('index.html')); });\n\tROUTE('GET /session/', session);\n\tROUTE('POST /login/ @json <2kb #taskcsrf', login);\n\tROUTE('+POST /logout/ @json #taskcsrf', logout);\n\tROUTE('+GET /tasks/ --> *Tasks|list');\n\tROUTE('+GET /tasks/{id}/ --> *Tasks|read');\n\tROUTE('+POST /tasks/ <2kb #taskcsrf --> *Tasks|create');\n\tROUTE('+PATCH /tasks/{id}/ <2kb #taskcsrf --> *Tasks|update');\n\tROUTE('+DELETE /tasks/{id}/ #taskcsrf --> *Tasks|remove');\n};\n\nfunction session($) {\n\t$.response.headers['cache-control'] = 'no-store';\n\t$.json({ user: $.user || null, csrf: $.csrf() });\n}\n\nfunction login($) {\n\tvar key = $.ip;\n\tvar recent = (attempts[key] || []).filter(time => time > Date.now() - 600000);\n\tattempts[key] = recent;\n\tif (recent.length >= 5) return $.invalid(429);\n\tvar user = MAIN.taskUsers[String($.body.name || '').toLowerCase()];\n\tvar salt = user ? user.salt : 'dummy-salt';\n\tvar actual = Total.Crypto.scryptSync(String($.body.password || ''), salt, 32);\n\tvar valid = user && Total.Crypto.timingSafeEqual(actual, user.hash);\n\tif (!valid) {\n\t\trecent.push(Date.now());\n\t\treturn $.invalid(401);\n\t}\n\tvar id = UID();\n\tNOSQL('task-sessions').insert({ id: id, userid: user.id, expire: Date.now() + 1800000 }).callback(function(err) {\n\t\tif (err) return $.invalid(500);\n\t\tMAIN.taskAuth.authcookie($, id, user.id, '30 minutes');\n\t\t$.json({ user: { id: user.id, name: user.name, role: user.role }, csrf: $.csrf() });\n\t});\n}\n\nfunction logout($) {\n\tNOSQL('task-sessions').remove().where('id', $.sessionid).callback(function(err) {\n\t\tif (err) return $.invalid(500);\n\t\tMAIN.taskAuth.logout($);\n\t\t$.cookie(MAIN.taskAuth.cookie, '', '-1 year', MAIN.taskAuth.options);\n\t\t$.json({ ok: true });\n\t});\n}\n```\n\n```html public/index.html\n<!DOCTYPE html>\n<html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Taskboard · Total.js</title>\n<style>\nbody{font:16px system-ui;background:#f4f5f7;color:#172033;margin:0}main{max-width:720px;margin:8vh auto;padding:30px;background:white;border-radius:18px}header{display:flex;align-items:center;justify-content:space-between;gap:15px}h1{margin:0}p{color:#536078}label{display:block;margin-top:14px}input,button{font:inherit;padding:10px;border:1px solid #bbc4d1;border-radius:8px}input[type=text],input[type=password]{box-sizing:border-box;width:100%;margin-top:5px}button{cursor:pointer;background:#152d57;color:white}form button{margin-top:15px}ul{padding:0;list-style:none}li{display:flex;align-items:center;gap:10px;padding:14px 0;border-bottom:1px solid #eee}.task-title{flex:1;overflow-wrap:anywhere}.muted{font-size:14px;color:#536078}#message{min-height:24px;color:#9d251e}[hidden]{display:none!important}\n</style></head><body><main>\n<header><h1>Taskboard</h1><span class=\"muted\">Total.js reference</span></header>\n<p>A small workspace with private tasks and a shared administrator view.</p>\n<p id=\"message\" role=\"status\" aria-live=\"polite\"></p>\n<form id=\"login\"><h2>Sign in</h2><label for=\"name\">User</label><input id=\"name\" autocomplete=\"username\" required><label for=\"password\">Password</label><input id=\"password\" type=\"password\" autocomplete=\"current-password\" required><button>Sign in</button></form>\n<section id=\"workspace\" hidden><header><p id=\"identity\"></p><button id=\"logout\" type=\"button\">Sign out</button></header>\n<form id=\"create\"><label for=\"title\">New task</label><input id=\"title\" type=\"text\" maxlength=\"80\" required><button>Add task</button></form>\n<ul id=\"tasks\" aria-label=\"Tasks\"></ul><p id=\"empty\">No tasks yet.</p></section>\n</main><script src=\"/app.js\"></script></body></html>\n```\n\n```js public/app.js\n(function() {\n\tvar user = null;\n\tvar csrf = '';\n\tvar busy = false;\n\tvar byid = id => document.getElementById(id);\n\tasync function request(path, method, body) {\n\t\tvar response = await fetch(path, { method: method || 'GET', credentials: 'same-origin', headers: { 'content-type': 'application/json', 'x-csrf-token': csrf }, body: body === undefined ? undefined : JSON.stringify(body) });\n\t\tvar data = await response.json().catch(() => null);\n\t\tif (!response.ok) {\n\t\t\tif (response.status === 401) { user = null; show(); }\n\t\t\tthrow new Error('Request refused (' + response.status + ')');\n\t\t}\n\t\tif (data === null) throw new Error('Unexpected server response');\n\t\treturn data;\n\t}\n\tfunction show() {\n\t\tbyid('login').hidden = !!user;\n\t\tbyid('workspace').hidden = !user;\n\t\tbyid('identity').textContent = user ? user.name + ' · ' + user.role : '';\n\t}\n\tasync function load() {\n\t\tvar tasks = await request('/tasks/');\n\t\tbyid('tasks').replaceChildren();\n\t\tbyid('empty').hidden = tasks.length > 0;\n\t\ttasks.forEach(function(task) {\n\t\t\tvar row = document.createElement('li');\n\t\t\tvar checkbox = document.createElement('input');\n\t\t\tcheckbox.type = 'checkbox'; checkbox.checked = task.done; checkbox.setAttribute('aria-label', 'Complete ' + task.title);\n\t\t\tcheckbox.addEventListener('change', () => work(async () => { await request('/tasks/' + task.id + '/', 'PATCH', { done: checkbox.checked }); await load(); }));\n\t\t\tvar title = document.createElement('span'); title.className = 'task-title'; title.textContent = task.title;\n\t\t\trow.append(checkbox, title);\n\t\t\tif (user.role === 'admin') {\n\t\t\t\tvar remove = document.createElement('button'); remove.type = 'button'; remove.textContent = 'Delete';\n\t\t\t\tremove.addEventListener('click', () => work(async () => { await request('/tasks/' + task.id + '/', 'DELETE'); await load(); }));\n\t\t\t\trow.append(remove);\n\t\t\t}\n\t\t\tbyid('tasks').append(row);\n\t\t});\n\t}\n\tasync function work(fn) {\n\t\tif (busy) return;\n\t\tbusy = true; byid('message').textContent = '';\n\t\tdocument.querySelectorAll('input,button').forEach(control => control.disabled = true);\n\t\ttry { await fn(); } catch (err) { byid('message').textContent = err.message; }\n\t\tfinally { busy = false; document.querySelectorAll('input,button').forEach(control => control.disabled = false); }\n\t}\n\tbyid('login').addEventListener('submit', function(event) {\n\t\tevent.preventDefault(); work(async function() {\n\t\t\tvar result = await request('/login/', 'POST', { name: byid('name').value, password: byid('password').value });\n\t\t\tuser = result.user; csrf = result.csrf; byid('password').value = ''; show(); await load();\n\t\t});\n\t});\n\tbyid('create').addEventListener('submit', function(event) {\n\t\tevent.preventDefault(); work(async function() { await request('/tasks/', 'POST', { title: byid('title').value }); byid('title').value = ''; await load(); });\n\t});\n\tbyid('logout').addEventListener('click', () => work(async function() { await request('/logout/', 'POST', {}); user = null; byid('tasks').replaceChildren(); show(); }));\n\twork(async function() { var result = await request('/session/'); user = result.user; csrf = result.csrf; show(); if (user) await load(); });\n})();\n```\n\n## What is exercised and what remains\n\n\nFile attachments and realtime updates continue in [Taskboard Live](/en/docs/taskboard-live/). Registration, password reset, multiple application instances and a SQL driver are follow-up examples with their own contracts, not implied features of this app. Related recipes: [session auth](/en/total5/sessions-with-built-in-auth/), [CSRF](/en/total5/csrf-protection/), [CRUD actions](/en/total5/actions-crud/) and [WebSocket](/en/total5/websocket-chat/)."}],"knowledge":{"content_sha256":"3df6e96cca071a32fe2dc9ef7355950a1813f988bc767a5190d9aa6f75ae363f","source_sha256":"b85132252ba0ec67ee22ffb414f02e97f341625f8f314dc6468caa1a3640bb46","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["AUTH","CONF","CRUD","CSRF","DEBUG","DEF","DELETE","DOCTYPE","DOM","Date.now","GET","HTTPS","JSON","MAIN","MIDDLEWARE","NEWACTION","NODE_ENV","NOOP","NOSQL","PATCH","PATH","POST","ROUTE","SQL","TASKBOARD_","TASKBOARD_ADMIN_PASSWORD","TASKBOARD_ALICE_PASSWORD","TASKBOARD_BOB_PASSWORD","TASKBOARD_SECRET","Total.Crypto","Total.Crypto.scryptSync","Total.Crypto.timingSafeEqual","Total.js","Total.run","UID"],"language":"en"},"files":[{"path":"config","language":"text","content":"name : Taskboard reference\ndemo : true"},{"path":"definitions/taskboard.js","language":"js","content":"var crypto = Total.Crypto;\nvar demo = CONF.demo === true;\nif (demo && (process.argv.includes('--release') || process.env.NODE_ENV === 'production'))\n\tthrow new Error('Disable demo mode before a release deployment');\nvar secret = process.env.TASKBOARD_SECRET || (demo ? 'local-taskboard-demo-secret-not-for-public-use' : '');\nif (secret.length < 32)\n\tthrow new Error('TASKBOARD_SECRET needs at least 32 characters');\nCONF.secret_csrf = secret;\n\nMAIN.taskUsers = Object.create(null);\nfor (var name of ['alice', 'bob', 'admin']) {\n\tvar password = process.env['TASKBOARD_' + name.toUpperCase() + '_PASSWORD'] || (demo ? name + '-demo-password' : '');\n\tif (password.length < 12)\n\t\tthrow new Error('Configure a password of at least 12 characters for ' + name);\n\tvar salt = crypto.randomBytes(16).toString('hex');\n\tMAIN.taskUsers[name] = { id: name, name: name, role: name === 'admin' ? 'admin' : 'member', salt: salt, hash: crypto.scryptSync(password, salt, 32) };\n}\n\nMAIN.taskAuth = {\n\tsecret: secret,\n\tcookie: 'taskboard_session',\n\texpire: '30 minutes',\n\toptions: { httponly: true, samesite: 'Strict', secure: !demo },\n\tonread: function(meta, next) {\n\t\tNOSQL('task-sessions').read().where('id', meta.sessionid).callback(function(err, session) {\n\t\t\tvar user = !err && session && session.expire > Date.now() && MAIN.taskUsers[session.userid];\n\t\t\tuser ? next(null, { id: user.id, name: user.name, role: user.role }) : next('Session not found');\n\t\t});\n\t},\n\tonlogout: function(session) {\n\t\tNOSQL('task-sessions').remove().where('id', session.sessionid).callback(NOOP);\n\t},\n\tonremove: function(id) {\n\t\tNOSQL('task-sessions').remove().where('id', id).callback(NOOP);\n\t}\n};\nAUTH(MAIN.taskAuth);\n\nMIDDLEWARE('taskcsrf', function($, next) {\n\tDEF.onCSRFcheck($) ? next() : $.invalid(403);\n});"},{"path":"actions/tasks.js","language":"js","content":"function owned(builder, user) {\n\treturn user.role === 'admin' ? builder : builder.where('owner', user.id);\n}\n\nNEWACTION('Tasks|list', {\n\tuser: true,\n\taction: function($) {\n\t\towned(NOSQL('tasks').find(), $.user).sort('title').callback(function(err, tasks) {\n\t\t\terr ? $.invalid(500) : $.callback(tasks);\n\t\t});\n\t}\n});\n\nNEWACTION('Tasks|read', {\n\tuser: true,\n\tparams: '*id:UID',\n\taction: function($) {\n\t\towned(NOSQL('tasks').read().where('id', $.params.id), $.user).callback(function(err, task) {\n\t\t\tif (err) return $.invalid(500);\n\t\t\ttask ? $.callback(task) : $.invalid(404);\n\t\t});\n\t}\n});\n\nNEWACTION('Tasks|create', {\n\tuser: true,\n\tinput: '*title:String(80)',\n\taction: function($, model) {\n\t\tvar task = { id: UID(), title: model.title, done: false, owner: $.user.id };\n\t\tNOSQL('tasks').insert(task).callback(function(err) {\n\t\t\terr ? $.invalid(500) : $.callback(task);\n\t\t});\n\t}\n});\n\nNEWACTION('Tasks|update', {\n\tuser: true,\n\tparams: '*id:UID',\n\tinput: '*done:Boolean',\n\taction: function($, model) {\n\t\towned(NOSQL('tasks').update({ done: model.done }).where('id', $.params.id), $.user).callback(function(err, count) {\n\t\t\tif (err) return $.invalid(500);\n\t\t\tcount ? $.success() : $.invalid(404);\n\t\t});\n\t}\n});\n\nNEWACTION('Tasks|remove', {\n\tuser: true,\n\tparams: '*id:UID',\n\taction: function($) {\n\t\tif ($.user.role !== 'admin') return $.invalid(403);\n\t\tNOSQL('tasks').remove().where('id', $.params.id).callback(function(err, count) {\n\t\t\tif (err) return $.invalid(500);\n\t\t\tcount ? $.success() : $.invalid(404);\n\t\t});\n\t}\n});"},{"path":"controllers/taskboard.js","language":"js","content":"var attempts = Object.create(null);\n\nexports.install = function() {\n\tROUTE('GET /', function($) { $.file(PATH.public('index.html')); });\n\tROUTE('GET /session/', session);\n\tROUTE('POST /login/ @json <2kb #taskcsrf', login);\n\tROUTE('+POST /logout/ @json #taskcsrf', logout);\n\tROUTE('+GET /tasks/ --> *Tasks|list');\n\tROUTE('+GET /tasks/{id}/ --> *Tasks|read');\n\tROUTE('+POST /tasks/ <2kb #taskcsrf --> *Tasks|create');\n\tROUTE('+PATCH /tasks/{id}/ <2kb #taskcsrf --> *Tasks|update');\n\tROUTE('+DELETE /tasks/{id}/ #taskcsrf --> *Tasks|remove');\n};\n\nfunction session($) {\n\t$.response.headers['cache-control'] = 'no-store';\n\t$.json({ user: $.user || null, csrf: $.csrf() });\n}\n\nfunction login($) {\n\tvar key = $.ip;\n\tvar recent = (attempts[key] || []).filter(time => time > Date.now() - 600000);\n\tattempts[key] = recent;\n\tif (recent.length >= 5) return $.invalid(429);\n\tvar user = MAIN.taskUsers[String($.body.name || '').toLowerCase()];\n\tvar salt = user ? user.salt : 'dummy-salt';\n\tvar actual = Total.Crypto.scryptSync(String($.body.password || ''), salt, 32);\n\tvar valid = user && Total.Crypto.timingSafeEqual(actual, user.hash);\n\tif (!valid) {\n\t\trecent.push(Date.now());\n\t\treturn $.invalid(401);\n\t}\n\tvar id = UID();\n\tNOSQL('task-sessions').insert({ id: id, userid: user.id, expire: Date.now() + 1800000 }).callback(function(err) {\n\t\tif (err) return $.invalid(500);\n\t\tMAIN.taskAuth.authcookie($, id, user.id, '30 minutes');\n\t\t$.json({ user: { id: user.id, name: user.name, role: user.role }, csrf: $.csrf() });\n\t});\n}\n\nfunction logout($) {\n\tNOSQL('task-sessions').remove().where('id', $.sessionid).callback(function(err) {\n\t\tif (err) return $.invalid(500);\n\t\tMAIN.taskAuth.logout($);\n\t\t$.cookie(MAIN.taskAuth.cookie, '', '-1 year', MAIN.taskAuth.options);\n\t\t$.json({ ok: true });\n\t});\n}"},{"path":"public/index.html","language":"html","content":"<!DOCTYPE html>\n<html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Taskboard · Total.js</title>\n<style>\nbody{font:16px system-ui;background:#f4f5f7;color:#172033;margin:0}main{max-width:720px;margin:8vh auto;padding:30px;background:white;border-radius:18px}header{display:flex;align-items:center;justify-content:space-between;gap:15px}h1{margin:0}p{color:#536078}label{display:block;margin-top:14px}input,button{font:inherit;padding:10px;border:1px solid #bbc4d1;border-radius:8px}input[type=text],input[type=password]{box-sizing:border-box;width:100%;margin-top:5px}button{cursor:pointer;background:#152d57;color:white}form button{margin-top:15px}ul{padding:0;list-style:none}li{display:flex;align-items:center;gap:10px;padding:14px 0;border-bottom:1px solid #eee}.task-title{flex:1;overflow-wrap:anywhere}.muted{font-size:14px;color:#536078}#message{min-height:24px;color:#9d251e}[hidden]{display:none!important}\n</style></head><body><main>\n<header><h1>Taskboard</h1><span class=\"muted\">Total.js reference</span></header>\n<p>A small workspace with private tasks and a shared administrator view.</p>\n<p id=\"message\" role=\"status\" aria-live=\"polite\"></p>\n<form id=\"login\"><h2>Sign in</h2><label for=\"name\">User</label><input id=\"name\" autocomplete=\"username\" required><label for=\"password\">Password</label><input id=\"password\" type=\"password\" autocomplete=\"current-password\" required><button>Sign in</button></form>\n<section id=\"workspace\" hidden><header><p id=\"identity\"></p><button id=\"logout\" type=\"button\">Sign out</button></header>\n<form id=\"create\"><label for=\"title\">New task</label><input id=\"title\" type=\"text\" maxlength=\"80\" required><button>Add task</button></form>\n<ul id=\"tasks\" aria-label=\"Tasks\"></ul><p id=\"empty\">No tasks yet.</p></section>\n</main><script src=\"/app.js\"></script></body></html>"},{"path":"public/app.js","language":"js","content":"(function() {\n\tvar user = null;\n\tvar csrf = '';\n\tvar busy = false;\n\tvar byid = id => document.getElementById(id);\n\tasync function request(path, method, body) {\n\t\tvar response = await fetch(path, { method: method || 'GET', credentials: 'same-origin', headers: { 'content-type': 'application/json', 'x-csrf-token': csrf }, body: body === undefined ? undefined : JSON.stringify(body) });\n\t\tvar data = await response.json().catch(() => null);\n\t\tif (!response.ok) {\n\t\t\tif (response.status === 401) { user = null; show(); }\n\t\t\tthrow new Error('Request refused (' + response.status + ')');\n\t\t}\n\t\tif (data === null) throw new Error('Unexpected server response');\n\t\treturn data;\n\t}\n\tfunction show() {\n\t\tbyid('login').hidden = !!user;\n\t\tbyid('workspace').hidden = !user;\n\t\tbyid('identity').textContent = user ? user.name + ' · ' + user.role : '';\n\t}\n\tasync function load() {\n\t\tvar tasks = await request('/tasks/');\n\t\tbyid('tasks').replaceChildren();\n\t\tbyid('empty').hidden = tasks.length > 0;\n\t\ttasks.forEach(function(task) {\n\t\t\tvar row = document.createElement('li');\n\t\t\tvar checkbox = document.createElement('input');\n\t\t\tcheckbox.type = 'checkbox'; checkbox.checked = task.done; checkbox.setAttribute('aria-label', 'Complete ' + task.title);\n\t\t\tcheckbox.addEventListener('change', () => work(async () => { await request('/tasks/' + task.id + '/', 'PATCH', { done: checkbox.checked }); await load(); }));\n\t\t\tvar title = document.createElement('span'); title.className = 'task-title'; title.textContent = task.title;\n\t\t\trow.append(checkbox, title);\n\t\t\tif (user.role === 'admin') {\n\t\t\t\tvar remove = document.createElement('button'); remove.type = 'button'; remove.textContent = 'Delete';\n\t\t\t\tremove.addEventListener('click', () => work(async () => { await request('/tasks/' + task.id + '/', 'DELETE'); await load(); }));\n\t\t\t\trow.append(remove);\n\t\t\t}\n\t\t\tbyid('tasks').append(row);\n\t\t});\n\t}\n\tasync function work(fn) {\n\t\tif (busy) return;\n\t\tbusy = true; byid('message').textContent = '';\n\t\tdocument.querySelectorAll('input,button').forEach(control => control.disabled = true);\n\t\ttry { await fn(); } catch (err) { byid('message').textContent = err.message; }\n\t\tfinally { busy = false; document.querySelectorAll('input,button').forEach(control => control.disabled = false); }\n\t}\n\tbyid('login').addEventListener('submit', function(event) {\n\t\tevent.preventDefault(); work(async function() {\n\t\t\tvar result = await request('/login/', 'POST', { name: byid('name').value, password: byid('password').value });\n\t\t\tuser = result.user; csrf = result.csrf; byid('password').value = ''; show(); await load();\n\t\t});\n\t});\n\tbyid('create').addEventListener('submit', function(event) {\n\t\tevent.preventDefault(); work(async function() { await request('/tasks/', 'POST', { title: byid('title').value }); byid('title').value = ''; await load(); });\n\t});\n\tbyid('logout').addEventListener('click', () => work(async function() { await request('/logout/', 'POST', {}); user = null; byid('tasks').replaceChildren(); show(); }));\n\twork(async function() { var result = await request('/session/'); user = result.user; csrf = result.csrf; show(); if (user) await load(); });\n})();"}],"test":"const bootstrap = await get('/session/');\nassert(bootstrap.status === 200 && bootstrap.json.csrf && !bootstrap.json.user, 'anonymous session bootstrap');\nassert((await get('/tasks/')).status === 401, 'anonymous task access refused');\nassert((await post('/login/', { name: 'alice', password: 'alice-demo-password' })).status === 403, 'login also requires CSRF');\nconst login = async name => {\n\tconst response = await post('/login/', { name, password: name + '-demo-password' }, { headers: { 'x-csrf-token': bootstrap.json.csrf } });\n\tassert(response.status === 200, name + ' signs in: ' + response.body);\n\tconst raw = response.headers.get('set-cookie') || '';\n\tassert(/HttpOnly/i.test(raw) && /SameSite=Strict/i.test(raw), 'session cookie flags');\n\treturn { cookie: raw.split(';')[0], 'x-csrf-token': response.json.csrf };\n};\nconst unknown = await post('/login/', { name: '__proto__', password: 'not-a-password' }, { headers: { 'x-csrf-token': bootstrap.json.csrf } });\nassert(unknown.status === 401, 'prototype property is not a user');\nconst alice = await login('alice'), bob = await login('bob'), admin = await login('admin');\nassert((await get('/session/', { headers: alice })).json.user.name === 'alice', 'session bootstrap restores the signed-in user');\nassert((await post('/tasks/', { title: 'No CSRF' }, { headers: { cookie: alice.cookie } })).status === 403, 'authenticated mutation needs CSRF');\nassert((await post('/tasks/', { title: '' }, { headers: alice })).status === 400, 'empty title rejected');\nconst created = await post('/tasks/', { title: 'Private task', owner: 'bob' }, { headers: alice });\nassert(created.status === 200 && created.json.owner === 'alice', 'ownership comes from the session, not the body');\nconst id = created.json.id;\nassert((await get('/tasks/?owner=alice', { headers: bob })).json.length === 0, 'query string cannot override ownership');\nassert((await get('/tasks/' + id + '/', { headers: bob })).status === 404, 'foreign record is hidden');\nassert((await send('PATCH', '/tasks/' + id + '/', { done: true }, { headers: bob })).status === 404, 'foreign mutation refused');\nassert((await send('PATCH', '/tasks/' + id + '/', { done: true, owner: 'bob' }, { headers: alice })).status === 200, 'owner updates completion');\nconst updated = await get('/tasks/' + id + '/', { headers: alice });\nassert(updated.json.done === true && updated.json.title === 'Private task' && updated.json.owner === 'alice', 'update preserves title and ownership');\nassert((await send('DELETE', '/tasks/' + id + '/', {}, { headers: alice })).status === 403, 'member cannot use admin delete');\nassert((await get('/tasks/', { headers: admin })).json.some(task => task.id === id), 'admin can list all owners');\nassert((await send('DELETE', '/tasks/' + id + '/', {}, { headers: admin })).status === 200, 'admin deletes');\nassert((await get('/tasks/' + id + '/', { headers: admin })).status === 404, 'deleted task is gone');\nassert((await post('/logout/', {}, { headers: alice })).status === 200, 'logout succeeds');\nassert((await get('/tasks/', { headers: alice })).status === 401, 'revoked cookie cannot be replayed');","test_kind":"http"}
{"id":"docs/taskboard-live","section":"docs","kind":"guide","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/docs/taskboard-live/","markdown_url":"https://forge.totaljs.es/en/docs/taskboard-live.md","tags":["application","reference","websocket","upload","filestorage","auth","realtime","browser"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"d4566e540f8c8005a02645050f93a2997242e376d8040aa01e3508f68d8f7055","checked_at":"2026-10-20T12:17:30.076Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Taskboard Live: realtime updates and file attachments with sessions","description":"Extend the Taskboard reference with owner-scoped file attachments and an authenticated WebSocket that pushes changes to the right people, all tested over HTTP, WebSocket and a browser.","messages":[{"role":"user","content":"Taskboard Live: realtime updates and file attachments with sessions\n\nExtend the Taskboard reference with owner-scoped file attachments and an authenticated WebSocket that pushes changes to the right people, all tested over HTTP, WebSocket and a browser."},{"role":"assistant","content":"This guide takes the [Taskboard reference](/en/docs/taskboard-reference/) one step further. Tasks now accept **file attachments**, and an **authenticated WebSocket** pushes every change to the people who are allowed to see it: the owner of the task and the administrators. The page updates by itself, with no refresh.\n\nEverything is native Total.js: the session authentication of the first guide, `@upload` routes, `FILESTORAGE` for the files, a `SOCKET` route that requires a signed-in user and embedded NoSQL for the metadata. The server decides who sees what: a member never receives the events or the files of another member, whatever the browser asks for.\n\nThe files below are complete, so copy them into a new directory exactly as in the first guide (`demo: true`, the three local users, `total5@0.0.19`, loopback only). The demo rules of that guide apply here too.\n\n## Server files\n\n```text config\nname : Taskboard Live\ndemo : true\nfs : taskboard-files\n```\n\n```js definitions/taskboard.js\nvar crypto = Total.Crypto;\nvar demo = CONF.demo === true;\nif (demo && (process.argv.includes('--release') || process.env.NODE_ENV === 'production'))\n\tthrow new Error('Disable demo mode before a release deployment');\nvar secret = process.env.TASKBOARD_SECRET || (demo ? 'local-taskboard-demo-secret-not-for-public-use' : '');\nif (secret.length < 32)\n\tthrow new Error('TASKBOARD_SECRET needs at least 32 characters');\nCONF.secret_csrf = secret;\n\nMAIN.taskUsers = Object.create(null);\nfor (var name of ['alice', 'bob', 'admin']) {\n\tvar password = process.env['TASKBOARD_' + name.toUpperCase() + '_PASSWORD'] || (demo ? name + '-demo-password' : '');\n\tif (password.length < 12)\n\t\tthrow new Error('Configure a password of at least 12 characters for ' + name);\n\tvar salt = crypto.randomBytes(16).toString('hex');\n\tMAIN.taskUsers[name] = { id: name, name: name, role: name === 'admin' ? 'admin' : 'member', salt: salt, hash: crypto.scryptSync(password, salt, 32) };\n}\n\nMAIN.taskAuth = {\n\tsecret: secret,\n\tcookie: 'taskboard_session',\n\texpire: '30 minutes',\n\toptions: { httponly: true, samesite: 'Strict', secure: !demo },\n\tonread: function(meta, next) {\n\t\tNOSQL('task-sessions').read().where('id', meta.sessionid).callback(function(err, session) {\n\t\t\tvar user = !err && session && session.expire > Date.now() && MAIN.taskUsers[session.userid];\n\t\t\tuser ? next(null, { id: user.id, name: user.name, role: user.role }) : next('Session not found');\n\t\t});\n\t},\n\tonlogout: function(session) {\n\t\tNOSQL('task-sessions').remove().where('id', session.sessionid).callback(NOOP);\n\t},\n\tonremove: function(id) {\n\t\tNOSQL('task-sessions').remove().where('id', id).callback(NOOP);\n\t}\n};\nAUTH(MAIN.taskAuth);\n\nMIDDLEWARE('taskcsrf', function($, next) {\n\tDEF.onCSRFcheck($) ? next() : $.invalid(403);\n});\n\n// Sends an event to the sockets of the owner of a task and to the administrators only\nMAIN.live = function(owner, event) {\n\tMAIN.liveSocket && MAIN.liveSocket.send(event, function(client) {\n\t\treturn client.user.role === 'admin' || client.user.id === owner;\n\t});\n};\n```\n\n```js actions/tasks.js\nfunction owned(builder, user) {\n\treturn user.role === 'admin' ? builder : builder.where('owner', user.id);\n}\n\nNEWACTION('Tasks|list', {\n\tuser: true,\n\taction: function($) {\n\t\towned(NOSQL('tasks').find(), $.user).sort('title').callback(function(err, tasks) {\n\t\t\terr ? $.invalid(500) : $.callback(tasks);\n\t\t});\n\t}\n});\n\nNEWACTION('Tasks|create', {\n\tuser: true,\n\tinput: '*title:String(80)',\n\taction: function($, model) {\n\t\tvar task = { id: UID(), title: model.title, done: false, owner: $.user.id };\n\t\tNOSQL('tasks').insert(task).callback(function(err) {\n\t\t\tif (err)\n\t\t\t\treturn $.invalid(500);\n\t\t\tMAIN.live(task.owner, { type: 'task', id: task.id });\n\t\t\t$.callback(task);\n\t\t});\n\t}\n});\n\nNEWACTION('Tasks|update', {\n\tuser: true,\n\tparams: '*id:UID',\n\tinput: '*done:Boolean',\n\taction: function($, model) {\n\t\towned(NOSQL('tasks').update({ done: model.done }).where('id', $.params.id), $.user).callback(function(err, count) {\n\t\t\tif (err)\n\t\t\t\treturn $.invalid(500);\n\t\t\tif (!count)\n\t\t\t\treturn $.invalid(404);\n\t\t\tNOSQL('tasks').read().where('id', $.params.id).callback(function(err, task) {\n\t\t\t\ttask && MAIN.live(task.owner, { type: 'task', id: task.id });\n\t\t\t\t$.success();\n\t\t\t});\n\t\t});\n\t}\n});\n\nNEWACTION('Tasks|remove', {\n\tuser: true,\n\tparams: '*id:UID',\n\taction: function($) {\n\t\tif ($.user.role !== 'admin')\n\t\t\treturn $.invalid(403);\n\t\tNOSQL('tasks').read().where('id', $.params.id).callback(function(err, task) {\n\t\t\tif (err)\n\t\t\t\treturn $.invalid(500);\n\t\t\tif (!task)\n\t\t\t\treturn $.invalid(404);\n\t\t\t// The attachments of the task go away with it\n\t\t\tNOSQL('attachments').find().where('taskid', task.id).callback(function(err, files) {\n\t\t\t\t(files || []).forEach(file => FILESTORAGE(CONF.fs).remove(file.id, NOOP));\n\t\t\t\tNOSQL('attachments').remove().where('taskid', task.id).callback(NOOP);\n\t\t\t\tNOSQL('tasks').remove().where('id', task.id).callback(function(err) {\n\t\t\t\t\tif (err)\n\t\t\t\t\t\treturn $.invalid(500);\n\t\t\t\t\tMAIN.live(task.owner, { type: 'task', id: task.id });\n\t\t\t\t\t$.success();\n\t\t\t\t});\n\t\t\t});\n\t\t});\n\t}\n});\n```\n\n```js controllers/taskboard.js\nvar attempts = Object.create(null);\nvar allowed = ['text/plain', 'image/png', 'application/pdf'];\n\nexports.install = function() {\n\tROUTE('GET /', function($) { $.file(PATH.public('index.html')); });\n\tROUTE('GET /session/', session);\n\tROUTE('POST /login/ @json <2kb #taskcsrf', login);\n\tROUTE('+POST /logout/ @json #taskcsrf', logout);\n\tROUTE('+GET /tasks/ --> *Tasks|list');\n\tROUTE('+POST /tasks/ <2kb #taskcsrf --> *Tasks|create');\n\tROUTE('+PATCH /tasks/{id}/ <2kb #taskcsrf --> *Tasks|update');\n\tROUTE('+DELETE /tasks/{id}/ #taskcsrf --> *Tasks|remove');\n\tROUTE('+GET /tasks/{id}/files/', files);\n\tROUTE('+POST /tasks/{id}/files/ @upload <1mb #taskcsrf', attach);\n\tROUTE('+GET /tasks/{id}/files/{fileid}/', download);\n\tROUTE('+SOCKET /live/', live);\n};\n\n// The task must be visible to the user: members only reach their own tasks\nfunction visible($, next) {\n\tvar builder = NOSQL('tasks').read().where('id', $.params.id);\n\tif ($.user.role !== 'admin')\n\t\tbuilder.where('owner', $.user.id);\n\tbuilder.callback(function(err, task) {\n\t\tif (err)\n\t\t\treturn $.invalid(500);\n\t\ttask ? next(task) : $.invalid(404);\n\t});\n}\n\nfunction files($) {\n\tvisible($, function(task) {\n\t\tNOSQL('attachments').find().where('taskid', task.id).sort('filename').fields('id,filename,size,type').callback(function(err, items) {\n\t\t\terr ? $.invalid(500) : $.json(items);\n\t\t});\n\t});\n}\n\nfunction attach($) {\n\tvar file = $.files[0];\n\tif (!file || !allowed.includes(file.type))\n\t\treturn $.invalid(400);\n\tvisible($, function(task) {\n\t\tvar id = UID();\n\t\tfile.fs(CONF.fs, id, function(err) {\n\t\t\tif (err)\n\t\t\t\treturn $.invalid(500);\n\t\t\tvar meta = { id: id, taskid: task.id, filename: file.filename, size: file.size, type: file.type };\n\t\t\tNOSQL('attachments').insert(meta).callback(function(err) {\n\t\t\t\tif (err)\n\t\t\t\t\treturn $.invalid(500);\n\t\t\t\tMAIN.live(task.owner, { type: 'attachment', id: task.id });\n\t\t\t\t$.json({ id: id, filename: meta.filename, size: meta.size, type: meta.type });\n\t\t\t});\n\t\t});\n\t});\n}\n\nfunction download($) {\n\tvisible($, function(task) {\n\t\tNOSQL('attachments').read().where('id', $.params.fileid).where('taskid', task.id).callback(function(err, meta) {\n\t\t\tif (err || !meta)\n\t\t\t\treturn $.invalid(404);\n\t\t\t$.response.headers['x-content-type-options'] = 'nosniff';\n\t\t\t$.filefs(CONF.fs, meta.id, true);\n\t\t});\n\t});\n}\n\n// Only a signed-in user reaches this route; the sockets are kept to send events to them\nfunction live() {\n\tMAIN.liveSocket = this;\n}\n\nfunction session($) {\n\t$.response.headers['cache-control'] = 'no-store';\n\t$.json({ user: $.user || null, csrf: $.csrf() });\n}\n\nfunction login($) {\n\tvar key = $.ip;\n\tvar recent = (attempts[key] || []).filter(time => time > Date.now() - 600000);\n\tattempts[key] = recent;\n\tif (recent.length >= 5) return $.invalid(429);\n\tvar user = MAIN.taskUsers[String($.body.name || '').toLowerCase()];\n\tvar salt = user ? user.salt : 'dummy-salt';\n\tvar actual = Total.Crypto.scryptSync(String($.body.password || ''), salt, 32);\n\tvar valid = user && Total.Crypto.timingSafeEqual(actual, user.hash);\n\tif (!valid) {\n\t\trecent.push(Date.now());\n\t\treturn $.invalid(401);\n\t}\n\tvar id = UID();\n\tNOSQL('task-sessions').insert({ id: id, userid: user.id, expire: Date.now() + 1800000 }).callback(function(err) {\n\t\tif (err) return $.invalid(500);\n\t\tMAIN.taskAuth.authcookie($, id, user.id, '30 minutes');\n\t\t$.json({ user: { id: user.id, name: user.name, role: user.role }, csrf: $.csrf() });\n\t});\n}\n\nfunction logout($) {\n\tNOSQL('task-sessions').remove().where('id', $.sessionid).callback(function(err) {\n\t\tif (err) return $.invalid(500);\n\t\tMAIN.taskAuth.logout($);\n\t\t$.cookie(MAIN.taskAuth.cookie, '', '-1 year', MAIN.taskAuth.options);\n\t\t$.json({ ok: true });\n\t});\n}\n```\n\n```html public/index.html\n<!DOCTYPE html>\n<html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Taskboard Live · Total.js</title>\n<style>\nbody{font:16px system-ui;background:#f4f5f7;color:#172033;margin:0}main{max-width:720px;margin:8vh auto;padding:30px;background:white;border-radius:18px}header{display:flex;align-items:center;justify-content:space-between;gap:15px}h1{margin:0}p{color:#536078}label{display:block;margin-top:14px}input,button{font:inherit;padding:10px;border:1px solid #bbc4d1;border-radius:8px}input[type=text],input[type=password]{box-sizing:border-box;width:100%;margin-top:5px}button{cursor:pointer;background:#152d57;color:white}form button{margin-top:15px}ul{padding:0;list-style:none}li.task{padding:14px 0;border-bottom:1px solid #eee}.row{display:flex;align-items:center;gap:10px}.task-title{flex:1;overflow-wrap:anywhere}.muted{font-size:14px;color:#536078}.files{margin:8px 0 0 30px;padding:0;font-size:14px}#message{min-height:24px;color:#9d251e}#live{font-size:13px;color:#2a7a3b}[hidden]{display:none!important}\n</style></head><body><main>\n<header><h1>Taskboard Live</h1><span class=\"muted\">Total.js reference</span></header>\n<p>Private tasks with attachments, updated live for the people who may see them.</p>\n<p id=\"message\" role=\"status\" aria-live=\"polite\"></p>\n<form id=\"login\"><h2>Sign in</h2><label for=\"name\">User</label><input id=\"name\" autocomplete=\"username\" required><label for=\"password\">Password</label><input id=\"password\" type=\"password\" autocomplete=\"current-password\" required><button>Sign in</button></form>\n<section id=\"workspace\" hidden><header><p id=\"identity\"></p><span id=\"live\" hidden>● live</span><button id=\"logout\" type=\"button\">Sign out</button></header>\n<form id=\"create\"><label for=\"title\">New task</label><input id=\"title\" type=\"text\" maxlength=\"80\" required><button>Add task</button></form>\n<ul id=\"tasks\" aria-label=\"Tasks\"></ul><p id=\"empty\">No tasks yet.</p></section>\n</main><script src=\"/app.js\"></script></body></html>\n```\n\n```js public/app.js\n(function() {\n\tvar user = null;\n\tvar csrf = '';\n\tvar busy = false;\n\tvar socket = null;\n\tvar byid = id => document.getElementById(id);\n\tasync function request(path, method, body) {\n\t\tvar form = body instanceof FormData;\n\t\tvar headers = { 'x-csrf-token': csrf };\n\t\tif (!form) headers['content-type'] = 'application/json';\n\t\tvar response = await fetch(path, { method: method || 'GET', credentials: 'same-origin', headers: headers, body: body === undefined ? undefined : form ? body : JSON.stringify(body) });\n\t\tvar data = await response.json().catch(() => null);\n\t\tif (!response.ok) {\n\t\t\tif (response.status === 401) { user = null; show(); }\n\t\t\tthrow new Error('Request refused (' + response.status + ')');\n\t\t}\n\t\tif (data === null) throw new Error('Unexpected server response');\n\t\treturn data;\n\t}\n\tfunction show() {\n\t\tbyid('login').hidden = !!user;\n\t\tbyid('workspace').hidden = !user;\n\t\tbyid('identity').textContent = user ? user.name + ' · ' + user.role : '';\n\t\tif (!user && socket) { socket.close(); socket = null; byid('live').hidden = true; }\n\t}\n\t// The server pushes an event whenever something the user may see changes\n\tfunction connect() {\n\t\tif (socket) return;\n\t\tsocket = new WebSocket((location.protocol === 'https:' ? 'wss://' : 'ws://') + location.host + '/live/');\n\t\tsocket.onopen = () => { byid('live').hidden = false; };\n\t\tsocket.onclose = () => { byid('live').hidden = true; socket = null; };\n\t\tsocket.onmessage = () => { if (user && !busy) load().catch(err => byid('message').textContent = err.message); };\n\t}\n\tasync function load() {\n\t\tvar tasks = await request('/tasks/');\n\t\tvar lists = await Promise.all(tasks.map(task => request('/tasks/' + task.id + '/files/')));\n\t\tbyid('tasks').replaceChildren();\n\t\tbyid('empty').hidden = tasks.length > 0;\n\t\ttasks.forEach(function(task, index) {\n\t\t\tvar row = document.createElement('li');\n\t\t\trow.className = 'task';\n\t\t\tvar line = document.createElement('div');\n\t\t\tline.className = 'row';\n\t\t\tvar checkbox = document.createElement('input');\n\t\t\tcheckbox.type = 'checkbox'; checkbox.checked = task.done; checkbox.setAttribute('aria-label', 'Complete ' + task.title);\n\t\t\tcheckbox.addEventListener('change', () => work(async () => { await request('/tasks/' + task.id + '/', 'PATCH', { done: checkbox.checked }); await load(); }));\n\t\t\tvar title = document.createElement('span'); title.className = 'task-title'; title.textContent = task.title;\n\t\t\tvar upload = document.createElement('input');\n\t\t\tupload.type = 'file'; upload.accept = '.txt,.png,.pdf'; upload.setAttribute('aria-label', 'Attach a file to ' + task.title);\n\t\t\tupload.addEventListener('change', () => work(async () => {\n\t\t\t\tvar form = new FormData(); form.append('file', upload.files[0]);\n\t\t\t\tawait request('/tasks/' + task.id + '/files/', 'POST', form); await load();\n\t\t\t}));\n\t\t\tline.append(checkbox, title, upload);\n\t\t\tif (user.role === 'admin') {\n\t\t\t\tvar remove = document.createElement('button'); remove.type = 'button'; remove.textContent = 'Delete';\n\t\t\t\tremove.addEventListener('click', () => work(async () => { await request('/tasks/' + task.id + '/', 'DELETE'); await load(); }));\n\t\t\t\tline.append(remove);\n\t\t\t}\n\t\t\trow.append(line);\n\t\t\tvar list = document.createElement('ul'); list.className = 'files';\n\t\t\tlists[index].forEach(function(file) {\n\t\t\t\tvar item = document.createElement('li');\n\t\t\t\tvar link = document.createElement('a');\n\t\t\t\tlink.href = '/tasks/' + task.id + '/files/' + file.id + '/'; link.textContent = file.filename;\n\t\t\t\titem.append(link, ' (' + file.size + ' bytes)');\n\t\t\t\tlist.append(item);\n\t\t\t});\n\t\t\trow.append(list);\n\t\t\tbyid('tasks').append(row);\n\t\t});\n\t}\n\tasync function work(fn) {\n\t\tif (busy) return;\n\t\tbusy = true; byid('message').textContent = '';\n\t\tdocument.querySelectorAll('input,button').forEach(control => control.disabled = true);\n\t\ttry { await fn(); } catch (err) { byid('message').textContent = err.message; }\n\t\tfinally { busy = false; document.querySelectorAll('input,button').forEach(control => control.disabled = false); }\n\t}\n\tbyid('login').addEventListener('submit', function(event) {\n\t\tevent.preventDefault(); work(async function() {\n\t\t\tvar result = await request('/login/', 'POST', { name: byid('name').value, password: byid('password').value });\n\t\t\tuser = result.user; csrf = result.csrf; byid('password').value = ''; show(); connect(); await load();\n\t\t});\n\t});\n\tbyid('create').addEventListener('submit', function(event) {\n\t\tevent.preventDefault(); work(async function() { await request('/tasks/', 'POST', { title: byid('title').value }); byid('title').value = ''; await load(); });\n\t});\n\tbyid('logout').addEventListener('click', () => work(async function() { await request('/logout/', 'POST', {}); user = null; byid('tasks').replaceChildren(); show(); }));\n\twork(async function() { var result = await request('/session/'); user = result.user; csrf = result.csrf; show(); if (user) { connect(); await load(); } });\n})();\n```\n\n## What is exercised\n\nThe application applies these access and update rules:\n\n- an anonymous socket is refused and the sockets of the owner and of the administrator receive a task event while the socket of another member receives nothing,\n- an attachment is accepted only from a user who can see the task, only with a valid CSRF token and only for the allowed types, and files over the limit are refused,\n- the owner and the administrator download the file with the right headers while another member gets a 404,\n- an administrator deleting the task removes its attachments too.\n\nConnected pages receive task updates through the socket without reloading. Related recipes: [WebSocket flags](/en/total5/websocket-flags/), [file storage](/en/total5/filestorage/), [uploads](/en/total5/file-upload/) and [CSRF](/en/total5/csrf-protection/)."}],"knowledge":{"content_sha256":"85e51cdc38974e26afe30a84dcf75b118da5711946e5cb061c205315d4675c65","source_sha256":"d4566e540f8c8005a02645050f93a2997242e376d8040aa01e3508f68d8f7055","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["AUTH","CONF","CSRF","DEF","DELETE","DOCTYPE","Date.now","FILESTORAGE","GET","JSON","MAIN","MIDDLEWARE","NEWACTION","NODE_ENV","NOOP","NOSQL","PATCH","PATH","POST","ROUTE","SOCKET","TASKBOARD_","TASKBOARD_SECRET","Total.Crypto","Total.Crypto.scryptSync","Total.Crypto.timingSafeEqual","Total.js","UID"],"language":"en"},"files":[{"path":"config","language":"text","content":"name : Taskboard Live\ndemo : true\nfs : taskboard-files"},{"path":"definitions/taskboard.js","language":"js","content":"var crypto = Total.Crypto;\nvar demo = CONF.demo === true;\nif (demo && (process.argv.includes('--release') || process.env.NODE_ENV === 'production'))\n\tthrow new Error('Disable demo mode before a release deployment');\nvar secret = process.env.TASKBOARD_SECRET || (demo ? 'local-taskboard-demo-secret-not-for-public-use' : '');\nif (secret.length < 32)\n\tthrow new Error('TASKBOARD_SECRET needs at least 32 characters');\nCONF.secret_csrf = secret;\n\nMAIN.taskUsers = Object.create(null);\nfor (var name of ['alice', 'bob', 'admin']) {\n\tvar password = process.env['TASKBOARD_' + name.toUpperCase() + '_PASSWORD'] || (demo ? name + '-demo-password' : '');\n\tif (password.length < 12)\n\t\tthrow new Error('Configure a password of at least 12 characters for ' + name);\n\tvar salt = crypto.randomBytes(16).toString('hex');\n\tMAIN.taskUsers[name] = { id: name, name: name, role: name === 'admin' ? 'admin' : 'member', salt: salt, hash: crypto.scryptSync(password, salt, 32) };\n}\n\nMAIN.taskAuth = {\n\tsecret: secret,\n\tcookie: 'taskboard_session',\n\texpire: '30 minutes',\n\toptions: { httponly: true, samesite: 'Strict', secure: !demo },\n\tonread: function(meta, next) {\n\t\tNOSQL('task-sessions').read().where('id', meta.sessionid).callback(function(err, session) {\n\t\t\tvar user = !err && session && session.expire > Date.now() && MAIN.taskUsers[session.userid];\n\t\t\tuser ? next(null, { id: user.id, name: user.name, role: user.role }) : next('Session not found');\n\t\t});\n\t},\n\tonlogout: function(session) {\n\t\tNOSQL('task-sessions').remove().where('id', session.sessionid).callback(NOOP);\n\t},\n\tonremove: function(id) {\n\t\tNOSQL('task-sessions').remove().where('id', id).callback(NOOP);\n\t}\n};\nAUTH(MAIN.taskAuth);\n\nMIDDLEWARE('taskcsrf', function($, next) {\n\tDEF.onCSRFcheck($) ? next() : $.invalid(403);\n});\n\n// Sends an event to the sockets of the owner of a task and to the administrators only\nMAIN.live = function(owner, event) {\n\tMAIN.liveSocket && MAIN.liveSocket.send(event, function(client) {\n\t\treturn client.user.role === 'admin' || client.user.id === owner;\n\t});\n};"},{"path":"actions/tasks.js","language":"js","content":"function owned(builder, user) {\n\treturn user.role === 'admin' ? builder : builder.where('owner', user.id);\n}\n\nNEWACTION('Tasks|list', {\n\tuser: true,\n\taction: function($) {\n\t\towned(NOSQL('tasks').find(), $.user).sort('title').callback(function(err, tasks) {\n\t\t\terr ? $.invalid(500) : $.callback(tasks);\n\t\t});\n\t}\n});\n\nNEWACTION('Tasks|create', {\n\tuser: true,\n\tinput: '*title:String(80)',\n\taction: function($, model) {\n\t\tvar task = { id: UID(), title: model.title, done: false, owner: $.user.id };\n\t\tNOSQL('tasks').insert(task).callback(function(err) {\n\t\t\tif (err)\n\t\t\t\treturn $.invalid(500);\n\t\t\tMAIN.live(task.owner, { type: 'task', id: task.id });\n\t\t\t$.callback(task);\n\t\t});\n\t}\n});\n\nNEWACTION('Tasks|update', {\n\tuser: true,\n\tparams: '*id:UID',\n\tinput: '*done:Boolean',\n\taction: function($, model) {\n\t\towned(NOSQL('tasks').update({ done: model.done }).where('id', $.params.id), $.user).callback(function(err, count) {\n\t\t\tif (err)\n\t\t\t\treturn $.invalid(500);\n\t\t\tif (!count)\n\t\t\t\treturn $.invalid(404);\n\t\t\tNOSQL('tasks').read().where('id', $.params.id).callback(function(err, task) {\n\t\t\t\ttask && MAIN.live(task.owner, { type: 'task', id: task.id });\n\t\t\t\t$.success();\n\t\t\t});\n\t\t});\n\t}\n});\n\nNEWACTION('Tasks|remove', {\n\tuser: true,\n\tparams: '*id:UID',\n\taction: function($) {\n\t\tif ($.user.role !== 'admin')\n\t\t\treturn $.invalid(403);\n\t\tNOSQL('tasks').read().where('id', $.params.id).callback(function(err, task) {\n\t\t\tif (err)\n\t\t\t\treturn $.invalid(500);\n\t\t\tif (!task)\n\t\t\t\treturn $.invalid(404);\n\t\t\t// The attachments of the task go away with it\n\t\t\tNOSQL('attachments').find().where('taskid', task.id).callback(function(err, files) {\n\t\t\t\t(files || []).forEach(file => FILESTORAGE(CONF.fs).remove(file.id, NOOP));\n\t\t\t\tNOSQL('attachments').remove().where('taskid', task.id).callback(NOOP);\n\t\t\t\tNOSQL('tasks').remove().where('id', task.id).callback(function(err) {\n\t\t\t\t\tif (err)\n\t\t\t\t\t\treturn $.invalid(500);\n\t\t\t\t\tMAIN.live(task.owner, { type: 'task', id: task.id });\n\t\t\t\t\t$.success();\n\t\t\t\t});\n\t\t\t});\n\t\t});\n\t}\n});"},{"path":"controllers/taskboard.js","language":"js","content":"var attempts = Object.create(null);\nvar allowed = ['text/plain', 'image/png', 'application/pdf'];\n\nexports.install = function() {\n\tROUTE('GET /', function($) { $.file(PATH.public('index.html')); });\n\tROUTE('GET /session/', session);\n\tROUTE('POST /login/ @json <2kb #taskcsrf', login);\n\tROUTE('+POST /logout/ @json #taskcsrf', logout);\n\tROUTE('+GET /tasks/ --> *Tasks|list');\n\tROUTE('+POST /tasks/ <2kb #taskcsrf --> *Tasks|create');\n\tROUTE('+PATCH /tasks/{id}/ <2kb #taskcsrf --> *Tasks|update');\n\tROUTE('+DELETE /tasks/{id}/ #taskcsrf --> *Tasks|remove');\n\tROUTE('+GET /tasks/{id}/files/', files);\n\tROUTE('+POST /tasks/{id}/files/ @upload <1mb #taskcsrf', attach);\n\tROUTE('+GET /tasks/{id}/files/{fileid}/', download);\n\tROUTE('+SOCKET /live/', live);\n};\n\n// The task must be visible to the user: members only reach their own tasks\nfunction visible($, next) {\n\tvar builder = NOSQL('tasks').read().where('id', $.params.id);\n\tif ($.user.role !== 'admin')\n\t\tbuilder.where('owner', $.user.id);\n\tbuilder.callback(function(err, task) {\n\t\tif (err)\n\t\t\treturn $.invalid(500);\n\t\ttask ? next(task) : $.invalid(404);\n\t});\n}\n\nfunction files($) {\n\tvisible($, function(task) {\n\t\tNOSQL('attachments').find().where('taskid', task.id).sort('filename').fields('id,filename,size,type').callback(function(err, items) {\n\t\t\terr ? $.invalid(500) : $.json(items);\n\t\t});\n\t});\n}\n\nfunction attach($) {\n\tvar file = $.files[0];\n\tif (!file || !allowed.includes(file.type))\n\t\treturn $.invalid(400);\n\tvisible($, function(task) {\n\t\tvar id = UID();\n\t\tfile.fs(CONF.fs, id, function(err) {\n\t\t\tif (err)\n\t\t\t\treturn $.invalid(500);\n\t\t\tvar meta = { id: id, taskid: task.id, filename: file.filename, size: file.size, type: file.type };\n\t\t\tNOSQL('attachments').insert(meta).callback(function(err) {\n\t\t\t\tif (err)\n\t\t\t\t\treturn $.invalid(500);\n\t\t\t\tMAIN.live(task.owner, { type: 'attachment', id: task.id });\n\t\t\t\t$.json({ id: id, filename: meta.filename, size: meta.size, type: meta.type });\n\t\t\t});\n\t\t});\n\t});\n}\n\nfunction download($) {\n\tvisible($, function(task) {\n\t\tNOSQL('attachments').read().where('id', $.params.fileid).where('taskid', task.id).callback(function(err, meta) {\n\t\t\tif (err || !meta)\n\t\t\t\treturn $.invalid(404);\n\t\t\t$.response.headers['x-content-type-options'] = 'nosniff';\n\t\t\t$.filefs(CONF.fs, meta.id, true);\n\t\t});\n\t});\n}\n\n// Only a signed-in user reaches this route; the sockets are kept to send events to them\nfunction live() {\n\tMAIN.liveSocket = this;\n}\n\nfunction session($) {\n\t$.response.headers['cache-control'] = 'no-store';\n\t$.json({ user: $.user || null, csrf: $.csrf() });\n}\n\nfunction login($) {\n\tvar key = $.ip;\n\tvar recent = (attempts[key] || []).filter(time => time > Date.now() - 600000);\n\tattempts[key] = recent;\n\tif (recent.length >= 5) return $.invalid(429);\n\tvar user = MAIN.taskUsers[String($.body.name || '').toLowerCase()];\n\tvar salt = user ? user.salt : 'dummy-salt';\n\tvar actual = Total.Crypto.scryptSync(String($.body.password || ''), salt, 32);\n\tvar valid = user && Total.Crypto.timingSafeEqual(actual, user.hash);\n\tif (!valid) {\n\t\trecent.push(Date.now());\n\t\treturn $.invalid(401);\n\t}\n\tvar id = UID();\n\tNOSQL('task-sessions').insert({ id: id, userid: user.id, expire: Date.now() + 1800000 }).callback(function(err) {\n\t\tif (err) return $.invalid(500);\n\t\tMAIN.taskAuth.authcookie($, id, user.id, '30 minutes');\n\t\t$.json({ user: { id: user.id, name: user.name, role: user.role }, csrf: $.csrf() });\n\t});\n}\n\nfunction logout($) {\n\tNOSQL('task-sessions').remove().where('id', $.sessionid).callback(function(err) {\n\t\tif (err) return $.invalid(500);\n\t\tMAIN.taskAuth.logout($);\n\t\t$.cookie(MAIN.taskAuth.cookie, '', '-1 year', MAIN.taskAuth.options);\n\t\t$.json({ ok: true });\n\t});\n}"},{"path":"public/index.html","language":"html","content":"<!DOCTYPE html>\n<html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Taskboard Live · Total.js</title>\n<style>\nbody{font:16px system-ui;background:#f4f5f7;color:#172033;margin:0}main{max-width:720px;margin:8vh auto;padding:30px;background:white;border-radius:18px}header{display:flex;align-items:center;justify-content:space-between;gap:15px}h1{margin:0}p{color:#536078}label{display:block;margin-top:14px}input,button{font:inherit;padding:10px;border:1px solid #bbc4d1;border-radius:8px}input[type=text],input[type=password]{box-sizing:border-box;width:100%;margin-top:5px}button{cursor:pointer;background:#152d57;color:white}form button{margin-top:15px}ul{padding:0;list-style:none}li.task{padding:14px 0;border-bottom:1px solid #eee}.row{display:flex;align-items:center;gap:10px}.task-title{flex:1;overflow-wrap:anywhere}.muted{font-size:14px;color:#536078}.files{margin:8px 0 0 30px;padding:0;font-size:14px}#message{min-height:24px;color:#9d251e}#live{font-size:13px;color:#2a7a3b}[hidden]{display:none!important}\n</style></head><body><main>\n<header><h1>Taskboard Live</h1><span class=\"muted\">Total.js reference</span></header>\n<p>Private tasks with attachments, updated live for the people who may see them.</p>\n<p id=\"message\" role=\"status\" aria-live=\"polite\"></p>\n<form id=\"login\"><h2>Sign in</h2><label for=\"name\">User</label><input id=\"name\" autocomplete=\"username\" required><label for=\"password\">Password</label><input id=\"password\" type=\"password\" autocomplete=\"current-password\" required><button>Sign in</button></form>\n<section id=\"workspace\" hidden><header><p id=\"identity\"></p><span id=\"live\" hidden>● live</span><button id=\"logout\" type=\"button\">Sign out</button></header>\n<form id=\"create\"><label for=\"title\">New task</label><input id=\"title\" type=\"text\" maxlength=\"80\" required><button>Add task</button></form>\n<ul id=\"tasks\" aria-label=\"Tasks\"></ul><p id=\"empty\">No tasks yet.</p></section>\n</main><script src=\"/app.js\"></script></body></html>"},{"path":"public/app.js","language":"js","content":"(function() {\n\tvar user = null;\n\tvar csrf = '';\n\tvar busy = false;\n\tvar socket = null;\n\tvar byid = id => document.getElementById(id);\n\tasync function request(path, method, body) {\n\t\tvar form = body instanceof FormData;\n\t\tvar headers = { 'x-csrf-token': csrf };\n\t\tif (!form) headers['content-type'] = 'application/json';\n\t\tvar response = await fetch(path, { method: method || 'GET', credentials: 'same-origin', headers: headers, body: body === undefined ? undefined : form ? body : JSON.stringify(body) });\n\t\tvar data = await response.json().catch(() => null);\n\t\tif (!response.ok) {\n\t\t\tif (response.status === 401) { user = null; show(); }\n\t\t\tthrow new Error('Request refused (' + response.status + ')');\n\t\t}\n\t\tif (data === null) throw new Error('Unexpected server response');\n\t\treturn data;\n\t}\n\tfunction show() {\n\t\tbyid('login').hidden = !!user;\n\t\tbyid('workspace').hidden = !user;\n\t\tbyid('identity').textContent = user ? user.name + ' · ' + user.role : '';\n\t\tif (!user && socket) { socket.close(); socket = null; byid('live').hidden = true; }\n\t}\n\t// The server pushes an event whenever something the user may see changes\n\tfunction connect() {\n\t\tif (socket) return;\n\t\tsocket = new WebSocket((location.protocol === 'https:' ? 'wss://' : 'ws://') + location.host + '/live/');\n\t\tsocket.onopen = () => { byid('live').hidden = false; };\n\t\tsocket.onclose = () => { byid('live').hidden = true; socket = null; };\n\t\tsocket.onmessage = () => { if (user && !busy) load().catch(err => byid('message').textContent = err.message); };\n\t}\n\tasync function load() {\n\t\tvar tasks = await request('/tasks/');\n\t\tvar lists = await Promise.all(tasks.map(task => request('/tasks/' + task.id + '/files/')));\n\t\tbyid('tasks').replaceChildren();\n\t\tbyid('empty').hidden = tasks.length > 0;\n\t\ttasks.forEach(function(task, index) {\n\t\t\tvar row = document.createElement('li');\n\t\t\trow.className = 'task';\n\t\t\tvar line = document.createElement('div');\n\t\t\tline.className = 'row';\n\t\t\tvar checkbox = document.createElement('input');\n\t\t\tcheckbox.type = 'checkbox'; checkbox.checked = task.done; checkbox.setAttribute('aria-label', 'Complete ' + task.title);\n\t\t\tcheckbox.addEventListener('change', () => work(async () => { await request('/tasks/' + task.id + '/', 'PATCH', { done: checkbox.checked }); await load(); }));\n\t\t\tvar title = document.createElement('span'); title.className = 'task-title'; title.textContent = task.title;\n\t\t\tvar upload = document.createElement('input');\n\t\t\tupload.type = 'file'; upload.accept = '.txt,.png,.pdf'; upload.setAttribute('aria-label', 'Attach a file to ' + task.title);\n\t\t\tupload.addEventListener('change', () => work(async () => {\n\t\t\t\tvar form = new FormData(); form.append('file', upload.files[0]);\n\t\t\t\tawait request('/tasks/' + task.id + '/files/', 'POST', form); await load();\n\t\t\t}));\n\t\t\tline.append(checkbox, title, upload);\n\t\t\tif (user.role === 'admin') {\n\t\t\t\tvar remove = document.createElement('button'); remove.type = 'button'; remove.textContent = 'Delete';\n\t\t\t\tremove.addEventListener('click', () => work(async () => { await request('/tasks/' + task.id + '/', 'DELETE'); await load(); }));\n\t\t\t\tline.append(remove);\n\t\t\t}\n\t\t\trow.append(line);\n\t\t\tvar list = document.createElement('ul'); list.className = 'files';\n\t\t\tlists[index].forEach(function(file) {\n\t\t\t\tvar item = document.createElement('li');\n\t\t\t\tvar link = document.createElement('a');\n\t\t\t\tlink.href = '/tasks/' + task.id + '/files/' + file.id + '/'; link.textContent = file.filename;\n\t\t\t\titem.append(link, ' (' + file.size + ' bytes)');\n\t\t\t\tlist.append(item);\n\t\t\t});\n\t\t\trow.append(list);\n\t\t\tbyid('tasks').append(row);\n\t\t});\n\t}\n\tasync function work(fn) {\n\t\tif (busy) return;\n\t\tbusy = true; byid('message').textContent = '';\n\t\tdocument.querySelectorAll('input,button').forEach(control => control.disabled = true);\n\t\ttry { await fn(); } catch (err) { byid('message').textContent = err.message; }\n\t\tfinally { busy = false; document.querySelectorAll('input,button').forEach(control => control.disabled = false); }\n\t}\n\tbyid('login').addEventListener('submit', function(event) {\n\t\tevent.preventDefault(); work(async function() {\n\t\t\tvar result = await request('/login/', 'POST', { name: byid('name').value, password: byid('password').value });\n\t\t\tuser = result.user; csrf = result.csrf; byid('password').value = ''; show(); connect(); await load();\n\t\t});\n\t});\n\tbyid('create').addEventListener('submit', function(event) {\n\t\tevent.preventDefault(); work(async function() { await request('/tasks/', 'POST', { title: byid('title').value }); byid('title').value = ''; await load(); });\n\t});\n\tbyid('logout').addEventListener('click', () => work(async function() { await request('/logout/', 'POST', {}); user = null; byid('tasks').replaceChildren(); show(); }));\n\twork(async function() { var result = await request('/session/'); user = result.user; csrf = result.csrf; show(); if (user) { connect(); await load(); } });\n})();"}],"test":"const bootstrap = await get('/session/');\nconst login = async name => {\n\tconst response = await post('/login/', { name, password: name + '-demo-password' }, { headers: { 'x-csrf-token': bootstrap.json.csrf } });\n\tassert(response.status === 200, name + ' signs in');\n\treturn { cookie: response.headers.get('set-cookie').split(';')[0], 'x-csrf-token': response.json.csrf };\n};\nconst alice = await login('alice'), bob = await login('bob'), admin = await login('admin');\n\nlet anonymous = false;\ntry { await socket('/live/'); } catch (e) { anonymous = true; }\nassert(anonymous, 'an anonymous socket is refused');\n\nconst sa = await socket('/live/', { headers: { cookie: alice.cookie } });\nconst sb = await socket('/live/', { headers: { cookie: bob.cookie } });\nconst sd = await socket('/live/', { headers: { cookie: admin.cookie } });\n\nconst created = await post('/tasks/', { title: 'With a file' }, { headers: alice });\nassert(created.status === 200, 'task created');\nconst id = created.json.id;\nconst eventA = await sa.next();\nassert(eventA.type === 'task' && eventA.id === id, 'the owner receives the task event');\nassert((await sd.next()).id === id, 'the administrator receives it too');\nlet leaked = true;\ntry { await sb.next(400); } catch (e) { leaked = false; }\nassert(!leaked, 'another member receives nothing');\n\nconst form = () => { const f = new FormData(); f.append('file', new Blob(['attached note'], { type: 'text/plain' }), 'note.txt'); return f; };\nassert((await post('/tasks/' + id + '/files/', form(), { headers: { cookie: alice.cookie } })).status === 403, 'upload needs the CSRF token');\nassert((await post('/tasks/' + id + '/files/', form(), { headers: bob })).status === 404, 'another member cannot attach to the task');\nconst bad = new FormData(); bad.append('file', new Blob(['<script>'], { type: 'text/html' }), 'x.html');\nassert((await post('/tasks/' + id + '/files/', bad, { headers: alice })).status === 400, 'a type outside the list is refused');\nconst big = new FormData(); big.append('file', new Blob([new Uint8Array(1200000)], { type: 'text/plain' }), 'big.txt');\nassert((await post('/tasks/' + id + '/files/', big, { headers: alice })).status >= 400, 'a file over the limit is refused');\n\nconst saved = await post('/tasks/' + id + '/files/', form(), { headers: alice });\nassert(saved.status === 200 && saved.json.filename === 'note.txt' && saved.json.size === 13, 'the owner attaches a file: ' + saved.body);\nconst fileid = saved.json.id;\nassert((await sa.next()).type === 'attachment', 'the owner receives the attachment event');\n\nconst listed = await get('/tasks/' + id + '/files/', { headers: alice });\nassert(listed.json.length === 1 && listed.json[0].filename === 'note.txt', 'the owner lists the files');\nassert((await get('/tasks/' + id + '/files/', { headers: bob })).status === 404, 'another member cannot list them');\n\nconst downloaded = await get('/tasks/' + id + '/files/' + fileid + '/', { headers: alice });\nassert(downloaded.status === 200 && downloaded.body === 'attached note', 'the owner downloads the file');\nassert(/attachment/i.test(downloaded.headers.get('content-disposition') || '') && downloaded.headers.get('x-content-type-options') === 'nosniff', 'download headers');\nassert((await get('/tasks/' + id + '/files/' + fileid + '/', { headers: admin })).status === 200, 'the administrator downloads it');\nassert((await get('/tasks/' + id + '/files/' + fileid + '/', { headers: bob })).status === 404, 'another member gets a 404');\nassert((await get('/tasks/' + id + '/files/' + fileid + '/')).status === 401, 'an anonymous request is refused');\n\nassert((await send('DELETE', '/tasks/' + id + '/', {}, { headers: admin })).status === 200, 'the administrator deletes the task');\nassert((await get('/tasks/' + id + '/files/' + fileid + '/', { headers: admin })).status === 404, 'its attachment is gone with it');","test_kind":"http"}
{"id":"docs/taskboard-sql","section":"docs","kind":"guide","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/docs/taskboard-sql/","markdown_url":"https://forge.totaljs.es/en/docs/taskboard-sql.md","tags":["application","reference","sql","sqlite","querybuilder","transactions","auth","browser"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"5a4665e828a99f21f508202830fde8872a755a2f06c61112761c456deb792f72","checked_at":"2026-10-20T12:17:34.028Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Taskboard SQL: the same application on SQLite with real SQL and a transaction","description":"Keep the Taskboard sessions, roles and CSRF, store the tasks in a SQLite file through the native QueryBuilder and move completed tasks to an archive in one atomic SQL transaction.","messages":[{"role":"user","content":"Taskboard SQL: the same application on SQLite with real SQL and a transaction\n\nKeep the Taskboard sessions, roles and CSRF, store the tasks in a SQLite file through the native QueryBuilder and move completed tasks to an archive in one atomic SQL transaction."},{"role":"assistant","content":"This guide moves the [Taskboard reference](/en/docs/taskboard-reference/) from embedded NoSQL to **real SQL**. The sessions, the three users, the CSRF protection and the owner rules stay the same. The tasks now live in a SQLite file, and the application talks to them through the native `DB()` QueryBuilder with a small driver that the application owns, exactly as in the [SQLite recipe](/en/total5/query-builder-sqlite/).\n\nThe new piece is a **transaction**: *Archive completed* copies the completed tasks to an archive table and deletes them from the task table in one atomic step, so a task is never in both places or in neither. Everything is bound parameters and allowlists; no value from a request is ever put into the SQL text.\n\nSQLite comes from Node itself (`node:sqlite`, tested here with Node 24), so there is no npm dependency. The driver is synchronous, which is fine for a small application and for this reference; a busy production service would use an asynchronous driver for its database server and the same QueryBuilder calls. Copy the files into a new directory as in the first guide (`demo: true`, the three local users, `total5@0.0.19`, loopback only); delete `taskboard.sqlite` to start from an empty database.\n\n## Server files\n\n```text config\nname : Taskboard SQL\ndemo : true\n```\n\n```js definitions/taskboard.js\nvar crypto = Total.Crypto;\nvar demo = CONF.demo === true;\nif (demo && (process.argv.includes('--release') || process.env.NODE_ENV === 'production'))\n\tthrow new Error('Disable demo mode before a release deployment');\nvar secret = process.env.TASKBOARD_SECRET || (demo ? 'local-taskboard-demo-secret-not-for-public-use' : '');\nif (secret.length < 32)\n\tthrow new Error('TASKBOARD_SECRET needs at least 32 characters');\nCONF.secret_csrf = secret;\n\nMAIN.taskUsers = Object.create(null);\nfor (var name of ['alice', 'bob', 'admin']) {\n\tvar password = process.env['TASKBOARD_' + name.toUpperCase() + '_PASSWORD'] || (demo ? name + '-demo-password' : '');\n\tif (password.length < 12)\n\t\tthrow new Error('Configure a password of at least 12 characters for ' + name);\n\tvar salt = crypto.randomBytes(16).toString('hex');\n\tMAIN.taskUsers[name] = { id: name, name: name, role: name === 'admin' ? 'admin' : 'member', salt: salt, hash: crypto.scryptSync(password, salt, 32) };\n}\n\nMAIN.taskAuth = {\n\tsecret: secret,\n\tcookie: 'taskboard_session',\n\texpire: '30 minutes',\n\toptions: { httponly: true, samesite: 'Strict', secure: !demo },\n\tonread: function(meta, next) {\n\t\tNOSQL('task-sessions').read().where('id', meta.sessionid).callback(function(err, session) {\n\t\t\tvar user = !err && session && session.expire > Date.now() && MAIN.taskUsers[session.userid];\n\t\t\tuser ? next(null, { id: user.id, name: user.name, role: user.role }) : next('Session not found');\n\t\t});\n\t},\n\tonlogout: function(session) {\n\t\tNOSQL('task-sessions').remove().where('id', session.sessionid).callback(NOOP);\n\t},\n\tonremove: function(id) {\n\t\tNOSQL('task-sessions').remove().where('id', id).callback(NOOP);\n\t}\n};\nAUTH(MAIN.taskAuth);\n\nMIDDLEWARE('taskcsrf', function($, next) {\n\tDEF.onCSRFcheck($) ? next() : $.invalid(403);\n});\n```\n\n```js definitions/sqlite.js\nvar DatabaseSync = require('node:sqlite').DatabaseSync;\nvar db = new DatabaseSync(PATH.root('taskboard.sqlite'));\n\ndb.exec('CREATE TABLE IF NOT EXISTS tasks (id TEXT PRIMARY KEY, title TEXT NOT NULL CHECK(length(title) BETWEEN 1 AND 80), done INTEGER NOT NULL DEFAULT 0 CHECK(done IN (0, 1)), owner TEXT NOT NULL, created INTEGER NOT NULL) STRICT');\ndb.exec('CREATE TABLE IF NOT EXISTS task_archive (id TEXT PRIMARY KEY, title TEXT NOT NULL, owner TEXT NOT NULL, created INTEGER NOT NULL, archived INTEGER NOT NULL) STRICT');\n\nvar COLUMNS = ['id', 'title', 'done', 'owner', 'created'];\n\nfunction column(name) {\n\tif (!COLUMNS.includes(name))\n\t\tthrow new Error('Column not allowed');\n\treturn '\"' + name + '\"';\n}\n\nfunction where(options, params) {\n\tvar filters = options.filter.map(function(f) {\n\t\tif (f.type !== 'where' || f.comparer !== '=')\n\t\t\tthrow new Error('Filter not supported');\n\t\tparams.push(f.name === 'done' ? (f.value ? 1 : 0) : f.value);\n\t\treturn column(f.name) + ' = ?';\n\t});\n\treturn filters.length ? ' WHERE ' + filters.join(' AND ') : '';\n}\n\nfunction row(item) {\n\titem.done = item.done === 1;\n\treturn item;\n}\n\n// The only table the QueryBuilder can reach is tasks; values are always bound parameters\nNEWDB('sqlite', function(options, callback) {\n\ttry {\n\t\tif (options.table !== 'tasks')\n\t\t\tthrow new Error('Table not allowed');\n\t\tvar params = [];\n\t\tvar condition = where(options, params);\n\t\tvar result;\n\t\tif (['find', 'read'].includes(options.exec)) {\n\t\t\tvar sort = (options.sort || ['created_asc']).map(function(s) {\n\t\t\t\tvar match = /^(title|created)_(asc|desc)$/.exec(s);\n\t\t\t\tif (!match)\n\t\t\t\t\tthrow new Error('Sort not allowed');\n\t\t\t\treturn column(match[1]) + ' ' + match[2];\n\t\t\t}).join(',');\n\t\t\tvar rows = db.prepare('SELECT id, title, done, owner, created FROM tasks' + condition + ' ORDER BY ' + sort + ' LIMIT 200').all(...params).map(row);\n\t\t\tresult = options.exec === 'read' ? rows[0] || null : rows;\n\t\t} else if (options.exec === 'insert') {\n\t\t\tvar p = options.payload;\n\t\t\tdb.prepare('INSERT INTO tasks (id, title, done, owner, created) VALUES (?, ?, ?, ?, ?)').run(p.id, p.title, p.done ? 1 : 0, p.owner, p.created);\n\t\t\tresult = { id: p.id };\n\t\t} else if (options.exec === 'update') {\n\t\t\tif (!condition)\n\t\t\t\tthrow new Error('Filter required');\n\t\t\tvar keys = Object.keys(options.payload);\n\t\t\tvar set = keys.map(key => column(key) + ' = ?').join(',');\n\t\t\tresult = db.prepare('UPDATE tasks SET ' + set + condition).run(...keys.map(key => key === 'done' ? (options.payload[key] ? 1 : 0) : options.payload[key]), ...params).changes;\n\t\t} else if (options.exec === 'remove') {\n\t\t\tif (!condition)\n\t\t\t\tthrow new Error('Filter required');\n\t\t\tresult = db.prepare('DELETE FROM tasks' + condition).run(...params).changes;\n\t\t} else\n\t\t\tthrow new Error('Operation not supported');\n\t\tcallback(null, result);\n\t} catch (e) {\n\t\tcallback(e);\n\t}\n});\n\n// One atomic step: the completed tasks (of one owner, or of everybody for the administrator) move to the archive.\n// Either both statements are applied or none is.\nMAIN.archiveTasks = function(owner) {\n\tvar params = owner ? [owner] : [];\n\tvar filter = owner ? ' AND owner = ?' : '';\n\tdb.exec('BEGIN IMMEDIATE');\n\ttry {\n\t\tdb.prepare('INSERT INTO task_archive (id, title, owner, created, archived) SELECT id, title, owner, created, ? FROM tasks WHERE done = 1' + filter).run(Date.now(), ...params);\n\t\tvar moved = db.prepare('DELETE FROM tasks WHERE done = 1' + filter).run(...params).changes;\n\t\tdb.exec('COMMIT');\n\t\treturn moved;\n\t} catch (e) {\n\t\tdb.exec('ROLLBACK');\n\t\tthrow e;\n\t}\n};\n\nMAIN.archivedTasks = function(owner) {\n\tvar filter = owner ? ' WHERE owner = ?' : '';\n\treturn db.prepare('SELECT id, title, owner FROM task_archive' + filter + ' ORDER BY archived DESC, id LIMIT 200').all(...(owner ? [owner] : []));\n};\n```\n\n```js actions/tasks.js\nfunction owned(builder, user) {\n\treturn user.role === 'admin' ? builder : builder.where('owner', user.id);\n}\n\nNEWACTION('Tasks|list', {\n\tuser: true,\n\taction: function($) {\n\t\towned(DB().find('sqlite/tasks').sort('title'), $.user).callback(function(err, tasks) {\n\t\t\terr ? $.invalid(500) : $.callback(tasks);\n\t\t});\n\t}\n});\n\nNEWACTION('Tasks|create', {\n\tuser: true,\n\tinput: '*title:String(80)',\n\taction: function($, model) {\n\t\tvar task = { id: UID(), title: model.title, done: false, owner: $.user.id, created: Date.now() };\n\t\tDB().insert('sqlite/tasks', task).callback(function(err) {\n\t\t\terr ? $.invalid(500) : $.callback(task);\n\t\t});\n\t}\n});\n\nNEWACTION('Tasks|update', {\n\tuser: true,\n\tparams: '*id:UID',\n\tinput: '*done:Boolean',\n\taction: function($, model) {\n\t\towned(DB().update('sqlite/tasks', { done: model.done }).id($.params.id), $.user).callback(function(err, count) {\n\t\t\tif (err)\n\t\t\t\treturn $.invalid(500);\n\t\t\tcount ? $.success() : $.invalid(404);\n\t\t});\n\t}\n});\n\nNEWACTION('Tasks|remove', {\n\tuser: true,\n\tparams: '*id:UID',\n\taction: function($) {\n\t\tif ($.user.role !== 'admin')\n\t\t\treturn $.invalid(403);\n\t\tDB().remove('sqlite/tasks').id($.params.id).callback(function(err, count) {\n\t\t\tif (err)\n\t\t\t\treturn $.invalid(500);\n\t\t\tcount ? $.success() : $.invalid(404);\n\t\t});\n\t}\n});\n\nNEWACTION('Tasks|archive', {\n\tuser: true,\n\taction: function($) {\n\t\ttry {\n\t\t\t$.callback({ moved: MAIN.archiveTasks($.user.role === 'admin' ? null : $.user.id) });\n\t\t} catch (e) {\n\t\t\t$.invalid(500);\n\t\t}\n\t}\n});\n\nNEWACTION('Tasks|archived', {\n\tuser: true,\n\taction: function($) {\n\t\t$.callback(MAIN.archivedTasks($.user.role === 'admin' ? null : $.user.id));\n\t}\n});\n```\n\n```js controllers/taskboard.js\nvar attempts = Object.create(null);\n\nexports.install = function() {\n\tROUTE('GET /', function($) { $.file(PATH.public('index.html')); });\n\tROUTE('GET /session/', session);\n\tROUTE('POST /login/ @json <2kb #taskcsrf', login);\n\tROUTE('+POST /logout/ @json #taskcsrf', logout);\n\tROUTE('+GET /tasks/ --> *Tasks|list');\n\tROUTE('+POST /tasks/ <2kb #taskcsrf --> *Tasks|create');\n\tROUTE('+PATCH /tasks/{id}/ <2kb #taskcsrf --> *Tasks|update');\n\tROUTE('+DELETE /tasks/{id}/ #taskcsrf --> *Tasks|remove');\n\tROUTE('+POST /archive/ #taskcsrf --> *Tasks|archive');\n\tROUTE('+GET /archive/ --> *Tasks|archived');\n};\n\nfunction session($) {\n\t$.response.headers['cache-control'] = 'no-store';\n\t$.json({ user: $.user || null, csrf: $.csrf() });\n}\n\nfunction login($) {\n\tvar key = $.ip;\n\tvar recent = (attempts[key] || []).filter(time => time > Date.now() - 600000);\n\tattempts[key] = recent;\n\tif (recent.length >= 5) return $.invalid(429);\n\tvar user = MAIN.taskUsers[String($.body.name || '').toLowerCase()];\n\tvar salt = user ? user.salt : 'dummy-salt';\n\tvar actual = Total.Crypto.scryptSync(String($.body.password || ''), salt, 32);\n\tvar valid = user && Total.Crypto.timingSafeEqual(actual, user.hash);\n\tif (!valid) {\n\t\trecent.push(Date.now());\n\t\treturn $.invalid(401);\n\t}\n\tvar id = UID();\n\tNOSQL('task-sessions').insert({ id: id, userid: user.id, expire: Date.now() + 1800000 }).callback(function(err) {\n\t\tif (err) return $.invalid(500);\n\t\tMAIN.taskAuth.authcookie($, id, user.id, '30 minutes');\n\t\t$.json({ user: { id: user.id, name: user.name, role: user.role }, csrf: $.csrf() });\n\t});\n}\n\nfunction logout($) {\n\tNOSQL('task-sessions').remove().where('id', $.sessionid).callback(function(err) {\n\t\tif (err) return $.invalid(500);\n\t\tMAIN.taskAuth.logout($);\n\t\t$.cookie(MAIN.taskAuth.cookie, '', '-1 year', MAIN.taskAuth.options);\n\t\t$.json({ ok: true });\n\t});\n}\n```\n\n```html public/index.html\n<!DOCTYPE html>\n<html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Taskboard SQL · Total.js</title>\n<style>\nbody{font:16px system-ui;background:#f4f5f7;color:#172033;margin:0}main{max-width:720px;margin:8vh auto;padding:30px;background:white;border-radius:18px}header{display:flex;align-items:center;justify-content:space-between;gap:15px}h1{margin:0}p{color:#536078}label{display:block;margin-top:14px}input,button{font:inherit;padding:10px;border:1px solid #bbc4d1;border-radius:8px}input[type=text],input[type=password]{box-sizing:border-box;width:100%;margin-top:5px}button{cursor:pointer;background:#152d57;color:white}form button{margin-top:15px}ul{padding:0;list-style:none}li{display:flex;align-items:center;gap:10px;padding:14px 0;border-bottom:1px solid #eee}.task-title{flex:1;overflow-wrap:anywhere}.muted{font-size:14px;color:#536078}#message{min-height:24px;color:#9d251e}[hidden]{display:none!important}\n</style></head><body><main>\n<header><h1>Taskboard SQL</h1><span class=\"muted\">Total.js reference</span></header>\n<p>Private tasks stored in SQLite, with an atomic archive of the completed ones.</p>\n<p id=\"message\" role=\"status\" aria-live=\"polite\"></p>\n<form id=\"login\"><h2>Sign in</h2><label for=\"name\">User</label><input id=\"name\" autocomplete=\"username\" required><label for=\"password\">Password</label><input id=\"password\" type=\"password\" autocomplete=\"current-password\" required><button>Sign in</button></form>\n<section id=\"workspace\" hidden><header><p id=\"identity\"></p><button id=\"logout\" type=\"button\">Sign out</button></header>\n<form id=\"create\"><label for=\"title\">New task</label><input id=\"title\" type=\"text\" maxlength=\"80\" required><button>Add task</button></form>\n<ul id=\"tasks\" aria-label=\"Tasks\"></ul><p id=\"empty\">No tasks yet.</p>\n<button id=\"archive\" type=\"button\">Archive completed</button>\n<h2>Archive</h2><ul id=\"archived\" aria-label=\"Archived tasks\"></ul></section>\n</main><script src=\"/app.js\"></script></body></html>\n```\n\n```js public/app.js\n(function() {\n\tvar user = null;\n\tvar csrf = '';\n\tvar busy = false;\n\tvar byid = id => document.getElementById(id);\n\tasync function request(path, method, body) {\n\t\tvar response = await fetch(path, { method: method || 'GET', credentials: 'same-origin', headers: { 'content-type': 'application/json', 'x-csrf-token': csrf }, body: body === undefined ? undefined : JSON.stringify(body) });\n\t\tvar data = await response.json().catch(() => null);\n\t\tif (!response.ok) {\n\t\t\tif (response.status === 401) { user = null; show(); }\n\t\t\tthrow new Error('Request refused (' + response.status + ')');\n\t\t}\n\t\tif (data === null) throw new Error('Unexpected server response');\n\t\treturn data;\n\t}\n\tfunction show() {\n\t\tbyid('login').hidden = !!user;\n\t\tbyid('workspace').hidden = !user;\n\t\tbyid('identity').textContent = user ? user.name + ' · ' + user.role : '';\n\t}\n\tasync function load() {\n\t\tvar tasks = await request('/tasks/');\n\t\tvar archived = await request('/archive/');\n\t\tbyid('tasks').replaceChildren();\n\t\tbyid('empty').hidden = tasks.length > 0;\n\t\ttasks.forEach(function(task) {\n\t\t\tvar row = document.createElement('li');\n\t\t\tvar checkbox = document.createElement('input');\n\t\t\tcheckbox.type = 'checkbox'; checkbox.checked = task.done; checkbox.setAttribute('aria-label', 'Complete ' + task.title);\n\t\t\tcheckbox.addEventListener('change', () => work(async () => { await request('/tasks/' + task.id + '/', 'PATCH', { done: checkbox.checked }); await load(); }));\n\t\t\tvar title = document.createElement('span'); title.className = 'task-title'; title.textContent = task.title;\n\t\t\trow.append(checkbox, title);\n\t\t\tif (user.role === 'admin') {\n\t\t\t\tvar remove = document.createElement('button'); remove.type = 'button'; remove.textContent = 'Delete';\n\t\t\t\tremove.addEventListener('click', () => work(async () => { await request('/tasks/' + task.id + '/', 'DELETE'); await load(); }));\n\t\t\t\trow.append(remove);\n\t\t\t}\n\t\t\tbyid('tasks').append(row);\n\t\t});\n\t\tbyid('archived').replaceChildren();\n\t\tarchived.forEach(function(task) {\n\t\t\tvar row = document.createElement('li');\n\t\t\trow.textContent = task.title;\n\t\t\tbyid('archived').append(row);\n\t\t});\n\t}\n\tasync function work(fn) {\n\t\tif (busy) return;\n\t\tbusy = true; byid('message').textContent = '';\n\t\tdocument.querySelectorAll('input,button').forEach(control => control.disabled = true);\n\t\ttry { await fn(); } catch (err) { byid('message').textContent = err.message; }\n\t\tfinally { busy = false; document.querySelectorAll('input,button').forEach(control => control.disabled = false); }\n\t}\n\tbyid('login').addEventListener('submit', function(event) {\n\t\tevent.preventDefault(); work(async function() {\n\t\t\tvar result = await request('/login/', 'POST', { name: byid('name').value, password: byid('password').value });\n\t\t\tuser = result.user; csrf = result.csrf; byid('password').value = ''; show(); await load();\n\t\t});\n\t});\n\tbyid('create').addEventListener('submit', function(event) {\n\t\tevent.preventDefault(); work(async function() { await request('/tasks/', 'POST', { title: byid('title').value }); byid('title').value = ''; await load(); });\n\t});\n\tbyid('archive').addEventListener('click', () => work(async function() { await request('/archive/', 'POST', {}); await load(); }));\n\tbyid('logout').addEventListener('click', () => work(async function() { await request('/logout/', 'POST', {}); user = null; byid('tasks').replaceChildren(); show(); }));\n\twork(async function() { var result = await request('/session/'); user = result.user; csrf = result.csrf; show(); if (user) await load(); });\n})();\n```\n\n## What is exercised\n\n\nRelated recipes: [SQLite and the QueryBuilder](/en/total5/query-builder-sqlite/), [QueryBuilder contract](/en/total5/query-builder-contract/), [CSRF](/en/total5/csrf-protection/) and [Taskboard Live](/en/docs/taskboard-live/)."}],"knowledge":{"content_sha256":"f7854552a20542f41a5fcfd23639a80cba171181b0cd8c142bcb5a0a132843df","source_sha256":"5a4665e828a99f21f508202830fde8872a755a2f06c61112761c456deb792f72","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["AND","AUTH","BEGIN","BETWEEN","CHECK","COLUMNS","COMMIT","CONF","CREATE","CSRF","DEF","DEFAULT","DELETE","DESC","DOCTYPE","Date.now","EXISTS","FROM","GET","IMMEDIATE","INSERT","INTEGER","INTO","JSON","KEY","LIMIT","MAIN","MIDDLEWARE","NEWACTION","NEWDB","NODE_ENV","NOOP","NOSQL","NOT","NULL","ORDER","PATCH","PATH","POST","PRIMARY","ROLLBACK","ROUTE","SELECT","SET","SQL","STRICT","TABLE","TASKBOARD_","TASKBOARD_SECRET","TEXT","Total.Crypto","Total.Crypto.scryptSync","Total.Crypto.timingSafeEqual","Total.js","UID","UPDATE","VALUES","WHERE"],"language":"en"},"files":[{"path":"config","language":"text","content":"name : Taskboard SQL\ndemo : true"},{"path":"definitions/taskboard.js","language":"js","content":"var crypto = Total.Crypto;\nvar demo = CONF.demo === true;\nif (demo && (process.argv.includes('--release') || process.env.NODE_ENV === 'production'))\n\tthrow new Error('Disable demo mode before a release deployment');\nvar secret = process.env.TASKBOARD_SECRET || (demo ? 'local-taskboard-demo-secret-not-for-public-use' : '');\nif (secret.length < 32)\n\tthrow new Error('TASKBOARD_SECRET needs at least 32 characters');\nCONF.secret_csrf = secret;\n\nMAIN.taskUsers = Object.create(null);\nfor (var name of ['alice', 'bob', 'admin']) {\n\tvar password = process.env['TASKBOARD_' + name.toUpperCase() + '_PASSWORD'] || (demo ? name + '-demo-password' : '');\n\tif (password.length < 12)\n\t\tthrow new Error('Configure a password of at least 12 characters for ' + name);\n\tvar salt = crypto.randomBytes(16).toString('hex');\n\tMAIN.taskUsers[name] = { id: name, name: name, role: name === 'admin' ? 'admin' : 'member', salt: salt, hash: crypto.scryptSync(password, salt, 32) };\n}\n\nMAIN.taskAuth = {\n\tsecret: secret,\n\tcookie: 'taskboard_session',\n\texpire: '30 minutes',\n\toptions: { httponly: true, samesite: 'Strict', secure: !demo },\n\tonread: function(meta, next) {\n\t\tNOSQL('task-sessions').read().where('id', meta.sessionid).callback(function(err, session) {\n\t\t\tvar user = !err && session && session.expire > Date.now() && MAIN.taskUsers[session.userid];\n\t\t\tuser ? next(null, { id: user.id, name: user.name, role: user.role }) : next('Session not found');\n\t\t});\n\t},\n\tonlogout: function(session) {\n\t\tNOSQL('task-sessions').remove().where('id', session.sessionid).callback(NOOP);\n\t},\n\tonremove: function(id) {\n\t\tNOSQL('task-sessions').remove().where('id', id).callback(NOOP);\n\t}\n};\nAUTH(MAIN.taskAuth);\n\nMIDDLEWARE('taskcsrf', function($, next) {\n\tDEF.onCSRFcheck($) ? next() : $.invalid(403);\n});"},{"path":"definitions/sqlite.js","language":"js","content":"var DatabaseSync = require('node:sqlite').DatabaseSync;\nvar db = new DatabaseSync(PATH.root('taskboard.sqlite'));\n\ndb.exec('CREATE TABLE IF NOT EXISTS tasks (id TEXT PRIMARY KEY, title TEXT NOT NULL CHECK(length(title) BETWEEN 1 AND 80), done INTEGER NOT NULL DEFAULT 0 CHECK(done IN (0, 1)), owner TEXT NOT NULL, created INTEGER NOT NULL) STRICT');\ndb.exec('CREATE TABLE IF NOT EXISTS task_archive (id TEXT PRIMARY KEY, title TEXT NOT NULL, owner TEXT NOT NULL, created INTEGER NOT NULL, archived INTEGER NOT NULL) STRICT');\n\nvar COLUMNS = ['id', 'title', 'done', 'owner', 'created'];\n\nfunction column(name) {\n\tif (!COLUMNS.includes(name))\n\t\tthrow new Error('Column not allowed');\n\treturn '\"' + name + '\"';\n}\n\nfunction where(options, params) {\n\tvar filters = options.filter.map(function(f) {\n\t\tif (f.type !== 'where' || f.comparer !== '=')\n\t\t\tthrow new Error('Filter not supported');\n\t\tparams.push(f.name === 'done' ? (f.value ? 1 : 0) : f.value);\n\t\treturn column(f.name) + ' = ?';\n\t});\n\treturn filters.length ? ' WHERE ' + filters.join(' AND ') : '';\n}\n\nfunction row(item) {\n\titem.done = item.done === 1;\n\treturn item;\n}\n\n// The only table the QueryBuilder can reach is tasks; values are always bound parameters\nNEWDB('sqlite', function(options, callback) {\n\ttry {\n\t\tif (options.table !== 'tasks')\n\t\t\tthrow new Error('Table not allowed');\n\t\tvar params = [];\n\t\tvar condition = where(options, params);\n\t\tvar result;\n\t\tif (['find', 'read'].includes(options.exec)) {\n\t\t\tvar sort = (options.sort || ['created_asc']).map(function(s) {\n\t\t\t\tvar match = /^(title|created)_(asc|desc)$/.exec(s);\n\t\t\t\tif (!match)\n\t\t\t\t\tthrow new Error('Sort not allowed');\n\t\t\t\treturn column(match[1]) + ' ' + match[2];\n\t\t\t}).join(',');\n\t\t\tvar rows = db.prepare('SELECT id, title, done, owner, created FROM tasks' + condition + ' ORDER BY ' + sort + ' LIMIT 200').all(...params).map(row);\n\t\t\tresult = options.exec === 'read' ? rows[0] || null : rows;\n\t\t} else if (options.exec === 'insert') {\n\t\t\tvar p = options.payload;\n\t\t\tdb.prepare('INSERT INTO tasks (id, title, done, owner, created) VALUES (?, ?, ?, ?, ?)').run(p.id, p.title, p.done ? 1 : 0, p.owner, p.created);\n\t\t\tresult = { id: p.id };\n\t\t} else if (options.exec === 'update') {\n\t\t\tif (!condition)\n\t\t\t\tthrow new Error('Filter required');\n\t\t\tvar keys = Object.keys(options.payload);\n\t\t\tvar set = keys.map(key => column(key) + ' = ?').join(',');\n\t\t\tresult = db.prepare('UPDATE tasks SET ' + set + condition).run(...keys.map(key => key === 'done' ? (options.payload[key] ? 1 : 0) : options.payload[key]), ...params).changes;\n\t\t} else if (options.exec === 'remove') {\n\t\t\tif (!condition)\n\t\t\t\tthrow new Error('Filter required');\n\t\t\tresult = db.prepare('DELETE FROM tasks' + condition).run(...params).changes;\n\t\t} else\n\t\t\tthrow new Error('Operation not supported');\n\t\tcallback(null, result);\n\t} catch (e) {\n\t\tcallback(e);\n\t}\n});\n\n// One atomic step: the completed tasks (of one owner, or of everybody for the administrator) move to the archive.\n// Either both statements are applied or none is.\nMAIN.archiveTasks = function(owner) {\n\tvar params = owner ? [owner] : [];\n\tvar filter = owner ? ' AND owner = ?' : '';\n\tdb.exec('BEGIN IMMEDIATE');\n\ttry {\n\t\tdb.prepare('INSERT INTO task_archive (id, title, owner, created, archived) SELECT id, title, owner, created, ? FROM tasks WHERE done = 1' + filter).run(Date.now(), ...params);\n\t\tvar moved = db.prepare('DELETE FROM tasks WHERE done = 1' + filter).run(...params).changes;\n\t\tdb.exec('COMMIT');\n\t\treturn moved;\n\t} catch (e) {\n\t\tdb.exec('ROLLBACK');\n\t\tthrow e;\n\t}\n};\n\nMAIN.archivedTasks = function(owner) {\n\tvar filter = owner ? ' WHERE owner = ?' : '';\n\treturn db.prepare('SELECT id, title, owner FROM task_archive' + filter + ' ORDER BY archived DESC, id LIMIT 200').all(...(owner ? [owner] : []));\n};"},{"path":"actions/tasks.js","language":"js","content":"function owned(builder, user) {\n\treturn user.role === 'admin' ? builder : builder.where('owner', user.id);\n}\n\nNEWACTION('Tasks|list', {\n\tuser: true,\n\taction: function($) {\n\t\towned(DB().find('sqlite/tasks').sort('title'), $.user).callback(function(err, tasks) {\n\t\t\terr ? $.invalid(500) : $.callback(tasks);\n\t\t});\n\t}\n});\n\nNEWACTION('Tasks|create', {\n\tuser: true,\n\tinput: '*title:String(80)',\n\taction: function($, model) {\n\t\tvar task = { id: UID(), title: model.title, done: false, owner: $.user.id, created: Date.now() };\n\t\tDB().insert('sqlite/tasks', task).callback(function(err) {\n\t\t\terr ? $.invalid(500) : $.callback(task);\n\t\t});\n\t}\n});\n\nNEWACTION('Tasks|update', {\n\tuser: true,\n\tparams: '*id:UID',\n\tinput: '*done:Boolean',\n\taction: function($, model) {\n\t\towned(DB().update('sqlite/tasks', { done: model.done }).id($.params.id), $.user).callback(function(err, count) {\n\t\t\tif (err)\n\t\t\t\treturn $.invalid(500);\n\t\t\tcount ? $.success() : $.invalid(404);\n\t\t});\n\t}\n});\n\nNEWACTION('Tasks|remove', {\n\tuser: true,\n\tparams: '*id:UID',\n\taction: function($) {\n\t\tif ($.user.role !== 'admin')\n\t\t\treturn $.invalid(403);\n\t\tDB().remove('sqlite/tasks').id($.params.id).callback(function(err, count) {\n\t\t\tif (err)\n\t\t\t\treturn $.invalid(500);\n\t\t\tcount ? $.success() : $.invalid(404);\n\t\t});\n\t}\n});\n\nNEWACTION('Tasks|archive', {\n\tuser: true,\n\taction: function($) {\n\t\ttry {\n\t\t\t$.callback({ moved: MAIN.archiveTasks($.user.role === 'admin' ? null : $.user.id) });\n\t\t} catch (e) {\n\t\t\t$.invalid(500);\n\t\t}\n\t}\n});\n\nNEWACTION('Tasks|archived', {\n\tuser: true,\n\taction: function($) {\n\t\t$.callback(MAIN.archivedTasks($.user.role === 'admin' ? null : $.user.id));\n\t}\n});"},{"path":"controllers/taskboard.js","language":"js","content":"var attempts = Object.create(null);\n\nexports.install = function() {\n\tROUTE('GET /', function($) { $.file(PATH.public('index.html')); });\n\tROUTE('GET /session/', session);\n\tROUTE('POST /login/ @json <2kb #taskcsrf', login);\n\tROUTE('+POST /logout/ @json #taskcsrf', logout);\n\tROUTE('+GET /tasks/ --> *Tasks|list');\n\tROUTE('+POST /tasks/ <2kb #taskcsrf --> *Tasks|create');\n\tROUTE('+PATCH /tasks/{id}/ <2kb #taskcsrf --> *Tasks|update');\n\tROUTE('+DELETE /tasks/{id}/ #taskcsrf --> *Tasks|remove');\n\tROUTE('+POST /archive/ #taskcsrf --> *Tasks|archive');\n\tROUTE('+GET /archive/ --> *Tasks|archived');\n};\n\nfunction session($) {\n\t$.response.headers['cache-control'] = 'no-store';\n\t$.json({ user: $.user || null, csrf: $.csrf() });\n}\n\nfunction login($) {\n\tvar key = $.ip;\n\tvar recent = (attempts[key] || []).filter(time => time > Date.now() - 600000);\n\tattempts[key] = recent;\n\tif (recent.length >= 5) return $.invalid(429);\n\tvar user = MAIN.taskUsers[String($.body.name || '').toLowerCase()];\n\tvar salt = user ? user.salt : 'dummy-salt';\n\tvar actual = Total.Crypto.scryptSync(String($.body.password || ''), salt, 32);\n\tvar valid = user && Total.Crypto.timingSafeEqual(actual, user.hash);\n\tif (!valid) {\n\t\trecent.push(Date.now());\n\t\treturn $.invalid(401);\n\t}\n\tvar id = UID();\n\tNOSQL('task-sessions').insert({ id: id, userid: user.id, expire: Date.now() + 1800000 }).callback(function(err) {\n\t\tif (err) return $.invalid(500);\n\t\tMAIN.taskAuth.authcookie($, id, user.id, '30 minutes');\n\t\t$.json({ user: { id: user.id, name: user.name, role: user.role }, csrf: $.csrf() });\n\t});\n}\n\nfunction logout($) {\n\tNOSQL('task-sessions').remove().where('id', $.sessionid).callback(function(err) {\n\t\tif (err) return $.invalid(500);\n\t\tMAIN.taskAuth.logout($);\n\t\t$.cookie(MAIN.taskAuth.cookie, '', '-1 year', MAIN.taskAuth.options);\n\t\t$.json({ ok: true });\n\t});\n}"},{"path":"public/index.html","language":"html","content":"<!DOCTYPE html>\n<html lang=\"en\"><head><meta charset=\"utf-8\"><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">\n<title>Taskboard SQL · Total.js</title>\n<style>\nbody{font:16px system-ui;background:#f4f5f7;color:#172033;margin:0}main{max-width:720px;margin:8vh auto;padding:30px;background:white;border-radius:18px}header{display:flex;align-items:center;justify-content:space-between;gap:15px}h1{margin:0}p{color:#536078}label{display:block;margin-top:14px}input,button{font:inherit;padding:10px;border:1px solid #bbc4d1;border-radius:8px}input[type=text],input[type=password]{box-sizing:border-box;width:100%;margin-top:5px}button{cursor:pointer;background:#152d57;color:white}form button{margin-top:15px}ul{padding:0;list-style:none}li{display:flex;align-items:center;gap:10px;padding:14px 0;border-bottom:1px solid #eee}.task-title{flex:1;overflow-wrap:anywhere}.muted{font-size:14px;color:#536078}#message{min-height:24px;color:#9d251e}[hidden]{display:none!important}\n</style></head><body><main>\n<header><h1>Taskboard SQL</h1><span class=\"muted\">Total.js reference</span></header>\n<p>Private tasks stored in SQLite, with an atomic archive of the completed ones.</p>\n<p id=\"message\" role=\"status\" aria-live=\"polite\"></p>\n<form id=\"login\"><h2>Sign in</h2><label for=\"name\">User</label><input id=\"name\" autocomplete=\"username\" required><label for=\"password\">Password</label><input id=\"password\" type=\"password\" autocomplete=\"current-password\" required><button>Sign in</button></form>\n<section id=\"workspace\" hidden><header><p id=\"identity\"></p><button id=\"logout\" type=\"button\">Sign out</button></header>\n<form id=\"create\"><label for=\"title\">New task</label><input id=\"title\" type=\"text\" maxlength=\"80\" required><button>Add task</button></form>\n<ul id=\"tasks\" aria-label=\"Tasks\"></ul><p id=\"empty\">No tasks yet.</p>\n<button id=\"archive\" type=\"button\">Archive completed</button>\n<h2>Archive</h2><ul id=\"archived\" aria-label=\"Archived tasks\"></ul></section>\n</main><script src=\"/app.js\"></script></body></html>"},{"path":"public/app.js","language":"js","content":"(function() {\n\tvar user = null;\n\tvar csrf = '';\n\tvar busy = false;\n\tvar byid = id => document.getElementById(id);\n\tasync function request(path, method, body) {\n\t\tvar response = await fetch(path, { method: method || 'GET', credentials: 'same-origin', headers: { 'content-type': 'application/json', 'x-csrf-token': csrf }, body: body === undefined ? undefined : JSON.stringify(body) });\n\t\tvar data = await response.json().catch(() => null);\n\t\tif (!response.ok) {\n\t\t\tif (response.status === 401) { user = null; show(); }\n\t\t\tthrow new Error('Request refused (' + response.status + ')');\n\t\t}\n\t\tif (data === null) throw new Error('Unexpected server response');\n\t\treturn data;\n\t}\n\tfunction show() {\n\t\tbyid('login').hidden = !!user;\n\t\tbyid('workspace').hidden = !user;\n\t\tbyid('identity').textContent = user ? user.name + ' · ' + user.role : '';\n\t}\n\tasync function load() {\n\t\tvar tasks = await request('/tasks/');\n\t\tvar archived = await request('/archive/');\n\t\tbyid('tasks').replaceChildren();\n\t\tbyid('empty').hidden = tasks.length > 0;\n\t\ttasks.forEach(function(task) {\n\t\t\tvar row = document.createElement('li');\n\t\t\tvar checkbox = document.createElement('input');\n\t\t\tcheckbox.type = 'checkbox'; checkbox.checked = task.done; checkbox.setAttribute('aria-label', 'Complete ' + task.title);\n\t\t\tcheckbox.addEventListener('change', () => work(async () => { await request('/tasks/' + task.id + '/', 'PATCH', { done: checkbox.checked }); await load(); }));\n\t\t\tvar title = document.createElement('span'); title.className = 'task-title'; title.textContent = task.title;\n\t\t\trow.append(checkbox, title);\n\t\t\tif (user.role === 'admin') {\n\t\t\t\tvar remove = document.createElement('button'); remove.type = 'button'; remove.textContent = 'Delete';\n\t\t\t\tremove.addEventListener('click', () => work(async () => { await request('/tasks/' + task.id + '/', 'DELETE'); await load(); }));\n\t\t\t\trow.append(remove);\n\t\t\t}\n\t\t\tbyid('tasks').append(row);\n\t\t});\n\t\tbyid('archived').replaceChildren();\n\t\tarchived.forEach(function(task) {\n\t\t\tvar row = document.createElement('li');\n\t\t\trow.textContent = task.title;\n\t\t\tbyid('archived').append(row);\n\t\t});\n\t}\n\tasync function work(fn) {\n\t\tif (busy) return;\n\t\tbusy = true; byid('message').textContent = '';\n\t\tdocument.querySelectorAll('input,button').forEach(control => control.disabled = true);\n\t\ttry { await fn(); } catch (err) { byid('message').textContent = err.message; }\n\t\tfinally { busy = false; document.querySelectorAll('input,button').forEach(control => control.disabled = false); }\n\t}\n\tbyid('login').addEventListener('submit', function(event) {\n\t\tevent.preventDefault(); work(async function() {\n\t\t\tvar result = await request('/login/', 'POST', { name: byid('name').value, password: byid('password').value });\n\t\t\tuser = result.user; csrf = result.csrf; byid('password').value = ''; show(); await load();\n\t\t});\n\t});\n\tbyid('create').addEventListener('submit', function(event) {\n\t\tevent.preventDefault(); work(async function() { await request('/tasks/', 'POST', { title: byid('title').value }); byid('title').value = ''; await load(); });\n\t});\n\tbyid('archive').addEventListener('click', () => work(async function() { await request('/archive/', 'POST', {}); await load(); }));\n\tbyid('logout').addEventListener('click', () => work(async function() { await request('/logout/', 'POST', {}); user = null; byid('tasks').replaceChildren(); show(); }));\n\twork(async function() { var result = await request('/session/'); user = result.user; csrf = result.csrf; show(); if (user) await load(); });\n})();"}],"test":"const bootstrap = await get('/session/');\nconst login = async name => {\n\tconst response = await post('/login/', { name, password: name + '-demo-password' }, { headers: { 'x-csrf-token': bootstrap.json.csrf } });\n\tassert(response.status === 200, name + ' signs in');\n\treturn { cookie: response.headers.get('set-cookie').split(';')[0], 'x-csrf-token': response.json.csrf };\n};\nconst alice = await login('alice'), bob = await login('bob'), admin = await login('admin');\nassert((await get('/tasks/')).status === 401, 'anonymous access refused');\n\nconst sqlish = \"x'); DROP TABLE tasks; --\";\nconst first = await post('/tasks/', { title: sqlish, owner: 'bob' }, { headers: alice });\nassert(first.status === 200 && first.json.owner === 'alice', 'ownership comes from the session: ' + first.body);\nconst second = await post('/tasks/', { title: 'Second', }, { headers: alice });\nconst bobs = await post('/tasks/', { title: 'Bob task' }, { headers: bob });\nassert((await post('/tasks/', { title: 'x'.repeat(81) }, { headers: alice })).status === 400, 'title over 80 characters is refused by the schema');\n\nconst mine = await get('/tasks/', { headers: alice });\nassert(mine.json.length === 2 && mine.json.some(t => t.title === sqlish), 'a SQL-looking title is stored and returned as text');\nassert((await get('/tasks/', { headers: bob })).json.length === 1, 'another member sees only their own tasks');\nassert((await get('/tasks/', { headers: admin })).json.length === 3, 'the administrator sees all tasks');\nassert((await send('PATCH', '/tasks/' + first.json.id + '/', { done: true }, { headers: bob })).status === 404, 'a foreign task cannot be changed');\nassert((await send('PATCH', '/tasks/' + first.json.id + '/', { done: true, owner: 'bob' }, { headers: alice })).status === 200, 'the owner completes the task');\nassert((await get('/tasks/', { headers: alice })).json.find(t => t.id === first.json.id).done === true, 'done is a boolean again after SQL');\nassert((await send('DELETE', '/tasks/' + second.json.id + '/', {}, { headers: alice })).status === 403, 'only the administrator deletes');\nassert((await post('/archive/', {}, { headers: { cookie: alice.cookie } })).status === 403, 'archive needs the CSRF token');\n\nconst archived = await post('/archive/', {}, { headers: alice });\nassert(archived.status === 200 && archived.json.moved === 1, 'the archive moves the completed task of the caller: ' + archived.body);\nconst after = await get('/tasks/', { headers: alice });\nassert(after.json.length === 1 && after.json[0].title === 'Second', 'only the open task remains');\nconst list = await get('/archive/', { headers: alice });\nassert(list.json.length === 1 && list.json[0].title === sqlish, 'the completed task is in the archive');\nassert((await get('/archive/', { headers: bob })).json.length === 0, 'another member does not see it');\nassert((await send('PATCH', '/tasks/' + bobs.json.id + '/', { done: true }, { headers: bob })).status === 200, 'bob completes his task');\nassert((await post('/archive/', {}, { headers: alice })).json.moved === 0, 'archiving again moves nothing for alice');\nconst all = await post('/archive/', {}, { headers: admin });\nassert(all.json.moved === 1, 'the administrator archives the completed tasks of everybody');\nassert((await get('/archive/', { headers: admin })).json.length === 2, 'the administrator sees the whole archive');\nassert((await get('/tasks/', { headers: admin })).json.length === 1, 'one open task is left');","test_kind":"http"}
{"id":"docs/globals-total5","section":"docs","kind":"reference","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/docs/globals-total5/","markdown_url":"https://forge.totaljs.es/en/docs/globals-total5.md","tags":["reference","globals"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"0c2ac5068f43ba0395b155f6f983ebbbb9689613890b9702ab10ecea1f8dface","checked_at":"2026-10-20T12:41:44.150Z","framework":"total5@0.0.19","node":"v24.14.1","browser":null,"test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Global names of Total.js 5","description":"Global functions and objects of Total.js 5, with their purpose and usage recipes.","messages":[{"role":"user","content":"Global names of Total.js 5\n\nGlobal functions and objects of Total.js 5, with their purpose and usage recipes."},{"role":"assistant","content":"Total.js 5 exposes global functions and objects for routing, actions, storage, messaging and application utilities. Use the table to find a name and its usage recipe.\n\n| Name | Kind | What it does | Recipe |\n|---|---|---|---|\n| `ON` | function | Registers a listener for an application event. | [events](/en/total5/events/) |\n| `ONCE` | function | Like `ON`, but the listener runs only for the first event. | [events](/en/total5/events/) |\n| `OFF` | function | Removes a listener registered with `ON`. | [events](/en/total5/events/) |\n| `EMIT` | function | Emits an event with up to seven arguments. | [events](/en/total5/events/) |\n| `LOAD` | function | Loads the files of the project (controllers, definitions...); the framework calls it for you at start. | [load-and-npminstall](/en/total5/load-and-npminstall/) |\n| `LOADCONFIG` | function | Parses a text in the `config` format and applies it to `CONF`. | [configuration-file](/en/total5/configuration-file/) |\n| `LOADRESOURCE` | function | Loads a resource (a translation file) from a text or from a URL. | [localization](/en/total5/localization/) |\n| `PAUSESERVER` | function | Pauses or resumes the server by name: while paused, requests get a 503 page. | [pause-server](/en/total5/pause-server/) |\n| `DEBUG` | boolean | `true` in debug mode, `false` in release mode. | [release-mode-and-debugging](/en/total5/release-mode-and-debugging/) |\n| `CONF` | object | The configuration of the application (the `config` file and its defaults). | [configuration-file](/en/total5/configuration-file/) |\n| `DEF` | object | Default handlers and settings of the framework (`DEF.onMail`, `DEF.helpers`, `DEF.onCSRFcheck`...). | [view-helpers-and-config](/en/total5/view-helpers-and-config/) |\n| `F` | object | The framework object. `Total` is the same object. | [utility-globals](/en/total5/utility-globals/) |\n| `Total` | object | The framework object (same as `F`). `Total.run()` starts the application. | [release-mode-and-debugging](/en/total5/release-mode-and-debugging/) |\n| `MAIN` | object | An empty object that the framework creates for your own application data. | [utility-globals](/en/total5/utility-globals/) |\n| `REPO` | object | An empty object that the framework creates for your own data. | [utility-globals](/en/total5/utility-globals/) |\n| `FUNC` | object | An empty object that the framework creates for your own functions. | [utility-globals](/en/total5/utility-globals/) |\n| `TEMP` | object | An object for temporary data; the framework replaces it with a new empty object from time to time. | [utility-globals](/en/total5/utility-globals/) |\n| `NOW` | object | The current date, refreshed by the framework once a minute and by some calls such as `CACHE`. | [utility-globals](/en/total5/utility-globals/) |\n| `EMPTYARRAY` | object | A frozen empty array, to avoid creating new ones. | [utility-globals](/en/total5/utility-globals/) |\n| `EMPTYOBJECT` | object | A frozen empty object, to avoid creating new ones. | [utility-globals](/en/total5/utility-globals/) |\n| `MODS` | object | The loaded modules of the `modules/` folder, by id. | [utility-globals](/en/total5/utility-globals/) |\n| `PLUGINS` | object | The loaded plugins of the `plugins/` folder, by id. | [utility-globals](/en/total5/utility-globals/) |\n| `NOOP` | function | A function that does nothing. | [utility-globals](/en/total5/utility-globals/) |\n| `print` | function | The same as `console.log`. | [utility-globals](/en/total5/utility-globals/) |\n| `REQUIRE` | function | Like `require`, but also looks in the `node_modules` folder of the application and accepts `node:` names. | [utility-globals](/en/total5/utility-globals/) |\n\n## Routing, actions and middleware\n\n| Name | Kind | What it does | Recipe |\n|---|---|---|---|\n| `ROUTE` | function | Declares a route (HTTP, `SOCKET`, `FILE`, `API` or a fallback such as `#404`). | [http-methods](/en/total5/http-methods/) |\n| `NEWACTION` | function | Declares an action with its input, params, query, output and permissions. | [actions-crud](/en/total5/actions-crud/) |\n| `NEWSCHEMA` | function | Groups the actions of an entity under one name. | [schemas](/en/total5/schemas/) |\n| `ACTION` | function | Runs an action from your own code. | [calling-actions-from-code](/en/total5/calling-actions-from-code/) |\n| `EXEC` | function | The same function as `ACTION`. | [calling-actions-from-code](/en/total5/calling-actions-from-code/) |\n| `NEWTRANSFORM` | function | Adds a step to a named chain of transformations. | [transforms](/en/total5/transforms/) |\n| `TRANSFORM` | function | Runs a value through a chain of transformations. | [transforms](/en/total5/transforms/) |\n| `MIDDLEWARE` | function | Registers a middleware to be used in routes with `#name`. | [middleware](/en/total5/middleware/) |\n| `NEWMIDDLEWARE` | function | The same function as `MIDDLEWARE`. | [middleware](/en/total5/middleware/) |\n| `PROXY` | function | Forwards the requests below a URL prefix to another server. | [proxy](/en/total5/proxy/) |\n| `CORS` | function | Sets the origins that may call the application from a browser. | [cors](/en/total5/cors/) |\n| `ErrorBuilder` | function | The class that collects validation errors. | [errors-and-audit](/en/total5/errors-and-audit/) |\n| `SUCCESS` | function | Returns the object that the framework sends for a successful operation (`{ success: true, value: value }`). | [errors-and-audit](/en/total5/errors-and-audit/) |\n| `ERROR` | function | `ERROR(name)` returns a callback that logs the error it receives under that name; `ERROR()` returns the default error callback. | [errors-and-audit](/en/total5/errors-and-audit/) |\n| `UNAUTHORIZED` | function | `UNAUTHORIZED($, ...permissions)` answers 401 and returns `true` when the user has none of the permissions; it returns `false` when the user has one of them or is a super administrator. | [action-user-and-permissions](/en/total5/action-user-and-permissions/) |\n| `AUTH` | function | Authenticates every request: a function, or the options of the built-in sessions. | [sessions-with-built-in-auth](/en/total5/sessions-with-built-in-auth/) |\n| `BLOCKED` | function | Counts the calls of an IP address and tells when it reached the limit. | [rate-limiting](/en/total5/rate-limiting/) |\n| `AUDIT` | function | Appends an audit entry (user, message and type) to a log file in `logs/`. | [errors-and-audit](/en/total5/errors-and-audit/) |\n\n## Data and cache\n\n| Name | Kind | What it does | Recipe |\n|---|---|---|---|\n| `NOSQL` | function | Opens a table of the built-in NoSQL database. | [nosql-crud](/en/total5/nosql-crud/) |\n| `DATA` | object | Database-independent entry to the query builder. | [data-api](/en/total5/data-api/) |\n| `DB` | function | Starts a query with the query builder (`DB().find('table').where(...).callback($)`); the query is run by the driver registered with `NEWDB`. | [query-builder-db](/en/total5/query-builder-db/) |\n| `NEWDB` | function | Registers a database driver `function(options, callback)` for the query builder: the default one, or a named one with `NEWDB('name', fn)`. | [query-builder-db](/en/total5/query-builder-db/) |\n| `CACHE` | object | An in-memory store with expiration. | [cache](/en/total5/cache/) |\n| `FILECACHE` | function | Computes a result once and keeps it in a file for a while. | [filecache](/en/total5/filecache/) |\n| `MEMORIZE` | function | A JSON object saved on disk. | [memorize](/en/total5/memorize/) |\n| `FILESTORAGE` | function | A storage for uploaded files. | [filestorage](/en/total5/filestorage/) |\n\n## HTTP, WebSocket and messaging clients\n\n| Name | Kind | What it does | Recipe |\n|---|---|---|---|\n| `REQUEST` | function | Low-level HTTP client. | [request-and-download](/en/total5/request-and-download/) |\n| `DOWNLOAD` | function | Saves the response of a URL into a file. | [request-and-download](/en/total5/request-and-download/) |\n| `RESTBuilder` | function | Builder for calls to other HTTP APIs. | [restbuilder](/en/total5/restbuilder/) |\n| `AJAX` | function | `AJAX('METHOD /url/', data, callback)` makes a request with the header `x-requested-with` and returns the parsed body; it returns a promise when there is no callback. | [ajax-requests](/en/total5/ajax-requests/) |\n| `API` | function | Calls an operation of an external service through its driver: `API('service', 'operation', data).callback($)`. | [api-drivers](/en/total5/api-drivers/) |\n| `NEWAPI` | function | Registers the driver `function(options, next)` of a service for `API`, with an optional configuration. | [api-drivers](/en/total5/api-drivers/) |\n| `TotalAPI` | function | Calls a Total.js platform service through `API('TAPI', name, model)`. | [remote-service-boundaries](/en/total5/remote-service-boundaries/) |\n| `WEBSOCKETCLIENT` | function | A WebSocket client for your application. | [websocket-client](/en/total5/websocket-client/) |\n| `TMSCLIENT` | function | A client of the Total Messaging Service. | [tms](/en/total5/tms/) |\n| `PUBLISH` | function | Sends a message to the TMS clients that subscribed to it. | [tms](/en/total5/tms/) |\n| `NEWPUBLISH` | function | Declares a message that the application publishes. | [tms](/en/total5/tms/) |\n| `SUBSCRIBE` | function | Adds a listener for a message that clients send to the application. | [tms](/en/total5/tms/) |\n| `UNSUBSCRIBE` | function | Removes the listeners of a TMS message. | [tms](/en/total5/tms/) |\n| `NEWSUBSCRIBE` | function | Declares a message that the application receives. | [tms](/en/total5/tms/) |\n| `NEWCALL` | function | Declares a function that TMS clients can call. | [tms](/en/total5/tms/) |\n| `OPENCLIENT` | function | Connects to OpenPlatform over WebSocket for remote calls, events and shared connections. | [openclient-protocol](/en/total5/openclient-protocol/) |\n| `PROXYCLIENT` | function | Connects to a `PROXYSERVER` over WebSocket and answers its requests with a local server (a port or `ip:port`). | [proxy-tunnel](/en/total5/proxy-tunnel/) |\n| `PROXYSERVER` | function | Turns the application into a tunnel server: the HTTP requests of an endpoint are sent through a WebSocket route to a `PROXYCLIENT`. | [proxy-tunnel](/en/total5/proxy-tunnel/) |\n| `LDAP` | function | Connects to an LDAP server (port 389 unless `ldap.port` says otherwise) and calls back with the result. | [ldap-openldap](/en/total5/ldap-openldap/) |\n| `MAIL` | function | Sends an email whose body is a view. | [send-email](/en/total5/send-email/) |\n| `HTMLMAIL` | function | Sends an email whose body is a string of HTML. | [send-email](/en/total5/send-email/) |\n| `LOGMAIL` | function | Sends an email whose body is a text printed in a `<pre>` block (pass the body as a text). | [logmail](/en/total5/logmail/) |\n| `Mail` | object | The class behind the email sending (SMTP). | [send-email](/en/total5/send-email/) |\n| `AIMODEL` | function | Starts a request for a language model registered with `NEWAIMODEL`: `AIMODEL('name').user('text').callback($.callback())`. | [ai-models](/en/total5/ai-models/) |\n| `AIPARSER` | function | Creates a parser for the streamed answer of an AI provider (`openai`, `claude`...): `write()` takes pieces and `end()` returns the content, thinking and tool calls. | [ai-models](/en/total5/ai-models/) |\n| `NEWAIMODEL` | function | Registers the driver `function(ai, done)` of one or more AI models for `AIMODEL`, with an optional configuration. | [ai-models](/en/total5/ai-models/) |\n| `PYPELINE` | function | Runs a Python file of the `pypelines/` folder (or inline Python code) from the application and talks to it with JSON frames over a socket. | [pypeline](/en/total5/pypeline/) |\n\n## Files, system and workers\n\n| Name | Kind | What it does | Recipe |\n|---|---|---|---|\n| `PATH` | object | Helpers that build paths in the project folders (`PATH.root()`, `PATH.public()`, `PATH.tmp()`...). | [file-download](/en/total5/file-download/) |\n| `MERGE` | function | Registers a virtual file made of several files, minified. | [minification](/en/total5/minification/) |\n| `TOUCH` | function | Clears the cached minified, temporary and not-found entries of a URL, or of all of them. | [touch-static-cache](/en/total5/touch-static-cache/) |\n| `CLEANUP` | function | Destroys a stream when it has finished. | [cleanup-streams](/en/total5/cleanup-streams/) |\n| `BACKUP` | function | Packs a directory into one file. | [backup-restore-shell](/en/total5/backup-restore-shell/) |\n| `RESTORE` | function | Unpacks a file created with `BACKUP`. | [backup-restore-shell](/en/total5/backup-restore-shell/) |\n| `SHELL` | function | Runs a shell command in the folder of the project. | [backup-restore-shell](/en/total5/backup-restore-shell/) |\n| `NPMINSTALL` | function | Installs an npm package in the modules folder of the application. | [load-and-npminstall](/en/total5/load-and-npminstall/) |\n| `IMPORT` | function | Downloads a JavaScript file from a URL and loads it. | [import-remote-module](/en/total5/import-remote-module/) |\n| `Image` | object | Image processing with GraphicsMagick or ImageMagick. | [images](/en/total5/images/) |\n| `NEWTHREAD` | function | Starts a worker thread. | [workers-and-threads](/en/total5/workers-and-threads/) |\n| `NEWFORK` | function | Starts a child process. | [workers-and-threads](/en/total5/workers-and-threads/) |\n| `NEWTHREADPOOL` | function | Creates a pool of workers that jobs borrow one at a time. | [thread-pool](/en/total5/thread-pool/) |\n| `CRON` | function | Runs a function on a schedule. | [cron-jobs](/en/total5/cron-jobs/) |\n| `setTimeout2` | function | `setTimeout` with an id: calling it again with the same id replaces the previous timer. | [debounce-timers](/en/total5/debounce-timers/) |\n| `clearTimeout2` | function | Cancels a timer created with `setTimeout2`. | [debounce-timers](/en/total5/debounce-timers/) |\n| `Flow` | object | The manager of the FlowStreams of the application: `Flow.load(schema)`, `Flow.remove(id)`, `Flow.instances` and `Flow.test()` for one component. | [flowstream](/en/total5/flowstream/), [flowstream-component-test](/en/total5/flowstream-component-test/) |\n| `FlowStream` | object | The same object as `Flow`. | [flowstream](/en/total5/flowstream/) |\n| `NEWMACRO` | function | Compiles a macro from a string. | [macros](/en/total5/macros/) |\n| `NEWCOMPONENT` | function | Compiles the HTML of a component. | [native-components](/en/total5/native-components/) |\n| `COMPONENTATOR` | function | Downloads a Componentator bundle into `public/` as `ui-<name>-<hash>.min.js` and stores its address in `F.repo[name]`. | [remote-service-boundaries](/en/total5/remote-service-boundaries/) |\n\n## Utilities, templates and localization\n\n| Name | Kind | What it does | Recipe |\n|---|---|---|---|\n| `U` | object | The utility object of the framework (`U.extend()`, `U.clone()`...). | [utilities](/en/total5/utilities/) |\n| `Utils` | object | The same object as `U`. | [utilities](/en/total5/utilities/) |\n| `UID` | function | Creates a short unique id sorted by time. | [unique-ids-and-hashes](/en/total5/unique-ids-and-hashes/) |\n| `GUID` | function | Creates a random string of the given length. | [unique-ids-and-hashes](/en/total5/unique-ids-and-hashes/) |\n| `HASH` | function | Returns a fast numeric hash (not for passwords). | [unique-ids-and-hashes](/en/total5/unique-ids-and-hashes/) |\n| `CLONE` | function | Makes a deep copy of an object. | [utility-globals](/en/total5/utility-globals/) |\n| `COPY` | function | Copies the properties of the first object into the second one and returns the second. | [utility-globals](/en/total5/utility-globals/) |\n| `DIFFARR` | function | Compares two arrays of objects by a property and returns the objects to add, the pairs to update and the values to remove. | [diffarr-sync-lists](/en/total5/diffarr-sync-lists/) |\n| `QUERIFY` | function | Builds a URL with a query string from an object. | [utility-globals](/en/total5/utility-globals/) |\n| `PROMISIFY` | function | `PROMISIFY(fn, a, b, c)` calls a function that takes a callback as the last argument and returns a promise. | [utility-globals](/en/total5/utility-globals/) |\n| `ENCRYPT` | function | Turns a value into an opaque token. | [encrypt-and-decrypt](/en/total5/encrypt-and-decrypt/) |\n| `DECRYPT` | function | Reads a value from a token created with `ENCRYPT`. | [encrypt-and-decrypt](/en/total5/encrypt-and-decrypt/) |\n| `ENCRYPTREQ` | function | Encrypts a value together with the user agent of a request (and its IP address when strict). | [request-bound-tokens](/en/total5/request-bound-tokens/) |\n| `DECRYPTREQ` | function | Reads a value created with `ENCRYPTREQ`; returns nothing when the request does not match. | [request-bound-tokens](/en/total5/request-bound-tokens/) |\n| `LOCALIZE` | function | Chooses the language of every request. | [localization](/en/total5/localization/) |\n| `TRANSLATE` | function | Translates the `@(...)` texts of a string into a language. | [localization](/en/total5/localization/) |\n| `Tangular` | object | The template engine (`Tangular.render()`). | [tangular-templates](/en/total5/tangular-templates/) |\n| `Ta` | object | Another name of `Tangular`, the template engine. | [template-files](/en/total5/template-files/) |\n| `Thelpers` | object | The helper functions available in Tangular templates. | [tangular-templates](/en/total5/tangular-templates/) |\n| `TEMPLATE` | function | Renders a Tangular template that is a text, a file of the `templates` folder or a URL, and returns a promise with the text. | [template-files](/en/total5/template-files/) |\n\n```js controllers/globals.js\nexports.install = function() {\n\tROUTE('GET /globals/', globals);\n};\n\nfunction globals($) {\n\tvar output = {};\n\tfor (var name of Object.getOwnPropertyNames(globalThis)) {\n\t\tvar value;\n\t\ttry {\n\t\t\tvalue = globalThis[name];\n\t\t} catch (e) {\n\t\t\tvalue = undefined;\n\t\t}\n\t\toutput[name] = typeof(value) === 'function' ? 'function' : value === null ? 'null' : typeof(value);\n\t}\n\t$.json(output);\n}\n```"}],"knowledge":{"content_sha256":"9a640579fbb48a0ccec8890088be894d9fbf539324ee4c7d49aa4e8833979023","source_sha256":"0c2ac5068f43ba0395b155f6f983ebbbb9689613890b9702ab10ecea1f8dface","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["ACTION","AIMODEL","AIPARSER","AJAX","API","AUDIT","AUTH","BACKUP","BLOCKED","CACHE","CLEANUP","CLONE","COMPONENTATOR","CONF","COPY","CORS","CRON","DATA","DEBUG","DECRYPT","DECRYPTREQ","DEF","DIFFARR","DOWNLOAD","EMIT","EMPTYARRAY","EMPTYOBJECT","ENCRYPT","ENCRYPTREQ","ERROR","EXEC","FILE","FILECACHE","FILESTORAGE","FUNC","GET","GUID","HASH","HTML","HTMLMAIL","HTTP","IMPORT","JSON","LDAP","LOAD","LOADCONFIG","LOADRESOURCE","LOCALIZE","LOGMAIL","MAIL","MAIN","MEMORIZE","MERGE","METHOD","MIDDLEWARE","MODS","NEWACTION","NEWAIMODEL","NEWAPI","NEWCALL","NEWCOMPONENT","NEWDB","NEWFORK","NEWMACRO","NEWMIDDLEWARE","NEWPUBLISH","NEWSCHEMA","NEWSUBSCRIBE","NEWTHREAD","NEWTHREADPOOL","NEWTRANSFORM","NOOP","NOSQL","NOW","NPMINSTALL","OFF","ONCE","OPENCLIENT","PATH","PAUSESERVER","PLUGINS","PROMISIFY","PROXY","PROXYCLIENT","PROXYSERVER","PUBLISH","PYPELINE","QUERIFY","REPO","REQUEST","REQUIRE","RESTB","RESTORE","ROUTE","SHELL","SMTP","SOCKET","SUBSCRIBE","SUCCESS","TAPI","TEMP","TEMPLATE","TMS","TMSCLIENT","TOUCH","TRANSFORM","TRANSLATE","Tangular.render","Total.js","Total.run","UID","UNAUTHORIZED","UNSUBSCRIBE","URL","WEBSOCKETCLIENT"],"language":"en"},"files":[{"path":"controllers/globals.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /globals/', globals);\n};\n\nfunction globals($) {\n\tvar output = {};\n\tfor (var name of Object.getOwnPropertyNames(globalThis)) {\n\t\tvar value;\n\t\ttry {\n\t\t\tvalue = globalThis[name];\n\t\t} catch (e) {\n\t\t\tvalue = undefined;\n\t\t}\n\t\toutput[name] = typeof(value) === 'function' ? 'function' : value === null ? 'null' : typeof(value);\n\t}\n\t$.json(output);\n}"}],"test":"// The names of a plain Node.js process, to find out what total5 adds\nconst spawn = process.getBuiltinModule('child_process').spawnSync;\nconst plain = JSON.parse(spawn(process.execPath, ['-p', 'JSON.stringify(Object.getOwnPropertyNames(globalThis))'], { encoding: 'utf8' }).stdout);\n\nconst all = (await get('/globals/')).json;\nconst added = {};\nfor (const name of Object.keys(all)) {\n\tif (!plain.includes(name))\n\t\tadded[name] = all[name];\n}\n\nconst expected = {\"ACTION\": \"function\", \"AIMODEL\": \"function\", \"AIPARSER\": \"function\", \"AJAX\": \"function\", \"API\": \"function\", \"AUDIT\": \"function\", \"AUTH\": \"function\", \"BACKUP\": \"function\", \"BLOCKED\": \"function\", \"CACHE\": \"object\", \"CLEANUP\": \"function\", \"CLONE\": \"function\", \"COMPONENTATOR\": \"function\", \"CONF\": \"object\", \"COPY\": \"function\", \"CORS\": \"function\", \"CRON\": \"function\", \"DATA\": \"object\", \"DB\": \"function\", \"DEBUG\": \"boolean\", \"DECRYPT\": \"function\", \"DECRYPTREQ\": \"function\", \"DEF\": \"object\", \"DIFFARR\": \"function\", \"DOWNLOAD\": \"function\", \"EMIT\": \"function\", \"EMPTYARRAY\": \"object\", \"EMPTYOBJECT\": \"object\", \"ENCRYPT\": \"function\", \"ENCRYPTREQ\": \"function\", \"ERROR\": \"function\", \"EXEC\": \"function\", \"ErrorBuilder\": \"function\", \"F\": \"object\", \"FILECACHE\": \"function\", \"FILESTORAGE\": \"function\", \"FUNC\": \"object\", \"Flow\": \"object\", \"FlowStream\": \"object\", \"GUID\": \"function\", \"HASH\": \"function\", \"HTMLMAIL\": \"function\", \"IMPORT\": \"function\", \"Image\": \"object\", \"LDAP\": \"function\", \"LOAD\": \"function\", \"LOADCONFIG\": \"function\", \"LOADRESOURCE\": \"function\", \"LOCALIZE\": \"function\", \"LOGMAIL\": \"function\", \"MAIL\": \"function\", \"MAIN\": \"object\", \"MEMORIZE\": \"function\", \"MERGE\": \"function\", \"MIDDLEWARE\": \"function\", \"MODS\": \"object\", \"Mail\": \"object\", \"NEWACTION\": \"function\", \"NEWAIMODEL\": \"function\", \"NEWAPI\": \"function\", \"NEWCALL\": \"function\", \"NEWCOMPONENT\": \"function\", \"NEWDB\": \"function\", \"NEWFORK\": \"function\", \"NEWMACRO\": \"function\", \"NEWMIDDLEWARE\": \"function\", \"NEWPUBLISH\": \"function\", \"NEWSCHEMA\": \"function\", \"NEWSUBSCRIBE\": \"function\", \"NEWTHREAD\": \"function\", \"NEWTHREADPOOL\": \"function\", \"NEWTRANSFORM\": \"function\", \"NOOP\": \"function\", \"NOSQL\": \"function\", \"NOW\": \"object\", \"NPMINSTALL\": \"function\", \"OFF\": \"function\", \"ON\": \"function\", \"ONCE\": \"function\", \"OPENCLIENT\": \"function\", \"PATH\": \"object\", \"PAUSESERVER\": \"function\", \"PLUGINS\": \"object\", \"PROMISIFY\": \"function\", \"PROXY\": \"function\", \"PROXYCLIENT\": \"function\", \"PROXYSERVER\": \"function\", \"PUBLISH\": \"function\", \"PYPELINE\": \"function\", \"QUERIFY\": \"function\", \"REPO\": \"object\", \"REQUEST\": \"function\", \"REQUIRE\": \"function\", \"RESTBuilder\": \"function\", \"RESTORE\": \"function\", \"ROUTE\": \"function\", \"SHELL\": \"function\", \"SUBSCRIBE\": \"function\", \"SUCCESS\": \"function\", \"TEMP\": \"object\", \"TEMPLATE\": \"function\", \"TMSCLIENT\": \"function\", \"TOUCH\": \"function\", \"TRANSFORM\": \"function\", \"TRANSLATE\": \"function\", \"Ta\": \"object\", \"Tangular\": \"object\", \"Thelpers\": \"object\", \"Total\": \"object\", \"TotalAPI\": \"function\", \"U\": \"object\", \"UID\": \"function\", \"UNAUTHORIZED\": \"function\", \"UNSUBSCRIBE\": \"function\", \"Utils\": \"object\", \"WEBSOCKETCLIENT\": \"function\", \"clearTimeout2\": \"function\", \"print\": \"function\", \"setTimeout2\": \"function\"};\n\nfor (const name of Object.keys(expected))\n\tassert(added[name] === expected[name], name + ' should be a ' + expected[name] + ', got: ' + added[name]);\n\nconst extra = Object.keys(added).filter(name => !expected[name]);\nassert(!extra.length, 'globals that this page does not list: ' + extra.join(', '));","test_kind":"http"}
{"id":"docs/configuration-keys","section":"docs","kind":"reference","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/docs/configuration-keys/","markdown_url":"https://forge.totaljs.es/en/docs/configuration-keys.md","tags":["reference","config","settings"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"d3751564816390d444846ac49eee06fde29d8c9ab0292e7ade5557cb79b1985c","checked_at":"2026-10-20T12:17:21.241Z","framework":"total5@0.0.19","node":"v24.14.1","browser":null,"test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Configuration keys of Total.js 5","description":"Configure Total.js 5 through CONF: application settings, HTTP responses, email and runtime defaults.","messages":[{"role":"user","content":"Configuration keys of Total.js 5\n\nConfigure Total.js 5 through CONF: application settings, HTTP responses, email and runtime defaults."},{"role":"assistant","content":"The `config` file holds one `key : value` setting per line and the values are available in the global `CONF` object (see the configuration recipe for the file format). The keys below are defined by `total5` 0.0.19 with these defaults; a key that is not here and not in the second table is yours. **Keys that start with `$` are settings of the framework** and `secret_` keys hold secrets.\n\nSet application values in the config file and read them through CONF. The table gives the defaults and the purpose of each setting.\n\n| Key | Default | Note |\n|---|---|---|\n| `name` | `'Total.js'` | The name of the application. In a view, `@{'%name'}` prints it. |\n| `version` | `'1.0.0'` | Application version used in startup output, statistics and the default HTTP ETag suffix (index.js). |\n| `author` | `''` | Application author displayed by startup imprint when nonempty (index.js). |\n| `secret` | `F.syshash` | Set the application secret explicitly in configuration. |\n| `secret_encryption` | `''` | Key read by encrypted WebSocket routes; this is distinct from an action encrypt metadata option (websocket.js). |\n| `secret_totalapi` | `''` | Fallback token sent by the built-in TotalAPI driver (api.js); keep server-side. |\n| `secret_csrf` | `''` | The secret of the CSRF tokens. Empty: the check always passes. |\n| `secret_tms` | `''` | The token that TMS clients must send in the header `x-token`. |\n| `$root` | `''` | URL prefix applied to route and asset paths; also adjusts the default API endpoint (routing.js, index.js). |\n| `$cors` | `''` | Hostnames separated by comma. |\n| `$api` | `'/api/'` | A default API endpoint. |\n| `$sourcemap` | `true` | Loader metadata setting; LOAD sets it from the final DEBUG mode or disables it for selective resource loads (index.js). |\n| `$httpreqlimit` | `0` | Request limit per ip. |\n| `$httpcompress` | `true` | Enables negotiated gzip for eligible responses; the dynamic response path checks a 256-byte threshold (controller.js). |\n| `$httpetag` | `''` | Suffix for framework-generated ETags; when empty, configuration derives it from the app version (index.js, controller.js). |\n| `$httpmaxsize` | `256` | KB. |\n| `$httprangebuffer` | `5120` | 5 MB. |\n| `$httptimeout` | `5` | 5 seconds. |\n| `$httpfiles` | `an object` | The file extensions that are served as static files, as an object `{ extension: true }`. |\n| `$httpchecktypes` | `true` | For multipart data only. |\n| `$httpmaxage` | `60` | In seconds. |\n| `$httpmaxkeys` | `33` | Maximum parsed query-string key count (utils.js); not a JSON-body field limit. |\n| `$httpmaxkey` | `25` | Maximum query-string key length used by the query parser (utils.js). |\n| `$httpcacheoffset` | `''` | E.g. 1 minute. |\n| `$blacklist` | `''` | HTTP/WebSocket request handling tests whether the configured value contains the client IP (http.js, websocket.js). Not a CIDR parser. |\n| `$xpoweredby` | `'Total.js'` | Value of the generated X-Powered-By response header; empty suppresses it in the inspected response paths (controller.js). |\n| `$minifyjs` | `true` | Minify the JavaScript files that are served. |\n| `$minifycss` | `true` | Minify the CSS files that are served. |\n| `$minifyhtml` | `true` | Minify the HTML that is sent. |\n| `$port` | `'auto'` | HTTP listen port; auto is resolved at startup, and Total.run options can override it (index.js). |\n| `$ip` | `'0.0.0.0'` | HTTP listen address; Total.run options can override it (index.js). |\n| `$unixsocket` | `''` | Alternative Unix socket listen path (index.js). Socket permissions and reverse proxy setup are deployment concerns. |\n| `$timezone` | `'utc'` | The time zone of the process (it is set in `process.env.TZ`). |\n| `$insecure` | `false` | Configuration sets the process TLS verification environment from this flag (index.js). Keep false for authenticated TLS connections. |\n| `$performance` | `false` | Adjusts HTTP agent maximum sockets and socket tuning (index.js); it is not evidence of an application speedup. |\n| `$filtererrors` | `true` | Suppresses matching runtime error patterns in the framework error logger (index.js). |\n| `$cleartemp` | `true` | Controls startup removal of selected generated/temp file extensions by the clear routine (index.js). |\n| `$customtitles` | `false` | When false, the view metadata generator appends the application name to a non-home page title (viewengine.js). |\n| `$clearcache` | `10` | Service-counter interval for resetting internal lookup/view/template/query-builder caches (index.js). Not a database deletion schedule. |\n| `$imageconverter` | `'gm'` | The program that `Image` calls: `gm`, `im` or `magick`. |\n| `$imagememory` | `0` | Disabled because e.g. GM v1.3.32 throws some error about the memory. |\n| `$stats` | `true` | Controls statistics output/update branch; selective LOAD can disable it (index.js). |\n| `$npmcache` | `'/var/www/.npm'` | Npm cache location passed by NPMINSTALL for the uid-33 execution branch (index.js). |\n| `$python` | `'python3'` | Interpreter command used by Python execution and pip helpers (index.js); install Python separately. |\n| `$wsmaxsize` | `256` | KB. |\n| `$wscompress` | `true` | Enables negotiation of WebSocket permessage-deflate on server connections (websocket.js). |\n| `$wsencodedecode` | `false` | Initial WebSocket server encode/decode setting (websocket.js); verify required wire format before changing it. |\n| `$wsmaxlatency` | `2000` | Ping-latency threshold in milliseconds used by WebSocket connection checks (websocket.js). |\n| `$proxytimeout` | `5` | 5 seconds. |\n| `$cookiesamesite` | `'Lax'` | Default SameSite cookie policy when a cookie does not specify one (controller.js). |\n| `$cookiesecure` | `false` | Default Secure cookie attribute when no per-cookie override is supplied (controller.js). |\n| `$csrfexpiration` | `'30 minutes'` | How long a CSRF token is valid. |\n| `$tapi` | `true` | Enables the built-in TotalAPI service driver; the special check operation bypasses this local gate (api.js). |\n| `$tapiurl` | `'eu'` | Regional hostname prefix used by the TotalAPI driver (api.js). |\n| `$tapimail` | `false` | Selects the TotalAPI mail path when the TotalAPI feature is enabled (mail.js). |\n| `$tapilogger` | `false` | Enables the TotalAPI logger path when service access and its secret are configured (index.js). |\n| `$imprint` | `true` | Enables the startup banner (index.js). |\n| `$tms` | `false` | Turns on the TMS WebSocket endpoint. |\n| `$tmsmaxsize` | `256` | The maximum size of a TMS message, in kB. |\n| `$tmsurl` | `'/$tms/'` | The URL of the TMS endpoint. |\n| `$tmsclearblocked` | `60` | In minutes. |\n| `$total5` | set at start | The folder of the `total5` package (the framework reads its own files from it). |\n| `$nodemodules` | set at start | The folder where `NPMINSTALL()` installs packages. |\n| `secret_uid` | set at start | Secret input used by the short encrypted-identifier utility; initialized from the system hash (index.js, utils.js). |\n| `$cryptoiv` | set at start | Initialization-vector buffer used by framework encryption helpers; initialized at startup (index.js, utils.js). |\n| `$httpexpire` | set at start | Future UTC HTTP expiry date initialized and periodically refreshed by the framework (index.js, controller.js). |\n\nThese keys have no default. They configure the email sending and are used by `MAIL()` and `HTMLMAIL()` (see the email recipe):\n\n| Key | Meaning |\n|---|---|\n| `mail_smtp` | The address of the SMTP server. |\n| `mail_smtp_options` | An object with the rest of the connection settings: `port`, `user`, `password`, `secure`... |\n| `mail_from` | The address of the sender. |\n| `mail_from_name` | The name of the sender. |\n| `mail_reply` | The address that gets the replies, added to every message. |\n| `mail_cc` | A copy address, added to every message. |\n| `mail_bcc` | A hidden copy address, added to every message. |\n\n```js controllers/config.js\nexports.install = function() {\n\tROUTE('GET /config/', config);\n};\n\nfunction config($) {\n\t$.json({ conf: CONF });\n}\n```"}],"knowledge":{"content_sha256":"2ef72a7b9fcde672c1a24fc856022e87b39a3e5facf68c57efc087c9fb4b90ef","source_sha256":"d3751564816390d444846ac49eee06fde29d8c9ab0292e7ade5557cb79b1985c","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["API","CIDR","CONF","CSRF","CSS","DEBUG","GET","HTML","HTMLMAIL","HTTP","JSON","LOAD","MAIL","NPMINSTALL","ROUTE","SMTP","TLS","TMS","Total.js","Total.run","URL","UTC"],"language":"en"},"files":[{"path":"controllers/config.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /config/', config);\n};\n\nfunction config($) {\n\t$.json({ conf: CONF });\n}"}],"test":"const conf = (await get('/config/')).json.conf;\nassert(conf, 'expected the configuration');\n\nconst expected = {\"$api\": \"/api/\", \"$blacklist\": \"\", \"$clearcache\": 10, \"$cleartemp\": true, \"$cookiesamesite\": \"Lax\", \"$cookiesecure\": false, \"$cors\": \"\", \"$csrfexpiration\": \"30 minutes\", \"$customtitles\": false, \"$filtererrors\": true, \"$httpcacheoffset\": \"\", \"$httpchecktypes\": true, \"$httpcompress\": true, \"$httpetag\": \"\", \"$httpmaxage\": 60, \"$httpmaxkey\": 25, \"$httpmaxkeys\": 33, \"$httpmaxsize\": 256, \"$httprangebuffer\": 5120, \"$httpreqlimit\": 0, \"$httptimeout\": 5, \"$imageconverter\": \"gm\", \"$imagememory\": 0, \"$imprint\": true, \"$insecure\": false, \"$localize\": true, \"$maxopenfiles\": 100, \"$minifycss\": true, \"$minifyhtml\": true, \"$minifyjs\": true, \"$npmcache\": \"/var/www/.npm\", \"$performance\": false, \"$proxytimeout\": 5, \"$python\": \"python3\", \"$root\": \"\", \"$stats\": true, \"$tapi\": true, \"$tapilogger\": false, \"$tapimail\": false, \"$tapiurl\": \"eu\", \"$timezone\": \"utc\", \"$tms\": false, \"$tmsclearblocked\": 60, \"$tmsmaxsize\": 256, \"$tmsurl\": \"/$tms/\", \"$unixsocket\": \"\", \"$version\": \"\", \"$wscompress\": true, \"$wsencodedecode\": false, \"$wsmaxlatency\": 2000, \"$wsmaxsize\": 256, \"$xpoweredby\": \"Total.js\", \"author\": \"\", \"name\": \"Total.js\", \"secret_csrf\": \"\", \"secret_encryption\": \"\", \"secret_tapi\": \"\", \"secret_tms\": \"\", \"secret_totalapi\": \"\", \"version\": \"1.0.0\"};\n\nfor (const key of Object.keys(expected))\n\tassert(JSON.stringify(conf[key]) === JSON.stringify(expected[key]), key + ' should be ' + JSON.stringify(expected[key]) + ', got: ' + JSON.stringify(conf[key]));\n\nconst listed = [\"name\", \"version\", \"author\", \"secret\", \"secret_encryption\", \"secret_totalapi\", \"secret_csrf\", \"secret_tapi\", \"secret_tms\", \"$root\", \"$cors\", \"$api\", \"$sourcemap\", \"$httpreqlimit\", \"$httpcompress\", \"$httpetag\", \"$httpmaxsize\", \"$httprangebuffer\", \"$httptimeout\", \"$httpfiles\", \"$httpchecktypes\", \"$httpmaxage\", \"$httpmaxkeys\", \"$httpmaxkey\", \"$httpcacheoffset\", \"$blacklist\", \"$xpoweredby\", \"$maxopenfiles\", \"$minifyjs\", \"$minifycss\", \"$minifyhtml\", \"$localize\", \"$port\", \"$ip\", \"$unixsocket\", \"$timezone\", \"$insecure\", \"$performance\", \"$filtererrors\", \"$cleartemp\", \"$customtitles\", \"$version\", \"$clearcache\", \"$imageconverter\", \"$imagememory\", \"$stats\", \"$npmcache\", \"$python\", \"$wsmaxsize\", \"$wscompress\", \"$wsencodedecode\", \"$wsmaxlatency\", \"$proxytimeout\", \"$cookiesamesite\", \"$cookiesecure\", \"$csrfexpiration\", \"$tapi\", \"$tapiurl\", \"$tapimail\", \"$tapilogger\", \"$imprint\", \"$tms\", \"$tmsmaxsize\", \"$tmsurl\", \"$tmsclearblocked\", \"$total5\", \"$nodemodules\", \"secret_uid\", \"$cryptoiv\", \"$httpexpire\"];\nfor (const key of listed.filter(key => key !== 'secret'))\n\tassert(key in conf, key + ' is listed but not defined');\n\nconst extra = Object.keys(conf).filter(key => !listed.includes(key));\nassert(!extra.length, 'keys that this page does not list: ' + extra.join(', '));","test_kind":"http"}
{"id":"docs/route-syntax","section":"docs","kind":"reference","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/docs/route-syntax/","markdown_url":"https://forge.totaljs.es/en/docs/route-syntax.md","tags":["reference","routing","syntax"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"519c67606cfdfef0bd7aaccd6ec30b046bfdb6e85e24687943f44e4086b6f092","checked_at":"2026-10-20T12:17:27.855Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Route syntax of Total.js 5","description":"Declare Total.js routes with methods, URL parameters, wildcards, flags, authentication, middleware and actions.","messages":[{"role":"user","content":"Route syntax of Total.js 5\n\nDeclare Total.js routes with methods, URL parameters, wildcards, flags, authentication, middleware and actions."},{"role":"assistant","content":"`ROUTE(definition, handler)` declares a route. The definition is one text with these parts, in this order:\n\n`[+|-]METHOD /url/{param}/ [@flag ...] [#middleware ...] [<limit] [--> *Action ...]`\n\n| Part | Meaning |\n|---|---|\n| `METHOD` | `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, or `SOCKET` for a WebSocket and `FILE` for URLs with an extension: a URL such as `/a.txt` is never matched by a `GET` route. |\n| `+` or `-` before the method | `+` accepts only requests that `AUTH()` approved, `-` only anonymous requests; any other request is answered with 401. Without a prefix the route is open. |\n| `/url/` | The path. Write the end slash: routes are matched with it. |\n| `{name}` | A segment of the path that is read from `$.params.name`. |\n| `*` as the last segment | Matches any path below it. A route without `*` is more specific and wins. |\n| `@flag` | A condition for the request: `@json`, `@xml`, `@xhr`, `@upload`, `@debug`, `@release`, `@mobile`, `@robot`, `@referer` (or `@referrer`). |\n| `#name` | A middleware registered with `MIDDLEWARE(name, fn)`. |\n| `<limit` | `<10kb` limits the size of the body, `<30s` the time of the request. |\n| `--> *Action` | Runs one or more actions with the input of the request and answers with the result. |\n| `#404`, `#401`... | A route whose definition is only `#` and a status code replaces the error page. |\n\nA request is handled by the first matching route. A request that does not meet the flags of a route continues to the next route and ends with a 404 when none matches; a request that does not meet `+` or `-` gets a 401. **A route without flags on the same URL always wins over a route with flags**, so never put both on one URL.\n\n```js definitions/auth.js\nAUTH(function($) {\n\tif ($.headers['x-token'] === 'secret')\n\t\t$.success({ id: '1' });\n\telse\n\t\t$.invalid();\n});\n\nMIDDLEWARE('stamp', function($, next) {\n\t$.response.headers['x-stamp'] = 'yes';\n\tnext();\n});\n\nNEWACTION('Echo/one', { name: 'one', input: '*text:String', action: ($, model) => $.callback({ one: model.text }) });\nNEWACTION('Echo/two', { name: 'two', action: ($, model) => $.callback({ two: true }) });\n```\n\n```js controllers/syntax.js\nexports.install = function() {\n\tROUTE('GET /plain/', plain);\n\tROUTE('GET /users/{id}/', user);\n\tROUTE('GET /files/*', files);\n\tROUTE('POST /json/ @json', json);\n\tROUTE('+GET /private/', plain);\n\tROUTE('-GET /guest/', plain);\n\tROUTE('GET /stamped/ #stamp', plain);\n\tROUTE('POST /echo/ @json --> *Echo/one');\n\tROUTE('FILE /robots-example.txt', file);\n\tROUTE('#404', missing);\n};\n\nfunction plain($) {\n\t$.json({ ok: true });\n}\n\nfunction user($) {\n\t$.json({ id: $.params.id });\n}\n\nfunction files($) {\n\t$.json({ url: $.url });\n}\n\nfunction json($) {\n\t$.json({ body: $.body });\n}\n\nfunction file($) {\n\t$.plain('from a FILE route');\n}\n\nfunction missing($) {\n\t$.response.status = 404;\n\t$.plain('custom 404');\n}\n```"}],"knowledge":{"content_sha256":"09125c53800b76738f7a5a0ef26c4af820bdac0879b1cc3eae94133af2f4387c","source_sha256":"519c67606cfdfef0bd7aaccd6ec30b046bfdb6e85e24687943f44e4086b6f092","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["AUTH","DELETE","FILE","GET","METHOD","MIDDLEWARE","NEWACTION","PATCH","POST","PUT","ROUTE","SOCKET","URL"],"language":"en"},"files":[{"path":"definitions/auth.js","language":"js","content":"AUTH(function($) {\n\tif ($.headers['x-token'] === 'secret')\n\t\t$.success({ id: '1' });\n\telse\n\t\t$.invalid();\n});\n\nMIDDLEWARE('stamp', function($, next) {\n\t$.response.headers['x-stamp'] = 'yes';\n\tnext();\n});\n\nNEWACTION('Echo/one', { name: 'one', input: '*text:String', action: ($, model) => $.callback({ one: model.text }) });\nNEWACTION('Echo/two', { name: 'two', action: ($, model) => $.callback({ two: true }) });"},{"path":"controllers/syntax.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /plain/', plain);\n\tROUTE('GET /users/{id}/', user);\n\tROUTE('GET /files/*', files);\n\tROUTE('POST /json/ @json', json);\n\tROUTE('+GET /private/', plain);\n\tROUTE('-GET /guest/', plain);\n\tROUTE('GET /stamped/ #stamp', plain);\n\tROUTE('POST /echo/ @json --> *Echo/one');\n\tROUTE('FILE /robots-example.txt', file);\n\tROUTE('#404', missing);\n};\n\nfunction plain($) {\n\t$.json({ ok: true });\n}\n\nfunction user($) {\n\t$.json({ id: $.params.id });\n}\n\nfunction files($) {\n\t$.json({ url: $.url });\n}\n\nfunction json($) {\n\t$.json({ body: $.body });\n}\n\nfunction file($) {\n\t$.plain('from a FILE route');\n}\n\nfunction missing($) {\n\t$.response.status = 404;\n\t$.plain('custom 404');\n}"}],"test":"assert((await get('/plain/')).json.ok === true, 'a plain route');\nassert((await get('/users/42/')).json.id === '42', '{id} is read from $.params');\nassert((await get('/files/a/b/c/')).json.url === '/files/a/b/c/', '* matches any path below');\n\nassert((await post('/json/', { a: 1 })).json.body.a === 1, '@json accepts a JSON body');\nconst notjson = await send('POST', '/json/', 'a=1', { headers: { 'content-type': 'application/x-www-form-urlencoded' } });\nassert(notjson.status === 404 && /custom 404/.test(notjson.body), 'a request that does not meet the flag falls to #404, got: ' + notjson.status + ' ' + notjson.body);\n\nassert((await get('/private/')).status === 401, '+ refuses an anonymous request with 401');\nassert((await get('/private/', { headers: { 'x-token': 'secret' } })).json.ok === true, '+ accepts an authenticated request');\nassert((await get('/guest/')).json.ok === true, '- accepts an anonymous request');\nconst guest = await get('/guest/', { headers: { 'x-token': 'secret' } });\nassert(guest.status === 401, '- refuses an authenticated request with 401, got: ' + guest.status);\n\nassert((await get('/stamped/')).headers.get('x-stamp') === 'yes', '#name runs the middleware');\nassert((await post('/echo/', { text: 'hi' })).json.one === 'hi', '--> runs the action');\n\nassert((await get('/robots-example.txt')).body === 'from a FILE route', 'FILE routes serve URLs with an extension');\nassert((await get('/nothing/here/')).body === 'custom 404', '#404 replaces the error page');","test_kind":"http"}
{"id":"docs/controller-api","section":"docs","kind":"reference","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/docs/controller-api/","markdown_url":"https://forge.totaljs.es/en/docs/controller-api.md","tags":["reference","controller","responses"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"b2aa2d23b19ed0f9fdf42cba44c9eeaef9b95c2ed92f50aa052e2145317d8997","checked_at":"2026-10-20T12:17:21.979Z","framework":"total5@0.0.19","node":"v24.14.1","browser":null,"test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"The $ object of a Total.js 5 handler","description":"Use the properties and methods of the controller received by a route handler.","messages":[{"role":"user","content":"The $ object of a Total.js 5 handler\n\nUse the properties and methods of the controller received by a route handler."},{"role":"assistant","content":"A route handler receives one object, written `$` in this site. It describes the request and it answers it. Answer **once**: after `$.json()`, `$.html()` or any other answer, the request is finished.\n\n## Properties\n\n| Property | Type | What it is |\n|---|---|---|\n| `$.url` | string | The path of the request, without the query string. |\n| `$.method` | string | `GET`, `POST`... |\n| `$.query` | object | The query string, parsed. |\n| `$.params` | object | The `{name}` segments of the route. |\n| `$.body` | object | The body, parsed by its content type (JSON, URL-encoded form, XML or multipart fields). |\n| `$.files` | array | The uploaded files of a multipart request. |\n| `$.headers` | object | The headers of the request, with lowercase names. |\n| `$.ip` | string | The IP address; the first address of `x-forwarded-for` when the header exists. |\n| `$.host` | string | The `host` header. |\n| `$.referrer` | string | The `referer` header. |\n| `$.protocol` | string | `http` or `https` (also when a proxy says so in `x-forwarded-proto`). |\n| `$.ext` | string | The extension of the URL, without the dot (`$.extension` is the same). |\n| `$.xhr` | boolean | `true` when the header `x-requested-with` is `XMLHttpRequest`. |\n| `$.mobile` | boolean | `true` when the user agent looks like a mobile browser. |\n| `$.robot` | boolean | `true` when the user agent looks like a crawler. |\n| `$.ua` | string | A short name of the browser and system, such as `iPhone Safari` or `Windows Chrome`. |\n| `$.user` | object | The user that `AUTH()` approved, or nothing. |\n| `$.language` | string | The language that `LOCALIZE()` chose. |\n| `$.datatype` | string | `json`, `urlencoded`, `multipart`, `xml`, `text` or `binary`: the type of the body of the request. |\n| `$.response` | object | What will be sent: `status`, `headers` and `value`. Change `$.response.status` and `$.response.headers` before you answer. |\n| `$.req`, `$.res` | objects | The Node.js request and response. |\n\n## Methods that answer\n\n| Method | What it does | Recipe |\n|---|---|---|\n| `$.json(value)` | Answers with JSON. | [responses](/en/total5/responses/) |\n| `$.jsonstring(text)` | Answers with a text that is already JSON, without serializing it again. | [controller-completion-and-cache](/en/total5/controller-completion-and-cache/) |\n| `$.html(text)` | Answers with HTML. | [responses](/en/total5/responses/) |\n| `$.plain(text)` | Answers with plain text (`$.text` is the same). | [responses](/en/total5/responses/) |\n| `$.xml(text)` | Answers with XML. | [responses](/en/total5/responses/) |\n| `$.empty()` | Answers with status 204 and no body. | [responses](/en/total5/responses/) |\n| `$.redirect(url, permanent)` | Redirects (302, or 301 when `permanent` is true). | [responses](/en/total5/responses/) |\n| `$.binary(buffer, type, name)` | Answers with bytes and a content type. | [responses](/en/total5/responses/) |\n| `$.file(path, name)` | Sends a file from disk. | [file-download](/en/total5/file-download/) |\n| `$.stream(type, stream, name)` | Sends a readable stream. | [file-download](/en/total5/file-download/) |\n| `$.filefs(storage, id)` | Sends a file of a `FILESTORAGE`. | [filestorage](/en/total5/filestorage/) |\n| `$.image(options)` | Creates and sends an image on demand. | [images](/en/total5/images/) |\n| `$.view(name, model)` | Renders a view with the layout and answers with it. | [views-and-layouts](/en/total5/views-and-layouts/) |\n| `$.layout(name)` | Chooses another layout for the next `$.view()`. | [view-sections](/en/total5/view-sections/) |\n| `$.proxy(url)` | Forwards the request to another URL and answers with its response. | [proxy](/en/total5/proxy/) |\n| `$.respond(value)` | Answers in the format of `$.response.output` (`json` when it is not set). | [controller-completion-and-cache](/en/total5/controller-completion-and-cache/) |\n| `$.invalid(value)` | Answers with an error: a text, a status code such as `404` or an `ErrorBuilder`. | [actions-crud](/en/total5/actions-crud/) |\n| `$.fallback(code)` | Answers with the page of an error code, the custom one when `ROUTE('#404')` exists. | [error-pages](/en/total5/error-pages/) |\n| `$.success(value)` | Answers `{ \"success\": true }`; `value` is added to it. | [responses](/en/total5/responses/) |\n| `$.callback(err, value)` | With an error answers `$.invalid(err)`, otherwise `$.json(value)`, or `$.success()` when `value` is `undefined`. Without arguments it returns a function `(err, response)` that does the same. | [actions-crud](/en/total5/actions-crud/) |\n| `$.successful(fn)` | Returns a function `(err, a, b, c)`: with an error it answers `$.invalid(err)`, otherwise it calls `fn(a, b, c)`. | [controller-completion-and-cache](/en/total5/controller-completion-and-cache/) |\n| `$.done(arg)` | Returns a function `(err, response)`: with an error it answers `$.invalid(err)`, otherwise it answers with the success object. | [controller-completion-and-cache](/en/total5/controller-completion-and-cache/) |\n\n## Other methods\n\n| Method | What it does | Recipe |\n|---|---|---|\n| `$.cookie(name, value, expiration)` | Reads a cookie, or writes one when you pass a value. | [headers-and-cookies](/en/total5/headers-and-cookies/) |\n| `$.csrf()` | Creates a CSRF token for this client. | [csrf-protection](/en/total5/csrf-protection/) |\n| `$.hostname(path)` | The address of this server: protocol, host and the path you pass. | [restbuilder](/en/total5/restbuilder/) |\n| `$.action(name, model)` | Runs an action with this request (user, query and params). | [calling-actions-from-code](/en/total5/calling-actions-from-code/) |\n| `$.transform(name, value, callback)` | Runs a transform with this request. | [transforms](/en/total5/transforms/) |\n| `$.authorize(callback)` | Runs `AUTH()` for this request and sets `$.user`; it returns a promise when there is no callback. | [controller-completion-and-cache](/en/total5/controller-completion-and-cache/) |\n| `$.notmodified(date)` | Answers 304 and returns `true` when the request has the header `if-modified-since` with that date. | [controller-completion-and-cache](/en/total5/controller-completion-and-cache/) |\n| `$.httpcache(date)` | Sets the cache headers and `last-modified` of the answer. | [controller-completion-and-cache](/en/total5/controller-completion-and-cache/) |\n| `$.clear()` | Removes the temporary files of the upload. They are removed anyway when the request ends. | [file-upload](/en/total5/file-upload/) |\n| `$.autoclear(false)` | Keeps the uploaded files after the request, so that you can use them later. | [upload-lifecycle-and-href](/en/total5/upload-lifecycle-and-href/) |\n| `$.custom()` | Tells the framework that you answer through `$.res` yourself: it will not send anything. | [controller-completion-and-cache](/en/total5/controller-completion-and-cache/) |\n| `$.href(key, value)` | Use `QUERIFY()` as in the linked recipe, the tested native query-link helper. | [upload-lifecycle-and-href](/en/total5/upload-lifecycle-and-href/) |\n| `$.flush()`, `$.resume()`, `$.free()`, `$.cancel()` | Internal methods: do not call them. | - |\n\nThe example answers with the properties and calls the methods that need no setup.\n\n```js controllers/api.js\nexports.install = function() {\n\tROUTE('GET /info/{a}/{b}/', info);\n\tROUTE('POST /body/ @json', body);\n\tROUTE('GET /answers/{kind}/', answers);\n\tROUTE('GET /callback/{kind}/', callback);\n};\n\nfunction info($) {\n\t$.json({\n\t\turl: $.url,\n\t\tmethod: $.method,\n\t\tquery: $.query,\n\t\tparams: $.params,\n\t\text: $.ext,\n\t\txhr: $.xhr,\n\t\tmobile: $.mobile,\n\t\trobot: $.robot,\n\t\tua: $.ua,\n\t\tip: $.ip,\n\t\thost: $.host,\n\t\treferrer: $.referrer,\n\t\tprotocol: $.protocol,\n\t\thostname: $.hostname('/x/'),\n\t\tdatatype: $.datatype\n\t});\n}\n\nfunction body($) {\n\t$.json({ body: $.body, datatype: $.datatype });\n}\n\nfunction answers($) {\n\tswitch ($.params.kind) {\n\t\tcase 'json': return $.json({ a: 1 });\n\t\tcase 'jsonstring': return $.jsonstring('{\"a\":1}');\n\t\tcase 'html': return $.html('<b>x</b>');\n\t\tcase 'plain': return $.plain('x');\n\t\tcase 'xml': return $.xml('<a>1</a>');\n\t\tcase 'empty': return $.empty();\n\t\tcase 'redirect': return $.redirect('/x/', true);\n\t\tcase 'binary': return $.binary(Buffer.from('abc'), 'application/octet-stream');\n\t\tcase 'invalid': return $.invalid(409);\n\t\tcase 'success': return $.success({ id: 7 });\n\t\tcase 'status': $.response.status = 201; return $.json({ created: true });\n\t\tcase 'header': $.response.headers['x-example'] = 'yes'; return $.json({});\n\t}\n}\n\nfunction callback($) {\n\tswitch ($.params.kind) {\n\t\tcase 'error': return $.callback('failed');\n\t\tcase 'value': return $.callback(null, { v: 1 });\n\t\tcase 'nothing': return $.callback(null);\n\t\tcase 'function': return setImmediate($.callback(), null, { later: true });\n\t\tcase 'successful': return setImmediate($.successful(function(a, b) { $.json({ a: a, b: b }); }), null, 'x', 'y');\n\t}\n}\n```"}],"knowledge":{"content_sha256":"b04b25b10d7f00afcc531b3bee7c01f76fd782ecbc8281873a3128d66e3c4614","source_sha256":"b2aa2d23b19ed0f9fdf42cba44c9eeaef9b95c2ed92f50aa052e2145317d8997","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["AUTH","CSRF","FILESTORAGE","GET","HTML","JSON","LOCALIZE","POST","QUERIFY","ROUTE","URL","XML","XMLH"],"language":"en"},"files":[{"path":"controllers/api.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /info/{a}/{b}/', info);\n\tROUTE('POST /body/ @json', body);\n\tROUTE('GET /answers/{kind}/', answers);\n\tROUTE('GET /callback/{kind}/', callback);\n};\n\nfunction info($) {\n\t$.json({\n\t\turl: $.url,\n\t\tmethod: $.method,\n\t\tquery: $.query,\n\t\tparams: $.params,\n\t\text: $.ext,\n\t\txhr: $.xhr,\n\t\tmobile: $.mobile,\n\t\trobot: $.robot,\n\t\tua: $.ua,\n\t\tip: $.ip,\n\t\thost: $.host,\n\t\treferrer: $.referrer,\n\t\tprotocol: $.protocol,\n\t\thostname: $.hostname('/x/'),\n\t\tdatatype: $.datatype\n\t});\n}\n\nfunction body($) {\n\t$.json({ body: $.body, datatype: $.datatype });\n}\n\nfunction answers($) {\n\tswitch ($.params.kind) {\n\t\tcase 'json': return $.json({ a: 1 });\n\t\tcase 'jsonstring': return $.jsonstring('{\"a\":1}');\n\t\tcase 'html': return $.html('<b>x</b>');\n\t\tcase 'plain': return $.plain('x');\n\t\tcase 'xml': return $.xml('<a>1</a>');\n\t\tcase 'empty': return $.empty();\n\t\tcase 'redirect': return $.redirect('/x/', true);\n\t\tcase 'binary': return $.binary(Buffer.from('abc'), 'application/octet-stream');\n\t\tcase 'invalid': return $.invalid(409);\n\t\tcase 'success': return $.success({ id: 7 });\n\t\tcase 'status': $.response.status = 201; return $.json({ created: true });\n\t\tcase 'header': $.response.headers['x-example'] = 'yes'; return $.json({});\n\t}\n}\n\nfunction callback($) {\n\tswitch ($.params.kind) {\n\t\tcase 'error': return $.callback('failed');\n\t\tcase 'value': return $.callback(null, { v: 1 });\n\t\tcase 'nothing': return $.callback(null);\n\t\tcase 'function': return setImmediate($.callback(), null, { later: true });\n\t\tcase 'successful': return setImmediate($.successful(function(a, b) { $.json({ a: a, b: b }); }), null, 'x', 'y');\n\t}\n}"}],"test":"const info = (await get('/info/one/two/?page=2', { headers: { 'x-requested-with': 'XMLHttpRequest', 'user-agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X) Mobile Safari', 'x-forwarded-for': '203.0.113.9, 10.0.0.1', referer: 'http://example.com/' } })).json;\nassert(info.url === '/info/one/two/' && info.method === 'GET', 'url and method, got: ' + JSON.stringify(info));\nassert(info.query.page === '2' && info.params.a === 'one' && info.params.b === 'two', 'query and params');\nassert(info.xhr === true && info.mobile === true && info.robot === false, 'xhr, mobile and robot, got: ' + JSON.stringify(info));\nassert(info.ua === 'iPhone Safari', 'ua is a short name, got: ' + info.ua);\nassert(info.ip === '203.0.113.9', 'ip is the first address of x-forwarded-for, got: ' + info.ip);\nassert(info.referrer === 'http://example.com/' && info.protocol === 'http', 'referrer and protocol');\nassert(/^http:\\/\\/[^/]+\\/x\\/$/.test(info.hostname) && info.hostname.indexOf(info.host) !== -1, 'hostname(), got: ' + info.hostname);\nassert(info.datatype === '', 'a GET has no body type, got: ' + JSON.stringify(info.datatype));\n\nconst robot = (await get('/info/a/b/', { headers: { 'user-agent': 'Googlebot/2.1 (+http://www.google.com/bot.html)' } })).json;\nassert(robot.robot === true && robot.mobile === false, 'a crawler is a robot, got: ' + JSON.stringify(robot));\n\nconst body = (await post('/body/', { a: 1 })).json;\nassert(body.body.a === 1 && body.datatype === 'json', 'the body is parsed by its content type, got: ' + JSON.stringify(body));\n\nconst json = await get('/answers/json/');\nassert(/application\\/json/.test(json.headers.get('content-type')) && json.json.a === 1, 'json');\nassert((await get('/answers/jsonstring/')).json.a === 1, 'jsonstring');\nconst html = await get('/answers/html/');\nassert(/text\\/html/.test(html.headers.get('content-type')) && html.body === '<b>x</b>', 'html');\nconst plain = await get('/answers/plain/');\nassert(/text\\/plain/.test(plain.headers.get('content-type')) && plain.body === 'x', 'plain');\nconst xml = await get('/answers/xml/');\nassert(/xml/.test(xml.headers.get('content-type')) && xml.body === '<a>1</a>', 'xml');\nassert((await get('/answers/empty/')).status === 204, 'empty is 204');\nconst redirect = await get('/answers/redirect/');\nassert(redirect.status === 301 && /\\/x\\//.test(redirect.headers.get('location')), 'a permanent redirect is 301, got: ' + redirect.status);\nconst binary = await get('/answers/binary/');\nassert(/octet-stream/.test(binary.headers.get('content-type')) && binary.body === 'abc', 'binary');\nassert((await get('/answers/invalid/')).status === 409, 'invalid(409) answers with that status');\nconst success = await get('/answers/success/');\nassert(success.json.success === true && success.json.value && success.json.value.id === 7, 'success(value), got: ' + success.body);\nassert((await get('/answers/status/')).status === 201, '$.response.status');\nassert((await get('/answers/header/')).headers.get('x-example') === 'yes', '$.response.headers');\n\nconst failed = await get('/callback/error/');\nassert(failed.status === 400 && /failed/.test(failed.body), 'callback(err) answers with an error, got: ' + failed.status + ' ' + failed.body);\nassert((await get('/callback/value/')).json.v === 1, 'callback(null, value) answers with JSON');\nassert((await get('/callback/nothing/')).json.success === true, 'callback(null) answers with success');\nassert((await get('/callback/function/')).json.later === true, 'callback() returns a function');\nconst successful = await get('/callback/successful/');\nassert(successful.json.a === 'x' && successful.json.b === 'y', 'successful(fn) calls fn with the arguments, got: ' + successful.body);","test_kind":"http"}
{"id":"docs/action-options","section":"docs","kind":"reference","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/docs/action-options/","markdown_url":"https://forge.totaljs.es/en/docs/action-options.md","tags":["reference","actions","validation"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"a56f70db3068822bd715f96c59aa412b6b8357fd0cde693ada42e127cb8670a5","checked_at":"2026-10-20T12:17:19.046Z","framework":"total5@0.0.19","node":"v24.14.1","browser":null,"test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Options of NEWACTION and the object an action receives","description":"Configure NEWACTION and use the action context to handle requests.","messages":[{"role":"user","content":"Options of NEWACTION and the object an action receives\n\nConfigure NEWACTION and use the action context to handle requests."},{"role":"assistant","content":"`NEWACTION(name, options)` declares an action. `name` is its id (`Users/insert`): routes use it with `--> *Users/insert` and your code with `ACTION('Users/insert', payload)`.\n\n## Options\n\n| Option | Value | What it does |\n|---|---|---|\n| `name` | text | A description of the action; it is also available as `$.name`. |\n| `action` | `function($, model)` | The code of the action. `model` is the validated input. |\n| `input` | text | The fields of the body, as `name:Type` separated by commas; a `*` before the name makes it required. See the input types page. |\n| `output` | text | The fields of the answer; fields that are not listed are removed. |\n| `params` | text | The `{name}` segments of the route, validated like `input`. |\n| `query` | text | The query string, validated like `input`. |\n| `partial` | `true` | Only the fields that were sent reach the action. Use it for `PATCH`. |\n| `user` | `true` or `false` | `true` answers 401 when there is no signed-in user; `false` accepts only anonymous requests. |\n| `permissions` | text or array | `'read,write'`: the user needs one of them (or to be a super administrator). |\n| `route` | text | Declares the route of the action here: `'GET /products/'`, or `true` for an `API` route. |\n| `middleware` | text | Names of middleware that run before the action, separated by spaces or commas. |\n| `timeout` | milliseconds | When the action does not answer in time, the request fails with the error `Timeout.`. |\n| `cache` | `true` or an object | Keep the result of an action with `CACHE`. |\n| `config` | object | Values that the action reads from `$.config`. |\n| `audit` | text | Records an automatic event after successful completion; see the dedicated recipe for tested boundaries. [action-audit](/en/total5/action-audit/) |\n| `csrf`, `encrypt`, `compress`, `publish` | - | [action-transport-boundaries](/en/total5/action-transport-boundaries/) |\n| `sa`, `su` | true | Either requires a user with either administrator flag. [action-hooks-and-admin](/en/total5/action-hooks-and-admin/) |\n| `oninput`, `onoutput` | function | Before the handler after validation; successful completion hook after output processing. [action-hooks-and-admin](/en/total5/action-hooks-and-admin/) |\n| `extend` | path | Adds the result under this path in the validated input and returns the extended payload. [action-hooks-and-admin](/en/total5/action-hooks-and-admin/) |\n\n## The object `$` of an action\n\n| Member | What it is |\n|---|---|\n| `$.query`, `$.params` | The validated query string and route segments. |\n| `$.payload` | The validated input; it is the same object as the second argument of `action`. |\n| `$.user` | The signed-in user. |\n| `$.config` | The `config` option. |\n| `$.name`, `$.id` | The `name` option and the id of the action. |\n| `$.controller` | The `$` of the route when the action was called from a route, so `$.controller.body` is the raw body. It does not exist when the action is called with `ACTION()`. |\n| `$.callback(value)` | Answers with a value; it is filtered by `output`. Without arguments it returns a function `(err, response)`. |\n| `$.success(value)` | Answers `{ success: true }`. |\n| `$.invalid(error)` | Answers with an error: a text, a status code or an `ErrorBuilder`. |\n| `$.done(arg)`, `$.successful(fn)` | Return callbacks for functions that report an error as their first argument. |\n| `$.cookie(name, value, expiration)` | Reads or writes a cookie of the request. |\n| `$.redirect(url)` | Redirects the request. |\n| `$.output(name)` | Chooses how a route answers: `json`, `html`, `xml`, `text`, `redirect`, `binary`, `jsonstring`, `empty` or `file`. |\n| `$.unauthorized(...permissions)` | Answers 401 and returns `true` when the user has none of the permissions. |\n| `$.action(name, payload)` | Runs another action with the same request. |\n| `$.hostname(path)` | The address of this server. |\n\n```js definitions/options.js\nMIDDLEWARE('tag', function($, next) {\n\t$.response.headers['x-tag'] = 'on';\n\tnext();\n});\n\nvar counter = { cached: 0 };\nMAIN.counter = counter;\n\nNEWACTION('Options/named', {\n\tname: 'The name',\n\tconfig: { limit: 3 },\n\taction: function($) {\n\t\t$.callback({ name: $.name, id: $.id, limit: $.config.limit });\n\t}\n});\n\nNEWACTION('Options/guarded', {\n\tname: 'Needs a user',\n\tuser: true,\n\tpermissions: 'read',\n\taction: function($) {\n\t\t$.success();\n\t}\n});\n\nNEWACTION('Options/slow', {\n\tname: 'Never answers',\n\ttimeout: 300,\n\taction: function($) {}\n});\n\nNEWACTION('Options/cached', {\n\tname: 'The cache option',\n\tquery: 'page:Number',\n\tcache: { expire: '1 minute', query: 'page' },\n\taction: function($) {\n\t\tcounter.cached++;\n\t\t$.callback({ page: $.query.page, runs: counter.cached });\n\t}\n});\n\nNEWACTION('Options/routed', {\n\tname: 'Declares its own route',\n\troute: 'GET /routed/ #tag',\n\taction: function($) {\n\t\t$.callback({ routed: true });\n\t}\n});\n\nNEWACTION('Options/output', {\n\tname: 'Chooses how to answer',\n\taction: function($) {\n\t\t$.output('plain');\n\t\t$.callback('plain text');\n\t}\n});\n\nAUTH(function($) {\n\tif ($.headers['x-user'])\n\t\t$.success({ id: '1', permissions: $.headers['x-user'].split(',') });\n\telse\n\t\t$.invalid();\n});\n```\n\n```js controllers/options.js\nexports.install = function() {\n\tROUTE('GET /named/ --> *Options/named');\n\tROUTE('GET /guarded/ --> *Options/guarded');\n\tROUTE('GET /slow/ --> *Options/slow');\n\tROUTE('GET /cached/ --> *Options/cached');\n\tROUTE('GET /output/ --> *Options/output');\n};\n```"}],"knowledge":{"content_sha256":"f1ffd2184b29e3078bcf6ecaeeb9ef9f5f0c5e6e808b6ea368164415466f662b","source_sha256":"a56f70db3068822bd715f96c59aa412b6b8357fd0cde693ada42e127cb8670a5","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["ACTION","API","AUTH","CACHE","GET","MAIN","MIDDLEWARE","NEWACTION","PATCH","ROUTE"],"language":"en"},"files":[{"path":"definitions/options.js","language":"js","content":"MIDDLEWARE('tag', function($, next) {\n\t$.response.headers['x-tag'] = 'on';\n\tnext();\n});\n\nvar counter = { cached: 0 };\nMAIN.counter = counter;\n\nNEWACTION('Options/named', {\n\tname: 'The name',\n\tconfig: { limit: 3 },\n\taction: function($) {\n\t\t$.callback({ name: $.name, id: $.id, limit: $.config.limit });\n\t}\n});\n\nNEWACTION('Options/guarded', {\n\tname: 'Needs a user',\n\tuser: true,\n\tpermissions: 'read',\n\taction: function($) {\n\t\t$.success();\n\t}\n});\n\nNEWACTION('Options/slow', {\n\tname: 'Never answers',\n\ttimeout: 300,\n\taction: function($) {}\n});\n\nNEWACTION('Options/cached', {\n\tname: 'The cache option',\n\tquery: 'page:Number',\n\tcache: { expire: '1 minute', query: 'page' },\n\taction: function($) {\n\t\tcounter.cached++;\n\t\t$.callback({ page: $.query.page, runs: counter.cached });\n\t}\n});\n\nNEWACTION('Options/routed', {\n\tname: 'Declares its own route',\n\troute: 'GET /routed/ #tag',\n\taction: function($) {\n\t\t$.callback({ routed: true });\n\t}\n});\n\nNEWACTION('Options/output', {\n\tname: 'Chooses how to answer',\n\taction: function($) {\n\t\t$.output('plain');\n\t\t$.callback('plain text');\n\t}\n});\n\nAUTH(function($) {\n\tif ($.headers['x-user'])\n\t\t$.success({ id: '1', permissions: $.headers['x-user'].split(',') });\n\telse\n\t\t$.invalid();\n});"},{"path":"controllers/options.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /named/ --> *Options/named');\n\tROUTE('GET /guarded/ --> *Options/guarded');\n\tROUTE('GET /slow/ --> *Options/slow');\n\tROUTE('GET /cached/ --> *Options/cached');\n\tROUTE('GET /output/ --> *Options/output');\n};"}],"test":"const named = (await get('/named/')).json;\nassert(named && named.name === 'The name' && named.id === 'Options/named' && named.limit === 3, 'name, id and config, got: ' + JSON.stringify(named));\n\nassert((await get('/guarded/')).status === 401, 'user: true refuses an anonymous request');\nassert((await get('/guarded/', { headers: { 'x-user': 'write' } })).status === 401, 'permissions refuses a user without the permission');\nassert((await get('/guarded/', { headers: { 'x-user': 'read,write' } })).json.success === true, 'permissions accepts a user with one of them');\n\nconst slow = await get('/slow/');\nassert(slow.status === 400 && /Timeout/.test(slow.body), 'timeout fails the request, got: ' + slow.status + ' ' + slow.body);\n\nconst first = (await get('/cached/?page=1')).json;\nconst again = (await get('/cached/?page=1')).json;\nconst other = (await get('/cached/?page=2')).json;\n// The cache option does nothing in this version: every request runs the action. If a newer version applies it, this fails and the page must be updated.\nassert(first.runs === 1 && again.runs === 2 && other.runs === 3, 'the action runs for every request, got: ' + JSON.stringify([first, again, other]));\n\nconst routed = await get('/routed/');\nassert(routed.json && routed.json.routed === true && routed.headers.get('x-tag') === 'on', 'route and middleware options, got: ' + routed.status + ' ' + routed.body);\n\nconst output = await get('/output/');\nassert(/text\\/plain/.test(output.headers.get('content-type')) && output.body === 'plain text', '$.output() chooses the format, got: ' + output.headers.get('content-type') + ' ' + output.body);","test_kind":"http"}
{"id":"docs/total5-coverage-map","section":"docs","kind":"reference","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/docs/total5-coverage-map/","markdown_url":"https://forge.totaljs.es/en/docs/total5-coverage-map.md","tags":["total5","reference","coverage","integrations"],"license":"CC-BY-4.0","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"Total.js 5 API guide by task","description":"Find the Total.js 5 API for routing, actions, storage, messaging and integrations.","messages":[{"role":"user","content":"Total.js 5 API guide by task\n\nFind the Total.js 5 API for routing, actions, storage, messaging and integrations."},{"role":"assistant","content":"Use these references to choose the native Total.js 5 API for your task. The examples target total5@0.0.19.\n\n## Native backend references\n\n| Area | Reference and examples |\n|---|---|\n| Installation, directories and startup | [getting started](/en/docs/getting-started/), [project structure](/en/docs/project-structure/), [LOAD and NPMINSTALL](/en/total5/load-and-npminstall/), [application bundles](/en/total5/application-bundles/), [release/debug modes](/en/total5/release-mode-and-debugging/) |\n| Global API and configuration | [globals](/en/docs/globals-total5/), [configuration keys](/en/docs/configuration-keys/), [configuration files](/en/total5/configuration-file/) |\n| Routing and controller | [route syntax](/en/docs/route-syntax/), [controller API](/en/docs/controller-api/), [completion and conditional responses](/en/total5/controller-completion-and-cache/), [upload lifecycle and query links](/en/total5/upload-lifecycle-and-href/), [static cache invalidation](/en/total5/touch-static-cache/) |\n| Actions and validation | [action reference](/en/docs/action-options/), [input types](/en/docs/input-types/), [enums and format boundaries](/en/total5/schema-enums-and-formats/), [numeric boundaries](/en/total5/numeric-schema-boundaries/), [hooks and admin guards](/en/total5/action-hooks-and-admin/), [transport options](/en/total5/action-transport-boundaries/), [audit](/en/total5/action-audit/) |\n| Database queries | [DATA](/en/total5/data-api/), [introductory DB/NEWDB](/en/total5/query-builder-db/), [builder operations and driver contract](/en/total5/query-builder-contract/), [real SQLite CRUD and transactions](/en/total5/query-builder-sqlite/), [NoSQL](/en/total5/nosql-crud/), [file storage](/en/total5/filestorage/) |\n| Messaging and platform protocols | [WebSockets](/en/total5/websocket-chat/), [TMS](/en/total5/tms/), [OPENCLIENT protocol fixture](/en/total5/openclient-protocol/), [real OpenLDAP fixture](/en/total5/ldap-openldap/) |\n| Flow and executable components | [FlowStream](/en/total5/flowstream/), [component testing](/en/total5/flowstream-component-test/), [flow files and workers](/en/total5/flow-files-and-workers/), [native server components](/en/total5/native-components/) |\n| Optional remote services | [API drivers](/en/total5/api-drivers/), [TotalAPI and Componentator local boundaries](/en/total5/remote-service-boundaries/) |\n| Full application | [Taskboard reference](/en/docs/taskboard-reference/): sessions, CSRF, owner-scoped NoSQL CRUD and browser assertions; exported app has separate restart/revocation checks |\n\nAdditional existing recipes cover views/localization, email, encryption helpers, caching, file downloads, proxies, jobs, threads, HTTP clients, macros, image conversion, AI-model driver contracts and Python processes. Use the Total5 catalogue and per-page declared scope for their exact behavior. Python scripts, image converter programs and directory servers are separate environment requirements, not npm dependencies of the framework core.\n\n## Which QueryBuilder?\n\nThe native DB/NEWDB API builds an options object for a registered driver. DATA is the singleton entry point for independent operations; DB creates a queue. Queue ordering is not a transaction. The SQLite recipe implements a bounded application-owned driver and explicit connection-owned transactions.\n\nThe optional [official querybuilderpg integration](https://github.com/totaljs/querybuilderpg) is a separate PostgreSQL package. Initialize a named connection with its connection string and pooling options, then use DATA. The application-owned SQLite driver is specific to SQLite.\n\n## Behavior in total5@0.0.19\n\nUse these version-specific behaviors when writing application code:\n\n- Compact Int accepts fractions and Number2 does not round to two places; SmallInt bounds differ from the familiar SQL range.\n- Compact JSON and IP labels are read as plain strings; use explicit native JSON Schema subtypes when you need those formats enforced.\n- For action csrf/encrypt/compress metadata, use explicit CSRF middleware and test the actual transport.\n- For LDAP, decide authentication from your own check of the bind result.\n- For query links use QUERIFY with a copied query.\n- For QueryBuilder column selection, use fields with an application allowlist."}],"knowledge":{"content_sha256":"aecbccee658ac585644f027f210437bc8b284f99b0ce3dd199ee4bd88bd5a667","source_sha256":"ef905ef9519362d0c51611c6f9a574ca0883ed58041d37778d8fec9631fb92ba","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["API","CRUD","CSRF","DATA","HTTP","JSON","LDAP","LOAD","NEWDB","NPMINSTALL","OPENCLIENT","QUERIFY","SQL","TMS","Total.js"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"docs/input-types","section":"docs","kind":"reference","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/docs/input-types/","markdown_url":"https://forge.totaljs.es/en/docs/input-types.md","tags":["reference","validation","input"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"13eafdd083dfd38c9851e846369795f235fa4ff3758b03d940bbdeb9db893f4a","checked_at":"2026-10-20T12:17:26.385Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Input types of NEWACTION","description":"Choose types for input, params, query and output definitions, including conversions and rejected values.","messages":[{"role":"user","content":"Input types of NEWACTION\n\nChoose types for input, params, query and output definitions, including conversions and rejected values."},{"role":"assistant","content":"A definition is a list of `name:Type` separated by commas, for example `*email:Email, age:Number`. A `*` before the name makes the field required. The same format is used by `input` (the body), `params`, `query` and `output`. **The type converts the value**: the action receives the converted value, not the text that was sent.\n\nThe names of the types are not case sensitive. Every type below was checked by sending the value in the second column.\n\n| Type | Value sent | Value received | Notes |\n|---|---|---|---|\n| `String` | `  hello  ` | `hello` | Text; leading and trailing spaces are removed. |\n| `Text` | `  text  ` | `  text  ` | Text kept exactly as it was sent. |\n| `Email` | ` A@B.CZ ` | `a@b.cz` | A valid email address, in lowercase; anything else is rejected. |\n| `Phone` | `+421 900 123 456` | `+421900123456` | A phone number; spaces are removed. |\n| `URL` | `https://totaljs.com/x` | `https://totaljs.com/x` | A valid URL; anything else is rejected. |\n| `Name` | `john smith` | `John Smith` | Capitalizes every word. |\n| `Capitalize` | `hello big world` | `Hello Big World` | Capitalizes every word. |\n| `Capitalize2` | `HELLO big WORLD` | `Hello big world` | Capitalizes the first letter only and lowercases the rest. |\n| `Lowercase` | `ABC` | `abc` | Lowercase. |\n| `Uppercase` | `abc` | `ABC` | Uppercase. |\n| `Search` | `Žluťoučký Kůň` | `zlutoucki kun` | Lowercase without accents, for searching. |\n| `SafeString` | `plain text` | `plain text` | Text; a value with HTML tags such as `<b>` is rejected. |\n| `Color` | `#FF0000` | `#FF0000` | [schema-enums-and-formats](/en/total5/schema-enums-and-formats/) |\n| `Base64` | `SGVsbG8=` | `SGVsbG8=` | [schema-enums-and-formats](/en/total5/schema-enums-and-formats/) |\n| `Zip` | `81101` | `81101` | [schema-enums-and-formats](/en/total5/schema-enums-and-formats/) |\n| `Icon` | `ti ti-home` | `ti ti-home` | [schema-enums-and-formats](/en/total5/schema-enums-and-formats/) |\n| `Number` | `12.5` | `12.5` | A number; the text is converted. |\n| `Float` | `3.14` | `3.14` | A number; the text is converted. |\n| `Decimal` | `2.5` | `2.5` | A number; the text is converted. |\n| `Boolean` | `true` | `true` | `true` or `false`; the text `true` is converted. |\n| `Date` | `2026-01-05` | `2026-01-05T00:00:00.000Z` | A date. In JSON it is sent as an ISO text. |\n\nOther forms:\n\n| Form | Example | Notes |\n|---|---|---|\n| `[String]`, `[Number]` | `ze_arr:[String]` | An array of that type: every item is converted. |\n| `{ x:Number, y:String }` | `zg_obj:{ x:Number, y:String }` | A nested object, validated with its own fields. |\n| `String(5)` | `zd_max:String(5)` | A text of at most 5 characters; a longer text is rejected. |\n\nA value that does not fit the type is rejected with status 400 and the name of the field in the error. These values were rejected in the checks: `SafeString` with `<b>x</b>`, `String(5)` with ten characters, a `UID` with `abc`, and a `GUID` with `not-guid`. The boundary recipe verifies `Int`, `SmallInt`, `TinyInt`, `Number2`: [numeric-schema-boundaries](/en/total5/numeric-schema-boundaries/). In this version `Int` does not truncate: `7.9` stays `7.9`.\n\n`UID` and `GUID` accept the values that `UID()` and `GUID()` create.\n\n```js definitions/types.js\nNEWACTION('Types/all', {\n\tname: 'Every type',\n\tinput: 'a_string:String, b_text:Text, c_email:Email, d_phone:Phone, e_url:URL, f_name:Name, g_capitalize:Capitalize, h_capitalize2:Capitalize2, i_lowercase:Lowercase, j_uppercase:Uppercase, k_search:Search, l_safestring:SafeString, m_color:Color, n_base64:Base64, o_zip:Zip, p_icon:Icon, q_number:Number, r_float:Float, s_decimal:Decimal, t_boolean:Boolean, u_date:Date, ze_arr:[String], zf_nums:[Number], zg_obj:{ x:Number, y:String }, zd_max:String(5)',\n\taction: function($, model) {\n\t\t$.callback(model);\n\t}\n});\n\nNEWACTION('Types/ids', {\n\tname: 'UID and GUID',\n\tinput: '*id:UID, *key:GUID',\n\taction: function($, model) {\n\t\t$.callback(model);\n\t}\n});\n```\n\n```js controllers/types.js\nexports.install = function() {\n\tROUTE('POST /types/ @json --> *Types/all');\n\tROUTE('POST /ids/ @json --> *Types/ids');\n\tROUTE('GET /newids/', ids);\n};\n\nfunction ids($) {\n\t$.json({ id: UID(), key: GUID() });\n}\n```"}],"knowledge":{"content_sha256":"44656a0df7eb097a27c8aa84184977e03028e29192b75dd120a80d0888d881bc","source_sha256":"13eafdd083dfd38c9851e846369795f235fa4ff3758b03d940bbdeb9db893f4a","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["ABC","FF0000","GET","GUID","HELLO","HTML","ISO","JSON","NEWACTION","POST","ROUTE","SGV","UID","URL","WORLD"],"language":"en"},"files":[{"path":"definitions/types.js","language":"js","content":"NEWACTION('Types/all', {\n\tname: 'Every type',\n\tinput: 'a_string:String, b_text:Text, c_email:Email, d_phone:Phone, e_url:URL, f_name:Name, g_capitalize:Capitalize, h_capitalize2:Capitalize2, i_lowercase:Lowercase, j_uppercase:Uppercase, k_search:Search, l_safestring:SafeString, m_color:Color, n_base64:Base64, o_zip:Zip, p_icon:Icon, q_number:Number, r_float:Float, s_decimal:Decimal, t_boolean:Boolean, u_date:Date, ze_arr:[String], zf_nums:[Number], zg_obj:{ x:Number, y:String }, zd_max:String(5)',\n\taction: function($, model) {\n\t\t$.callback(model);\n\t}\n});\n\nNEWACTION('Types/ids', {\n\tname: 'UID and GUID',\n\tinput: '*id:UID, *key:GUID',\n\taction: function($, model) {\n\t\t$.callback(model);\n\t}\n});"},{"path":"controllers/types.js","language":"js","content":"exports.install = function() {\n\tROUTE('POST /types/ @json --> *Types/all');\n\tROUTE('POST /ids/ @json --> *Types/ids');\n\tROUTE('GET /newids/', ids);\n};\n\nfunction ids($) {\n\t$.json({ id: UID(), key: GUID() });\n}"}],"test":"const sample = {\"a_string\": \"  hello  \", \"b_text\": \"  text  \", \"c_email\": \" A@B.CZ \", \"d_phone\": \"+421 900 123 456\", \"e_url\": \"https://totaljs.com/x\", \"f_name\": \"john smith\", \"g_capitalize\": \"hello big world\", \"h_capitalize2\": \"HELLO big WORLD\", \"i_lowercase\": \"ABC\", \"j_uppercase\": \"abc\", \"k_search\": \"Žluťoučký Kůň\", \"l_safestring\": \"plain text\", \"m_color\": \"#FF0000\", \"n_base64\": \"SGVsbG8=\", \"o_zip\": \"81101\", \"p_icon\": \"ti ti-home\", \"q_number\": \"12.5\", \"r_float\": \"3.14\", \"s_decimal\": \"2.5\", \"t_boolean\": \"true\", \"u_date\": \"2026-01-05\", \"ze_arr\": [\"a\", \"b\"], \"zf_nums\": [\"1\", \"2\"], \"zg_obj\": {\"x\": \"5\", \"y\": \"z\"}, \"zd_max\": \"abc\"};\nconst expected = {\"a_string\": \"hello\", \"b_text\": \"  text  \", \"c_email\": \"a@b.cz\", \"d_phone\": \"+421900123456\", \"e_url\": \"https://totaljs.com/x\", \"f_name\": \"John Smith\", \"g_capitalize\": \"Hello Big World\", \"h_capitalize2\": \"Hello big world\", \"i_lowercase\": \"abc\", \"j_uppercase\": \"ABC\", \"k_search\": \"zlutoucki kun\", \"l_safestring\": \"plain text\", \"m_color\": \"#FF0000\", \"n_base64\": \"SGVsbG8=\", \"o_zip\": \"81101\", \"p_icon\": \"ti ti-home\", \"q_number\": 12.5, \"r_float\": 3.14, \"s_decimal\": 2.5, \"t_boolean\": true, \"u_date\": \"2026-01-05T00:00:00.000Z\", \"ze_arr\": [\"a\", \"b\"], \"zf_nums\": [1, 2], \"zg_obj\": {\"x\": 5, \"y\": \"z\"}, \"zd_max\": \"abc\"};\n\nconst ok = await post('/types/', sample);\nassert(ok.status === 200 && ok.json, 'the values are accepted, got: ' + ok.status + ' ' + ok.body);\nfor (const key of Object.keys(expected))\n\tassert(JSON.stringify(ok.json[key]) === JSON.stringify(expected[key]), key + ' should be ' + JSON.stringify(expected[key]) + ', got: ' + JSON.stringify(ok.json[key]));\n\nfor (const [key, value] of [['l_safestring', '<b>x</b>'], ['zd_max', 'abcdefghij'], ['c_email', 'not an email'], ['e_url', 'nope']]) {\n\tconst bad = await post('/types/', Object.assign({}, sample, { [key]: value }));\n\tassert(bad.status === 400 && bad.body.indexOf(key) !== -1, key + ' rejects ' + JSON.stringify(value) + ', got: ' + bad.status + ' ' + bad.body);\n}\n\nconst ids = (await get('/newids/')).json;\nassert((await post('/ids/', ids)).status === 200, 'UID() and GUID() values are accepted');\nassert((await post('/ids/', { id: 'abc', key: ids.key })).status === 400, 'a UID that was not created by UID() is rejected');\nassert((await post('/ids/', { id: ids.id, key: 'not-guid' })).status === 400, 'a GUID that was not created by GUID() is rejected');","test_kind":"http"}
{"id":"docs/globals-jcomponent","section":"docs","kind":"reference","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/docs/globals-jcomponent/","markdown_url":"https://forge.totaljs.es/en/docs/globals-jcomponent.md","tags":["reference","globals"],"license":"CC-BY-4.0","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"09c3b668d5f5291901396a81010ec785f3b74b156ef859b6c7afce198b4a44d3","checked_at":"2026-10-20T12:17:24.877Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"browser","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Global names of jComponent 20","description":"Global functions and objects of jComponent 20, with their purpose and usage recipes.","messages":[{"role":"user","content":"Global names of jComponent 20\n\nGlobal functions and objects of jComponent 20, with their purpose and usage recipes."},{"role":"assistant","content":"jComponent 20 exposes global functions and objects for state, components, requests and browser utilities. The bundle also includes jQuery. Use the table to find a name and its usage recipe.\n\n| Name | Kind | What it does | Recipe |\n|---|---|---|---|\n| `$` | function | jQuery. | [getting-started](/en/jcomponent/getting-started/) |\n| `$totaluidate` | function | Internal: the hour minus 12 from noon on (`$totaluidate(13)` is `1`, `$totaluidate(5)` stays `5`). | [data-helpers](/en/jcomponent/data-helpers/) |\n| `$W` | object | The jQuery object of the window. | [dom-helpers](/en/jcomponent/dom-helpers/) |\n| `ADAPT` | function | Replaces the markers (`~PATH~`, `~ID~`, `CLASS`) and translates the text of a component. | [find-and-call](/en/jcomponent/find-and-call/) |\n| `AJAX` | function | `AJAX('METHOD /url/', data, callback)` makes a request to the server. | [ajax](/en/jcomponent/ajax/) |\n| `API` | function | Calls an `ROUTE('API ...')` endpoint of Total.js with a schema name and data. | [api-calls](/en/jcomponent/api-calls/) |\n| `APPEARANCE` | function | Sets the color, the dark mode and the CSS variables of the page. | [dom-helpers](/en/jcomponent/dom-helpers/) |\n| `ASETTER` | function | Returns a function that calls `SETTER()` later. | [find-and-call](/en/jcomponent/find-and-call/) |\n| `ATTRD` | function | Reads a `data-` attribute of an element or an event. | [dom-helpers](/en/jcomponent/dom-helpers/) |\n| `BLOCKED` | function | Returns false the first time and true during a time: a guard against repeated actions. | [state-shortcuts](/en/jcomponent/state-shortcuts/) |\n| `CACHE` | function | Stores a value in the browser for a period. | [storage-cache-pref-cookies](/en/jcomponent/storage-cache-pref-cookies/) |\n| `CACHEPATH` | function | Keeps a path of the state in the cache and restores it. | [state-shortcuts](/en/jcomponent/state-shortcuts/) |\n| `CL` | function | Loads the code lists that are registered with `CLINIT()` and are not loaded yet. | [data-helpers](/en/jcomponent/data-helpers/) |\n| `clearTimeout2` | function | Cancels a timer of `setTimeout2()`. | [state-shortcuts](/en/jcomponent/state-shortcuts/) |\n| `CLINIT` | function | Registers a code list and the function that loads it. | [data-helpers](/en/jcomponent/data-helpers/) |\n| `CLONE` | function | Deep copy of a value, dates included. | [state-shortcuts](/en/jcomponent/state-shortcuts/) |\n| `CLRELOAD` | function | Loads code lists again. | [data-helpers](/en/jcomponent/data-helpers/) |\n| `CMD` | function | Runs a command of a component. Use the `CMD` of an instance or `EXEC`. | [commands](/en/jcomponent/commands/) |\n| `COMPILE` | function | The same function as `NOOP` in jComponent 20. | [find-and-call](/en/jcomponent/find-and-call/) |\n| `COMPONENT` | function | Declares a component. | [custom-component](/en/jcomponent/custom-component/) |\n| `COMPONENTS` | function | The ready components under a path. | [find-and-call](/en/jcomponent/find-and-call/) |\n| `CONFIG` | function | Changes the configuration of components found by a selector. | [component-config](/en/jcomponent/component-config/) |\n| `COOKIES` | object | Reads, writes and removes cookies. | [storage-cache-pref-cookies](/en/jcomponent/storage-cache-pref-cookies/) |\n| `COPY` | function | Copies the properties of an object into another one, deep. | [state-shortcuts](/en/jcomponent/state-shortcuts/) |\n| `DAYS` | object | The names of the days of the week in English, from Sunday. | [data-helpers](/en/jcomponent/data-helpers/) |\n| `DECRYPT` | function | Reads a value made by `ENCRYPT()` with the same key and type. | [data-helpers](/en/jcomponent/data-helpers/) |\n| `DEF` | object | Defaults of jComponent. `DEF.fallback` is the address from which components that are not loaded are downloaded. | - |\n| `DIFFDOM` | function | Makes the children of an element the same as a new list without redrawing the ones that stay. | [dom-helpers](/en/jcomponent/dom-helpers/) |\n| `EMIT` | function | Emits a global event. | [events](/en/jcomponent/events/) |\n| `EMPTYARRAY` | object | A frozen empty array. | [data-helpers](/en/jcomponent/data-helpers/) |\n| `EMPTYOBJECT` | object | A frozen empty object. | [data-helpers](/en/jcomponent/data-helpers/) |\n| `ENCRYPT` | function | Turns a value into hexadecimal text with a check of the key (obfuscation, not security). | [data-helpers](/en/jcomponent/data-helpers/) |\n| `ENV` | function | Registers or reads a value of the page, used in URLs as `[name]`. | [environment-variables](/en/jcomponent/environment-variables/) |\n| `ENVIRONMENT` | function | Sets the name of the application for the storage, the version and the language. | [dom-helpers](/en/jcomponent/dom-helpers/) |\n| `ERROR` | function | Helper to handle the validation errors of an `API()` answer. | [api-calls](/en/jcomponent/api-calls/) |\n| `ERRORS` | function | Returns the invalid components under a path. | [validation](/en/jcomponent/validation/) |\n| `EXE` | function | Calls a plugin method, a global function or a function of the state (the same as `EXEC()`). | [find-and-call](/en/jcomponent/find-and-call/) |\n| `EXEC` | function | Calls the method of a plugin (`EXEC('plugin/method')`) or a global function. | [commands](/en/jcomponent/commands/) |\n| `EXTENSION` | function | Adds code to every instance of a component or plugin. | [extensions](/en/jcomponent/extensions/) |\n| `FIND` | function | Finds a ready component by name, path or id. | [find-and-call](/en/jcomponent/find-and-call/) |\n| `FLAGS` | function | Dispatches custom path flags and invokes a callback. | [custom-flags](/en/jcomponent/custom-flags/) |\n| `FN` | function | Compiles an expression into a function and remembers it. | [data-helpers](/en/jcomponent/data-helpers/) |\n| `FREE` | function | Runs the cleanup of removed components at once. | [component-lifecycle](/en/jcomponent/component-lifecycle/) |\n| `FUNC` | object | An object for functions that `EXE('FUNC.name')` can call. | [find-and-call](/en/jcomponent/find-and-call/) |\n| `GET` | function | Reads a value from a path of the state. | [state-api](/en/jcomponent/state-api/) |\n| `GUID` | function | Returns a random id. | [utilities](/en/jcomponent/utilities/) |\n| `HASH` | function | Returns a numeric hash. | [utilities](/en/jcomponent/utilities/) |\n| `HIDDEN` | function | True when an element is not shown. | [dom-helpers](/en/jcomponent/dom-helpers/) |\n| `IMPORT` | function | Loads an HTML part, a script or a style from a URL. | [ui-import](/en/jcomponent/ui-import/) |\n| `INC` | function | Adds 1 (or the given number) to the number in a path. | [state-shortcuts](/en/jcomponent/state-shortcuts/) |\n| `inDOM` | function | True while an element is in the document. | [dom-helpers](/en/jcomponent/dom-helpers/) |\n| `isIE` | boolean | True in Internet Explorer. | [dom-helpers](/en/jcomponent/dom-helpers/) |\n| `isMOBILE` | boolean | True on a mobile device. | [dom-helpers](/en/jcomponent/dom-helpers/) |\n| `isPRIVATEMODE` | boolean | True in a private window. | [dom-helpers](/en/jcomponent/dom-helpers/) |\n| `isROBOT` | boolean | True for a search engine robot. | [dom-helpers](/en/jcomponent/dom-helpers/) |\n| `isSTANDALONE` | boolean | True when the page runs as an installed application. | [dom-helpers](/en/jcomponent/dom-helpers/) |\n| `isTOUCH` | boolean | True on a device with touch. | [dom-helpers](/en/jcomponent/dom-helpers/) |\n| `jComponent` | object | The engine of jComponent: `M.components`, `M.version`... | [find-and-call](/en/jcomponent/find-and-call/) |\n| `jQuery` | function | jQuery, bundled in the same file. | [getting-started](/en/jcomponent/getting-started/) |\n| `LOCALIZE` | function | Loads a dictionary in the format of the Total.js resource files. | [data-helpers](/en/jcomponent/data-helpers/) |\n| `M` | object | The engine of jComponent (the same object as `jComponent`, `Total` and `MAIN`). | [find-and-call](/en/jcomponent/find-and-call/) |\n| `MAIN` | object | The engine of jComponent (the same object as `jComponent`, `Total` and `M`). | [find-and-call](/en/jcomponent/find-and-call/) |\n| `MIDDLEWARE` | function | Registers a function that runs before a route, or runs a list of them in order. | [find-and-call](/en/jcomponent/find-and-call/) |\n| `MONTHS` | object | The names of the months in English. | [data-helpers](/en/jcomponent/data-helpers/) |\n| `NAV` | object | The current location: `url`, `query`, `params`, and `back()`/`forward()`. | [spa-routing](/en/jcomponent/spa-routing/) |\n| `NEWUIBIND` | function | Makes a `ui-bind` on an element from the code. | [find-and-call](/en/jcomponent/find-and-call/) |\n| `NODEINDEXOF` | function | The position of an element among its siblings. | [dom-helpers](/en/jcomponent/dom-helpers/) |\n| `NODEINSERT` | function | Moves an element after or before another one. | [dom-helpers](/en/jcomponent/dom-helpers/) |\n| `NODEMOVE` | function | Moves an element one place down or up in its parent. | [dom-helpers](/en/jcomponent/dom-helpers/) |\n| `NOOP` | function | A function that does nothing. | [state-shortcuts](/en/jcomponent/state-shortcuts/) |\n| `NOTFOCUSED` | function | True when the browser tab is in the background. | [dom-helpers](/en/jcomponent/dom-helpers/) |\n| `NOW` | object | A `Date` that jComponent keeps up to date. | [data-helpers](/en/jcomponent/data-helpers/) |\n| `NUL` | function | Sets the value of a path to `null` (the same as `NULL`). | [state-shortcuts](/en/jcomponent/state-shortcuts/) |\n| `NULL` | function | Sets the value of a path to `null`. | [state-shortcuts](/en/jcomponent/state-shortcuts/) |\n| `OFF` | function | Removes listeners of a global event. | [events](/en/jcomponent/events/) |\n| `ON` | function | Registers a listener for a global event. | [events](/en/jcomponent/events/) |\n| `PARSE` | function | Parses JSON and turns date strings into dates. | [utilities](/en/jcomponent/utilities/) |\n| `PIPE` | function | `API()` to the address in `DEF.pipe`. | [find-and-call](/en/jcomponent/find-and-call/) |\n| `PLUGIN` | function | Declares a plugin. | [plugin](/en/jcomponent/plugin/) |\n| `PLUGINS` | object | The plugins of the page by their path. | [find-and-call](/en/jcomponent/find-and-call/) |\n| `PREF` | object | User preferences saved in the browser. | [storage-cache-pref-cookies](/en/jcomponent/storage-cache-pref-cookies/) |\n| `PUSH` | function | Adds a value to an array of the state and notifies. | [state-api](/en/jcomponent/state-api/) |\n| `QUERIFY` | function | Makes a query string from an object, also after an address or a method. | [data-helpers](/en/jcomponent/data-helpers/) |\n| `REDIRECT` | function | Changes the address without loading the page and runs the matching route. | [spa-routing](/en/jcomponent/spa-routing/) |\n| `REPO` | object | An object of yours to share data between components. | [find-and-call](/en/jcomponent/find-and-call/) |\n| `RESET` | function | Clears the touched and modified states under a path. | [validation](/en/jcomponent/validation/) |\n| `ROUTE` | function | Registers a client-side route. | [spa-routing](/en/jcomponent/spa-routing/) |\n| `SCROLLBAR` | function | Wraps an element with a custom scrollbar. | [dom-helpers](/en/jcomponent/dom-helpers/) |\n| `SCROLLBARWIDTH` | function | The width that a scrollbar takes. | [dom-helpers](/en/jcomponent/dom-helpers/) |\n| `SEEX` | function | Calls a function by name or sets a path. | [find-and-call](/en/jcomponent/find-and-call/) |\n| `SET` | function | Writes a value at a path of the state and notifies everything bound to it. | [state-api](/en/jcomponent/state-api/) |\n| `SETTER` | function | Calls a method of every component with a name, path or id. | [find-and-call](/en/jcomponent/find-and-call/) |\n| `setTimeout2` | function | A debounced `setTimeout()` with an id and an optional limit of restarts. | [state-shortcuts](/en/jcomponent/state-shortcuts/) |\n| `STRINGIFY` | function | `JSON.stringify()` that can compress empty values and choose the fields. | [data-helpers](/en/jcomponent/data-helpers/) |\n| `STYLE` | function | Adds a style to the head; the same id replaces it. | [dom-helpers](/en/jcomponent/dom-helpers/) |\n| `Ta` | object | The same object as `Tangular`, the template engine. | [data-helpers](/en/jcomponent/data-helpers/) |\n| `Tangular` | object | The template engine. | [ui-bind-templates](/en/jcomponent/ui-bind-templates/) |\n| `TAPI` | function | `API()` to the address in `DEF.api`. | [find-and-call](/en/jcomponent/find-and-call/) |\n| `TEMP` | object | An object for temporary data. | [find-and-call](/en/jcomponent/find-and-call/) |\n| `Thelpers` | object | The helpers of the templates (`Tangular.helpers`): add one with `Thelpers.name = fn`. | [data-helpers](/en/jcomponent/data-helpers/) |\n| `TOGGLE` | function | Changes the boolean in a path. | [state-shortcuts](/en/jcomponent/state-shortcuts/) |\n| `Total` | object | The engine of jComponent (the same object as `jComponent`). | [find-and-call](/en/jcomponent/find-and-call/) |\n| `TRANSLATE` | function | Replaces every `@(text)` with its translation. | [data-helpers](/en/jcomponent/data-helpers/) |\n| `UNAUTHORIZED` | function | True when `window.user` has none of the given permissions. | [data-helpers](/en/jcomponent/data-helpers/) |\n| `UNWATCH` | function | Removes the watchers of a path made with `WATCH()`. | [state-shortcuts](/en/jcomponent/state-shortcuts/) |\n| `UPD` | function | Notifies a path after you changed the object in place. | [state-api](/en/jcomponent/state-api/) |\n| `UPDATE` | function | Notifies the components and the watchers of a path without changing it (alias `UPD`). | [state-shortcuts](/en/jcomponent/state-shortcuts/) |\n| `UPLOAD` | function | Sends a `FormData` to a route with progress. | [upload](/en/jcomponent/upload/) |\n| `VISIBLE` | function | True when an element is in the visible part of the window. | [dom-helpers](/en/jcomponent/dom-helpers/) |\n| `W` | object | The `window`. | [find-and-call](/en/jcomponent/find-and-call/) |\n| `WAIT` | function | Runs a function when another one returns true or a path has a value; checks every 500 ms. | [state-shortcuts](/en/jcomponent/state-shortcuts/) |\n| `WARN` | function | Writes to `console.warn`. | [state-shortcuts](/en/jcomponent/state-shortcuts/) |\n| `WATCH` | function | Runs a function every time a path changes. | [state-api](/en/jcomponent/state-api/) |\n| `WH` | number | The height of the window. | [dom-helpers](/en/jcomponent/dom-helpers/) |\n| `WIDTH` | function | The size class of the width of an element or the window: xs, sm, md or lg. | [dom-helpers](/en/jcomponent/dom-helpers/) |\n| `WW` | number | The width of the window. | [dom-helpers](/en/jcomponent/dom-helpers/) |\n\n```html public/index.html\n<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<p>The test lists the globals of this page.</p>\n</body>\n</html>\n```"}],"knowledge":{"content_sha256":"475033e653e583e6bfd9374c95487eef789587c4d321284b4df134d1eb32018b","source_sha256":"09c3b668d5f5291901396a81010ec785f3b74b156ef859b6c7afce198b4a44d3","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"jcomponent20","url":"https://github.com/totaljs/jComponent","revision":"3fedd009a277f50fddc608b97afbec553b67c51b","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"01ce7c4d3dbd3d14b5a7ca9f44172ed332fa1eed250505811d4f828b38254bf9","license_files":[{"path":"LICENSE","sha256":"abf1260b51729c2fc0da7917e3564878725212bd7859683e7d5af91b6f9192bd","text":true,"characters":1095},{"path":"license.txt","sha256":"7226fe6e0be959f3c5e337bec8ca39ec08e694c8051dd7bc09d3e1a242abdc88","text":true,"characters":1099}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["ADAPT","AJAX","API","APPEARANCE","ASETTER","ATTRD","BLOCKED","CACHE","CACHEPATH","CLASS","CLINIT","CLONE","CLRELOAD","CMD","COMPILE","COMPONENT","COMPONENTS","CONFIG","COOKIES","COPY","CSS","DAYS","DECRYPT","DEF","DIFFDOM","DOCTYPE","EMIT","EMPTYARRAY","EMPTYOBJECT","ENCRYPT","ENV","ENVIRONMENT","ERROR","ERRORS","EXE","EXEC","EXTENSION","FIND","FLAGS","FREE","FUNC","GET","GUID","HASH","HIDDEN","HTML","IMPORT","INC","JSON","LOCALIZE","MAIN","METHOD","MIDDLEWARE","MONTHS","NAV","NEWUIBIND","NODEINDEXOF","NODEINSERT","NODEMOVE","NOOP","NOTFOCUSED","NOW","NUL","NULL","OFF","PARSE","PATH","PIPE","PLUGIN","PLUGINS","PREF","PUSH","QUERIFY","REDIRECT","REPO","RESET","ROUTE","SCROLLBAR","SCROLLBARWIDTH","SEEX","SET","SETTER","STRINGIFY","STYLE","TAPI","TEMP","TOGGLE","TRANSLATE","Tangular.helpers","Total.js","UNAUTHORIZED","UNWATCH","UPD","UPDATE","UPLOAD","URL","VISIBLE","WAIT","WARN","WATCH","WIDTH"],"language":"en"},"files":[{"path":"public/index.html","language":"html","content":"<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<p>The test lists the globals of this page.</p>\n</body>\n</html>"}],"test":"const results = [];\nconst check = (ok, msg) => ok || results.push(msg);\n\n// The names of an empty page, to find out what jComponent adds\nconst frame = document.createElement('iframe');\ndocument.body.appendChild(frame);\nconst plain = new Set(Object.getOwnPropertyNames(frame.contentWindow));\nframe.remove();\n\nconst added = {};\nfor (const name of Object.getOwnPropertyNames(window)) {\n\tif (plain.has(name) || /^jQuery\\d+$/.test(name))\n\t\tcontinue;\n\tlet value;\n\ttry {\n\t\tvalue = window[name];\n\t} catch (e) {}\n\tadded[name] = typeof value === 'function' ? 'function' : value === null ? 'null' : typeof value;\n}\n\nconst expected = {\"$\": \"function\", \"$W\": \"object\", \"$totaluidate\": \"function\", \"ADAPT\": \"function\", \"AJAX\": \"function\", \"API\": \"function\", \"APPEARANCE\": \"function\", \"ASETTER\": \"function\", \"ATTRD\": \"function\", \"BLOCKED\": \"function\", \"CACHE\": \"function\", \"CACHEPATH\": \"function\", \"CL\": \"function\", \"CLINIT\": \"function\", \"CLONE\": \"function\", \"CLRELOAD\": \"function\", \"CMD\": \"function\", \"COMPILE\": \"function\", \"COMPONENT\": \"function\", \"COMPONENTS\": \"function\", \"CONFIG\": \"function\", \"COOKIES\": \"object\", \"COPY\": \"function\", \"DAYS\": \"object\", \"DECRYPT\": \"function\", \"DEF\": \"object\", \"DIFFDOM\": \"function\", \"EMIT\": \"function\", \"EMPTYARRAY\": \"object\", \"EMPTYOBJECT\": \"object\", \"ENCRYPT\": \"function\", \"ENV\": \"function\", \"ENVIRONMENT\": \"function\", \"ERROR\": \"function\", \"ERRORS\": \"function\", \"EXE\": \"function\", \"EXEC\": \"function\", \"EXTENSION\": \"function\", \"FIND\": \"function\", \"FLAGS\": \"function\", \"FN\": \"function\", \"FREE\": \"function\", \"FUNC\": \"object\", \"GET\": \"function\", \"GUID\": \"function\", \"HASH\": \"function\", \"HIDDEN\": \"function\", \"IMPORT\": \"function\", \"INC\": \"function\", \"LOCALIZE\": \"function\", \"M\": \"object\", \"MAIN\": \"object\", \"MIDDLEWARE\": \"function\", \"MONTHS\": \"object\", \"NAV\": \"object\", \"NEWUIBIND\": \"function\", \"NODEINDEXOF\": \"function\", \"NODEINSERT\": \"function\", \"NODEMOVE\": \"function\", \"NOOP\": \"function\", \"NOTFOCUSED\": \"function\", \"NOW\": \"object\", \"NUL\": \"function\", \"NULL\": \"function\", \"OFF\": \"function\", \"ON\": \"function\", \"PARSE\": \"function\", \"PIPE\": \"function\", \"PLUGIN\": \"function\", \"PLUGINS\": \"object\", \"PREF\": \"object\", \"PUSH\": \"function\", \"QUERIFY\": \"function\", \"REDIRECT\": \"function\", \"REPO\": \"object\", \"RESET\": \"function\", \"ROUTE\": \"function\", \"SCROLLBAR\": \"function\", \"SCROLLBARWIDTH\": \"function\", \"SEEX\": \"function\", \"SET\": \"function\", \"SETTER\": \"function\", \"STRINGIFY\": \"function\", \"STYLE\": \"function\", \"TAPI\": \"function\", \"TEMP\": \"object\", \"TOGGLE\": \"function\", \"TRANSLATE\": \"function\", \"Ta\": \"object\", \"Tangular\": \"object\", \"Thelpers\": \"object\", \"Total\": \"object\", \"UNAUTHORIZED\": \"function\", \"UNWATCH\": \"function\", \"UPD\": \"function\", \"UPDATE\": \"function\", \"UPLOAD\": \"function\", \"VISIBLE\": \"function\", \"W\": \"object\", \"WAIT\": \"function\", \"WARN\": \"function\", \"WATCH\": \"function\", \"WH\": \"number\", \"WIDTH\": \"function\", \"WW\": \"number\", \"clearTimeout2\": \"function\", \"inDOM\": \"function\", \"isIE\": \"boolean\", \"isMOBILE\": \"boolean\", \"isPRIVATEMODE\": \"boolean\", \"isROBOT\": \"boolean\", \"isSTANDALONE\": \"boolean\", \"isTOUCH\": \"boolean\", \"jComponent\": \"object\", \"jQuery\": \"function\", \"setTimeout2\": \"function\"};\n\nfor (const name of Object.keys(expected))\n\tcheck(added[name] === expected[name], name + ' should be a ' + expected[name] + ', got: ' + added[name]);\n\nconst extra = Object.keys(added).filter(name => !expected[name]);\ncheck(!extra.length, 'globals that this page does not list: ' + extra.join(', '));\n\nresults","test_kind":"browser"}
{"id":"docs/totaljs-strengths","section":"docs","kind":"comparison","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/docs/totaljs-strengths/","markdown_url":"https://forge.totaljs.es/en/docs/totaljs-strengths.md","tags":["framework","strengths","dependencies","python","flowstream","realtime","architecture"],"license":"CC-BY-4.0","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"Total.js strengths and the application problems they solve","description":"A dependency-free core, a complete integrated backend, built-in security, realtime messaging, FlowStream, Python interoperability, a fast learning curve and complete MIT applications to start from.","messages":[{"role":"user","content":"Total.js strengths and the application problems they solve\n\nA dependency-free core, a complete integrated backend, built-in security, realtime messaging, FlowStream, Python interoperability, a fast learning curve and complete MIT applications to start from."},{"role":"assistant","content":"Total.js is much more than HTTP routing. Its central strength is a dependency-free framework core that brings the whole backend together under shared conventions: routes, validation, authentication, views, uploads, realtime, scheduled work and more. For a team building APIs, business applications or live dashboards, that means fewer separately selected packages, less integration code and a project that stays easy to maintain as it grows.\n\n## What Total.js offers\n\n| Strength | What it gives an application | Source or example |\n|---|---|---|\n| Framework core without third-party npm dependencies | The installed `total5@0.0.19` package declares no external runtime dependencies. No tree of modules from different authors to keep compatible, and no module that stops being maintained and leaves your project stranded. This protects the continuity and the scalability of a project in a very direct way. | [Framework source and package manifest](https://github.com/totaljs/framework5), [official platform description](https://www.totaljs.com/) |\n| Integrated backend facilities | Routing, schemas, views, uploads, scheduled jobs, email and localization share one set of conventions. A business application covers many requirements without assembling a separate library for each one. | [Project structure](/en/docs/project-structure/), [uploads](/en/total5/file-upload/), [cron](/en/total5/cron-jobs/), [email](/en/total5/send-email/), [localization](/en/total5/localization/) |\n| Robust input validation and authentication | Required fields, input contracts, protected routes, signed sessions and CSRF protection are native building blocks, so security starts built in instead of assembled from add-ons. | [Input validation](/en/total5/validate-json-body/), [protected routes](/en/total5/protect-routes-with-auth/), [sessions](/en/total5/sessions-with-built-in-auth/) |\n| Realtime communication | WebSocket server and client facilities power bidirectional applications such as live dashboards, notifications and connected services. | [WebSocket server](/en/total5/websocket-chat/), [WebSocket client](/en/total5/websocket-client/) |\n| FlowStream in the backend | Connected components let an application express message-processing workflows and change component configuration while running: ideal for automation and integration pipelines. | [FlowStream example](/en/total5/flowstream/), [component test](/en/total5/flowstream-component-test/) |\n| Native Python process integration | `PYPELINE` is a framework-managed path to start Python scripts and exchange data. Your Node.js application keeps its API and orchestration while Python handles a specialist task. | [PYPELINE entry point](https://github.com/totaljs/framework5/blob/main/global.js), [implementation](https://github.com/totaljs/framework5/blob/main/pypeline.js) |\n| Server-rendered pages in the same framework | Views, layouts, includes and localization let one application serve HTML next to its APIs and realtime endpoints. | [Views and layouts](/en/total5/views-and-layouts/), [includes and assets](/en/total5/view-includes-and-assets/) |\n| Direct routes and reusable actions | A small endpoint answers from its controller; a domain operation uses an action and its validated contract. You choose the layer each piece needs. | [Direct controller example](/en/docs/choosing-totaljs/), [native action example](/en/docs/totaljs-agent-conventions/) |\n| Open-source framework under MIT | You can read the implementation, learn from it and adapt it under the license terms, including for commercial applications. | [Framework license](https://github.com/totaljs/framework5/blob/main/LICENSE) |\n\n## Python interoperability: a concrete capability\n\nIn the `total5@0.0.19` source, `PYPELINE(name, options)` resolves named scripts from `pypelines/` or accepts inline Python. It starts a `python3` process and exchanges messages over a local socket, supporting JSON, text and binary data. This is a built-in bridge between the Node.js application and Python processing, so data science, imaging or machine-learning code can sit next to your API without a separate service.\n\nPython needs to be installed in the target environment, and a script may use Python packages of its own, as with any Python project.\n\n## Fast and light\n\nTotal.js is fast, and it is light: an all-in-one core without a dependency tree means fewer packages to install, load, update and audit, which counts most on small servers and small devices. Forge measures it under controlled conditions and publishes latency, CPU and memory for every workload, with hardware, versions, raw data and downloadable archives, in the [benchmarks](/en/benchmarks/), so you can see the numbers and reproduce them.\n\n## Secure from the first line\n\nThe core has no third-party npm dependencies, so a whole class of supply-chain risk never reaches your installation. On top of that, native input validation, authentication, signed sessions, CSRF protection and scrypt password hashing make every application secure by construction. Add the usual deployment practice of any serious project, HTTPS and a trusted proxy, and you have a solid foundation.\n\n## Easy to learn, with a lot to start from\n\nTotal.js is JavaScript. In our experience the learning curve is very short, so a team that does not know it yet is not at a disadvantage. The Total.js repositories contain a large collection of scripts and examples, and complete applications under the MIT licence: they are ready-made solutions you can use, and clear examples of how the framework is meant to be used. Forge adds executed recipes in 14 languages to go with them.\n\n## An ecosystem that grows with you\n\nOn the frontend, Total.js comes with jComponent and a [catalogue of hundreds of ready-made components](/en/components/), so the interface grows from the same foundations as the backend. The [official platform site](https://www.totaljs.com/) shows the wider Total.js ecosystem, with separate apps and tools for the frontend, for automation and for desktop, including tooling for Apple platforms. They build on the same foundations as the framework, and none of them is required to build a backend with Total.js.\n\n## Evidence and scope\n\nThis guide describes capabilities of the `total5@0.0.19` package and links to executed Forge examples and official sources. The source links to `main` can change, so check the installed version before copying an API. The [execution manifest](/ai-validation.json) lists the introductory guide examples that Forge executes. Read [why choose Total.js](/en/docs/choosing-totaljs/) for the decision overview and [native assistant conventions](/en/docs/totaljs-agent-conventions/) for implementation guidance."}],"knowledge":{"content_sha256":"4ac399045c8774737de1e5d46188c4230b57be3a7a260c4c950ac9c93f76cef6","source_sha256":"cb043019daa344aa0d42262cab1cea4dd607e76140fe07efa3749df3ecca4d79","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["API","CPU","CSRF","HTML","HTTP","HTTPS","JSON","LICENSE","MIT","PYPELINE","Total.js"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"docs/choosing-totaljs","section":"docs","kind":"comparison","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/docs/choosing-totaljs/","markdown_url":"https://forge.totaljs.es/en/docs/choosing-totaljs.md","tags":["framework","nodejs","choose","alternatives","architecture"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"1ad7af0cd3522f1f75bd37f3e39ee94112371b3ca56dab0adfbdf648f962688f","checked_at":"2026-10-20T12:17:19.775Z","framework":"total5@0.0.19","node":"v24.14.1","browser":null,"test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Why choose Total.js for a Node.js application","description":"What Total.js gives a Node.js team: a dependency-free core, everything a business application needs in one framework, built-in authentication and validation, a fast learning curve and complete open-source applications to start from.","messages":[{"role":"user","content":"Why choose Total.js for a Node.js application\n\nWhat Total.js gives a Node.js team: a dependency-free core, everything a business application needs in one framework, built-in authentication and validation, a fast learning curve and complete open-source applications to start from."},{"role":"assistant","content":"Total.js 5 is a complete Node.js application framework, distributed as `total5`. One package gives a team HTTP routes, input validation, server-rendered views, authentication, uploads, WebSocket routes, scheduled jobs, localization, email and more, all designed to work together. You build the application instead of choosing, wiring and maintaining a dozen separate libraries.\n\n## What your project gets\n\n| What your project needs | What Total.js gives you | See it in Forge |\n|---|---|---|\n| An internal business application with forms, protected routes and file uploads | Native input schemas, authentication hooks and upload handling, ready to use | [Validation](/en/total5/validate-json-body/), [authentication](/en/total5/protect-routes-with-auth/), [uploads](/en/total5/file-upload/) |\n| A dashboard with live updates and periodic reports | WebSocket routes, scheduled jobs and email in the same framework | [WebSocket example](/en/total5/websocket-chat/), [cron jobs](/en/total5/cron-jobs/), [email](/en/total5/send-email/) |\n| A small team that wants to ship and maintain one server application | Auto-loaded files and a shared set of conventions that keep every project familiar | [Project structure](/en/docs/project-structure/), [conventions](/en/docs/totaljs-agent-conventions/) |\n| A server-rendered application | Views, layouts and localization inside the same framework | [Views and layouts](/en/total5/views-and-layouts/), [localization](/en/total5/localization/) |\n\nEvery row links to an example that Forge executes. On the frontend, Total.js has jComponent and a [catalogue of hundreds of ready components](/en/components/), so the same ecosystem covers the interface too; using them is optional.\n\n## Why teams choose Total.js\n\nA core without external dependencies. The installed `total5` package declares no third-party runtime dependencies. There is no chain of modules written by different authors to keep compatible, no abandoned package that stops your build, no surprise from a transitive update. Continuity like this has a direct and powerful effect on how well a project scales and how easily it is maintained over the years: what you learn and ship today keeps working, backed by one project.\n\nBuilt-in security APIs. The framework includes authentication, signed sessions, CSRF checks, scrypt password hashing and action input validation. Apply these facilities to your application routes and data. The [Taskboard reference application](/en/docs/taskboard-reference/) combines them in one application.\n\nA fast learning curve. Total.js is JavaScript, with conventions that are quick to pick up, so a team that does not know it yet is not at a disadvantage. The Total.js repositories hold a large collection of scripts, examples and complete applications under the MIT licence that you can read, reuse and learn from, and Forge adds executed recipes for the rest.\n\nFast and light. Total.js is fast, and one package with no dependency tree keeps it light: fewer things to load, update and audit, which counts most on small servers and small devices. Forge measures it under controlled conditions and publishes latency, CPU and memory for every workload, with the raw data, in the [benchmarks](/en/benchmarks/).\n\n## Start simple: a direct HTTP route\n\nThis executable example validates a body with a compact native schema and answers directly from a controller: a few lines and no extra layers. As the application grows, domain actions add a reusable, validated contract that you can call from routes, code and tools.\n\n```js controllers/contacts.js\nvar schema = NEWSCHEMA('ContactsInput', '*name:String');\n\nexports.install = function() {\n\tROUTE('GET /health/', health);\n\tROUTE('POST /contacts/', create);\n};\n\nfunction health($) {\n\t$.json({ ok: true });\n}\n\nfunction create($) {\n\tvar result = schema.transform($.body);\n\tif (result.error) {\n\t\t$.response.status = 400;\n\t\t$.json({ error: 'invalid' });\n\t\treturn;\n\t}\n\t$.response.status = 201;\n\t$.json({ name: result.response.name });\n}\n```\n\n## Sources\n\nForge pairs its claims with things you can run. The [Total.js guidelines](https://github.com/totaljs-es/guidelines) describe the ecosystem's coding style, [Total.js AI Context](https://github.com/totaljs/aicontext) supplies implementation guidance and the [source manifest](/ai-sources.json) records the reviewed revisions. If a guide and the installed framework ever differ, the installed source is the reference."}],"knowledge":{"content_sha256":"808147362df4aec1a9d9eec0e5cf9621bb4e70391d2e138ea6680ddd5d9f8c41","source_sha256":"1ad7af0cd3522f1f75bd37f3e39ee94112371b3ca56dab0adfbdf648f962688f","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["API","CPU","CSRF","GET","HTTP","MIT","NEWSCHEMA","POST","ROUTE","Total.js"],"language":"en"},"files":[{"path":"controllers/contacts.js","language":"js","content":"var schema = NEWSCHEMA('ContactsInput', '*name:String');\n\nexports.install = function() {\n\tROUTE('GET /health/', health);\n\tROUTE('POST /contacts/', create);\n};\n\nfunction health($) {\n\t$.json({ ok: true });\n}\n\nfunction create($) {\n\tvar result = schema.transform($.body);\n\tif (result.error) {\n\t\t$.response.status = 400;\n\t\t$.json({ error: 'invalid' });\n\t\treturn;\n\t}\n\t$.response.status = 201;\n\t$.json({ name: result.response.name });\n}"}],"test":"const health = await get('/health/');\nassert(health.status === 200 && health.json.ok === true, 'health route');\nconst valid = await post('/contacts/', { name: 'Ada' });\nassert(valid.status === 201 && valid.json.name === 'Ada', 'valid contact');\nconst invalid = await post('/contacts/', {});\nassert(invalid.status === 400, 'missing required name');","test_kind":"http"}
{"id":"docs/express-to-totaljs","section":"docs","kind":"comparison","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/docs/express-to-totaljs/","markdown_url":"https://forge.totaljs.es/en/docs/express-to-totaljs.md","tags":["comparison","express","migration"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"c7715c193efee4100098574309f1f093a59747063829e231288c5a0811f3d616","checked_at":"2026-10-20T12:17:22.706Z","framework":"total5@0.0.19","node":"v24.14.1","browser":null,"test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"From Express to Total.js 5","description":"What you write in Express and what you write in Total.js 5 for the same job: routes, parameters, bodies, responses, middleware, static files, cookies, validation, views and more.","messages":[{"role":"user","content":"From Express to Total.js 5\n\nWhat you write in Express and what you write in Total.js 5 for the same job: routes, parameters, bodies, responses, middleware, static files, cookies, validation, views and more."},{"role":"assistant","content":"Total.js is not Express with another name. The biggest differences: a handler receives **one object, `$`**, instead of `(req, res)`; the body is parsed for you by its content type; you do not install packages for cookies, uploads, validation, sessions, CORS, cron or email, because they are part of the framework; and the files of your project are **loaded from folders** instead of being imported (see the project structure page).\n\nThe table pairs common Express patterns with their Total.js 5 equivalents.\n\n| Job | Express | Total.js 5 |\n|---|---|---|\n| Start the server | `app.listen(3000)` | `Total.run({ port: 3000 })` |\n| A route | `app.get('/users', fn)` | `ROUTE('GET /users/', fn)` (write the end slash) |\n| Path parameter | `app.get('/users/:id', fn)` and `req.params.id` | `ROUTE('GET /users/{id}/', fn)` and `$.params.id` |\n| Query string | `req.query.page` | `$.query.page` |\n| JSON body | `app.use(express.json())` and `req.body` | `$.body`, parsed by its content type |\n| Request headers | `req.headers['x-token']` | `$.headers['x-token']` |\n| Send JSON | `res.json(object)` | `$.json(object)` |\n| Send text or HTML | `res.send('text')` | `$.plain('text')` or `$.html('<p>..</p>')` |\n| Status code | `res.status(404).json(...)` | `$.invalid(404)` for an error, or `$.response.status = 201` before answering |\n| Response header | `res.set('x-a', 'b')` | `$.response.headers['x-a'] = 'b'` |\n| Redirect | `res.redirect('/x')` | `$.redirect('/x')` |\n| Cookies | `cookie-parser`, `res.cookie(...)` | `$.cookie('name')` to read, `$.cookie('name', value, '1 day')` to write |\n| Middleware on a route | `app.get('/a', mw, fn)` | `MIDDLEWARE('mw', ($, next) => ...)` and `ROUTE('GET /a/ #mw', fn)` |\n| Static files | `app.use(express.static('public'))` | Put the files in `public/`: no code |\n| Download a file | `res.download(path, name)` | `$.file(path, name)` |\n| Template | `res.render('page', model)` | `$.view('page', model)` |\n| Validate input | `express-validator` or `zod` | `input: '*email:Email, age:Number'` on an action |\n| Error page | an error-handling middleware | `ROUTE('#404', fn)` and `ROUTE('#401', fn)` |\n| Authentication | `passport` or `express-session` | `AUTH(fn)` and `ROUTE('+GET /private/', fn)` |\n| Uploads | `multer` | the flag `@upload` and `$.files` |\n| CORS | `cors` | `CORS('https://example.com')` |\n| Call another API | `axios` or `fetch` | `REQUEST()` and `RESTBuilder` |\n| Scheduled jobs | `node-cron` | `CRON('0 9 * * 1-5', fn)` |\n| Send email | `nodemailer` | `MAIL()` and `HTMLMAIL()` with `mail_smtp` in `config` |\n| WebSocket | `ws` or `socket.io` | `ROUTE('SOCKET /chat/', fn)` |\n\nEach part has a recipe with more detail; this is the whole mapping in one place. The example below is the Total.js side of the rows that need code.\n\n```js definitions/compare.js\nMIDDLEWARE('mw', function($, next) {\n\t$.response.headers['x-a'] = 'b';\n\tnext();\n});\n\nNEWACTION('Users/create', {\n\tname: 'Create a user',\n\tinput: '*email:Email, age:Number',\n\taction: function($, model) {\n\t\t$.callback({ email: model.email, age: model.age });\n\t}\n});\n```\n\n```js controllers/compare.js\nexports.install = function() {\n\tROUTE('GET /users/{id}/', user);\n\tROUTE('POST /users/ --> *Users/create');\n\tROUTE('GET /mw/ #mw', ok);\n\tROUTE('GET /created/', created);\n\tROUTE('GET /moved/', moved);\n\tROUTE('GET /cookie/', cookie);\n\tROUTE('GET /html/', html);\n};\n\nfunction user($) {\n\t$.json({ id: $.params.id, page: $.query.page, token: $.headers['x-token'] });\n}\n\nfunction ok($) {\n\t$.json({ ok: true });\n}\n\nfunction created($) {\n\t$.response.status = 201;\n\t$.json({ created: true });\n}\n\nfunction moved($) {\n\t$.redirect('/users/1/');\n}\n\nfunction cookie($) {\n\t$.cookie('visited', 'yes', '1 day');\n\t$.json({ before: $.cookie('visited') || null });\n}\n\nfunction html($) {\n\t$.html('<p>Hello</p>');\n}\n```"}],"knowledge":{"content_sha256":"047ebe18afed21ea0bb8f52dadd4ca12918d374970fe2f9d2224b556c36c6b10","source_sha256":"c7715c193efee4100098574309f1f093a59747063829e231288c5a0811f3d616","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["API","AUTH","CORS","CRON","GET","HTML","HTMLMAIL","JSON","MAIL","MIDDLEWARE","NEWACTION","POST","REQUEST","RESTB","ROUTE","SOCKET","Total.js","Total.run"],"language":"en"},"files":[{"path":"definitions/compare.js","language":"js","content":"MIDDLEWARE('mw', function($, next) {\n\t$.response.headers['x-a'] = 'b';\n\tnext();\n});\n\nNEWACTION('Users/create', {\n\tname: 'Create a user',\n\tinput: '*email:Email, age:Number',\n\taction: function($, model) {\n\t\t$.callback({ email: model.email, age: model.age });\n\t}\n});"},{"path":"controllers/compare.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /users/{id}/', user);\n\tROUTE('POST /users/ --> *Users/create');\n\tROUTE('GET /mw/ #mw', ok);\n\tROUTE('GET /created/', created);\n\tROUTE('GET /moved/', moved);\n\tROUTE('GET /cookie/', cookie);\n\tROUTE('GET /html/', html);\n};\n\nfunction user($) {\n\t$.json({ id: $.params.id, page: $.query.page, token: $.headers['x-token'] });\n}\n\nfunction ok($) {\n\t$.json({ ok: true });\n}\n\nfunction created($) {\n\t$.response.status = 201;\n\t$.json({ created: true });\n}\n\nfunction moved($) {\n\t$.redirect('/users/1/');\n}\n\nfunction cookie($) {\n\t$.cookie('visited', 'yes', '1 day');\n\t$.json({ before: $.cookie('visited') || null });\n}\n\nfunction html($) {\n\t$.html('<p>Hello</p>');\n}"}],"test":"const user = await get('/users/7/?page=2', { headers: { 'x-token': 'abc' } });\nassert(user.json && user.json.id === '7' && user.json.page === '2' && user.json.token === 'abc', 'params, query and headers, got: ' + user.body);\n\nconst valid = await post('/users/', { email: 'a@b.cz', age: '30' });\nassert(valid.json && valid.json.email === 'a@b.cz' && valid.json.age === 30, 'an action validates and converts the input, got: ' + valid.body);\nassert((await post('/users/', { email: 'nope' })).status === 400, 'an invalid body is rejected with 400');\n\nassert((await get('/mw/')).headers.get('x-a') === 'b', 'a middleware on a route');\nassert((await get('/created/')).status === 201, 'a status code');\n\nconst moved = await get('/moved/');\nassert(moved.status === 302 && /\\/users\\/1\\//.test(moved.headers.get('location')), 'a redirect, got: ' + moved.status);\n\nconst cookie = await get('/cookie/');\nassert(/visited=yes/.test(cookie.headers.get('set-cookie')), 'a cookie is written, got: ' + cookie.headers.get('set-cookie'));\n\nconst html = await get('/html/');\nassert(/text\\/html/.test(html.headers.get('content-type')) && html.body === '<p>Hello</p>', 'HTML is sent with its content type');","test_kind":"http"}
{"id":"total5/validate-json-body","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/validate-json-body/","markdown_url":"https://forge.totaljs.es/en/total5/validate-json-body.md","tags":["routing","actions","validation"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"8c58627c8d1a09872188fb9136ddf2e2b66f29a5ab7bb5bb7e884445ccb620b1","checked_at":"2026-10-20T12:19:00.247Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Validate a JSON body with an action","description":"Define an input schema on an action and let Total.js reject invalid requests before your code runs.","messages":[{"role":"user","content":"Validate a JSON body with an action\n\nDefine an input schema on an action and let Total.js reject invalid requests before your code runs."},{"role":"assistant","content":"In Total.js 5 you declare the input of an action with `input`. A star in front of a field name (`*email`) marks it as required, and the type after the colon (`Email`) is validated by the framework. Your `action` function runs only when the data is valid.\n\nRoutes point to an action with the `-->` operator. The `*` prefix means \"action\" (as opposed to a controller function).\n\n```js definitions/subscribe.js\nNEWACTION('Subscribe/exec', {\n\tname: 'Subscribe to the newsletter',\n\tinput: '*email:Email, name:String',\n\taction: function($, model) {\n\t\t$.success(model.email);\n\t}\n});\n```\n\n```js controllers/subscribe.js\nexports.install = function() {\n\tROUTE('POST /subscribe/ --> *Subscribe/exec');\n};\n```\n\nSend a valid body and the action is executed. Send an invalid e-mail address and the framework answers with a validation error without calling your function."}],"knowledge":{"content_sha256":"f17fb63e9cf7f6ea86a8bf1c188191dce6312bdd0903b83f83d84e693ce5916e","source_sha256":"8c58627c8d1a09872188fb9136ddf2e2b66f29a5ab7bb5bb7e884445ccb620b1","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["NEWACTION","POST","ROUTE","Total.js"],"language":"en"},"files":[{"path":"definitions/subscribe.js","language":"js","content":"NEWACTION('Subscribe/exec', {\n\tname: 'Subscribe to the newsletter',\n\tinput: '*email:Email, name:String',\n\taction: function($, model) {\n\t\t$.success(model.email);\n\t}\n});"},{"path":"controllers/subscribe.js","language":"js","content":"exports.install = function() {\n\tROUTE('POST /subscribe/ --> *Subscribe/exec');\n};"}],"test":"const ok = await post('/subscribe/', { email: 'hello@totaljs.com', name: 'Peter' });\nassert(ok.status === 200, 'valid body returns 200');\n\nconst bad = await post('/subscribe/', { email: 'not-an-email' });\nassert(bad.status === 400, 'invalid e-mail returns 400');","test_kind":"http"}
{"id":"total5/api-routes","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/api-routes/","markdown_url":"https://forge.totaljs.es/en/total5/api-routes.md","tags":["routing","api","actions"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"1c42341e61a81dc95a6565ff5bf53166ec1dc3d69409149fd1c44a620e7b6e15","checked_at":"2026-10-20T12:17:46.498Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Expose actions through one API endpoint","description":"Declare ROUTE('API ...') endpoints so a client can call many actions with POST {schema, data} on a single URL.","messages":[{"role":"user","content":"Expose actions through one API endpoint\n\nDeclare ROUTE('API ...') endpoints so a client can call many actions with POST {schema, data} on a single URL."},{"role":"assistant","content":"An API route publishes actions under one URL. The client always sends `POST` with a JSON body `{ \"schema\": \"name\", \"data\": { ... } }`, and Total.js dispatches it to the action registered for that name. One URL, many operations: it is the style that jComponent's `API()` function speaks.\n\nThe prefix of the name decides what happens to `data`:\n\n- `+name`: the input of the action is required and validated\n- `-name`: the action takes no input\n- `%name`: partial input (for patches)\n\n```js definitions/math.js\nNEWACTION('Math/add', {\n\tname: 'Add two numbers',\n\tinput: '*a:Number, *b:Number',\n\taction: function($, model) {\n\t\t$.callback({ sum: model.a + model.b });\n\t}\n});\n\nNEWACTION('Math/time', {\n\tname: 'Server time',\n\taction: function($) {\n\t\t$.callback({ year: new Date().getFullYear() });\n\t}\n});\n```\n\n```js controllers/api.js\nexports.install = function() {\n\tROUTE('API /api/ +math_add --> Math/add');\n\tROUTE('API /api/ -math_time --> Math/time');\n};\n```"}],"knowledge":{"content_sha256":"27b3adc10009244d13949af7ef18f0cad861057d5c38ebc55c6cf0d8e7a6b30a","source_sha256":"1c42341e61a81dc95a6565ff5bf53166ec1dc3d69409149fd1c44a620e7b6e15","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["API","JSON","NEWACTION","POST","ROUTE","Total.js","URL"],"language":"en"},"files":[{"path":"definitions/math.js","language":"js","content":"NEWACTION('Math/add', {\n\tname: 'Add two numbers',\n\tinput: '*a:Number, *b:Number',\n\taction: function($, model) {\n\t\t$.callback({ sum: model.a + model.b });\n\t}\n});\n\nNEWACTION('Math/time', {\n\tname: 'Server time',\n\taction: function($) {\n\t\t$.callback({ year: new Date().getFullYear() });\n\t}\n});"},{"path":"controllers/api.js","language":"js","content":"exports.install = function() {\n\tROUTE('API /api/ +math_add --> Math/add');\n\tROUTE('API /api/ -math_time --> Math/time');\n};"}],"test":"const add = await post('/api/', { schema: 'math_add', data: { a: 20, b: 22 } });\nassert(add.status === 200 && add.json && add.json.sum === 42, 'a call with input, got: ' + add.status + ' ' + add.body);\n\nconst invalid = await post('/api/', { schema: 'math_add', data: { a: 'x' } });\nassert(invalid.status === 400, 'the input is validated (+), got: ' + invalid.status);\n\nconst time = await post('/api/', { schema: 'math_time' });\nassert(time.status === 200 && time.json && time.json.year >= 2024, 'a call without input (-), got: ' + time.status + ' ' + time.body);\n\nconst unknown = await post('/api/', { schema: 'nothing', data: {} });\nassert(unknown.status !== 200, 'an unknown name is rejected, got: ' + unknown.status);","test_kind":"http"}
{"id":"total5/routes-with-params","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/routes-with-params/","markdown_url":"https://forge.totaljs.es/en/total5/routes-with-params.md","tags":["routing","params"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"654c8e755a30c98272ce76e931695ee3e14f2a1ccb513bee7c9c57437a19fcca","checked_at":"2026-10-20T12:18:46.027Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Read URL parameters in a route","description":"Declare dynamic segments such as {id} in a route and read them from $.params.","messages":[{"role":"user","content":"Read URL parameters in a route\n\nDeclare dynamic segments such as {id} in a route and read them from $.params."},{"role":"assistant","content":"Wrap a segment of the URL in curly braces and Total.js exposes its value in `$.params`. A route can contain as many parameters as you need.\n\n```js controllers/users.js\nexports.install = function() {\n\tROUTE('GET /users/{id}/', show_user);\n\tROUTE('GET /users/{id}/posts/{post}/', show_post);\n};\n\nfunction show_user($) {\n\t$.json({ id: $.params.id });\n}\n\nfunction show_post($) {\n\t$.json({ id: $.params.id, post: $.params.post });\n}\n```\n\n`$.json()` sends the value as JSON. Parameters are always strings; validate or convert them yourself, or declare their types in an action (see the actions recipe)."}],"knowledge":{"content_sha256":"a45ca90c0ed6335f89cecb2eedc19c0abb8191d26bbe2f487b4f2e3ff7f7b76f","source_sha256":"654c8e755a30c98272ce76e931695ee3e14f2a1ccb513bee7c9c57437a19fcca","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["GET","JSON","ROUTE","Total.js","URL"],"language":"en"},"files":[{"path":"controllers/users.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /users/{id}/', show_user);\n\tROUTE('GET /users/{id}/posts/{post}/', show_post);\n};\n\nfunction show_user($) {\n\t$.json({ id: $.params.id });\n}\n\nfunction show_post($) {\n\t$.json({ id: $.params.id, post: $.params.post });\n}"}],"test":"const user = await get('/users/abc/');\nassert(user.status === 200 && user.json.id === 'abc', 'one parameter');\n\nconst article = await get('/users/abc/posts/7/');\nassert(article.json && article.json.id === 'abc' && article.json.post === '7', 'two parameters');","test_kind":"http"}
{"id":"total5/route-wildcards","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/route-wildcards/","markdown_url":"https://forge.totaljs.es/en/total5/route-wildcards.md","tags":["routing"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"afdcbbdd641d8dc9e1138a871d4be7031cebc3244962300e7622432795df663e","checked_at":"2026-10-20T12:18:45.304Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Catch many URLs with a wildcard route","description":"End a route with * to match everything below a path, and keep specific routes for the exceptions.","messages":[{"role":"user","content":"Catch many URLs with a wildcard route\n\nEnd a route with * to match everything below a path, and keep specific routes for the exceptions."},{"role":"assistant","content":"A `*` as the last segment of a route matches any URL below that path. A route without the wildcard is more specific and wins, so you can handle the general case with the wildcard and add fixed routes for the exceptions. The request path is available in `$.url`.\n\n```js controllers/docs.js\nexports.install = function() {\n\tROUTE('GET /docs/*', docs);\n\tROUTE('GET /docs/changelog/', changelog);\n};\n\nfunction docs($) {\n\t$.json({ handler: 'docs', url: $.url });\n}\n\nfunction changelog($) {\n\t$.json({ handler: 'changelog' });\n}\n```"}],"knowledge":{"content_sha256":"22d7e15bb84e561a49e3f7be0e35f2ea48aceacffdf5c94d7f215e9dce31757e","source_sha256":"afdcbbdd641d8dc9e1138a871d4be7031cebc3244962300e7622432795df663e","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["GET","ROUTE","URL"],"language":"en"},"files":[{"path":"controllers/docs.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /docs/*', docs);\n\tROUTE('GET /docs/changelog/', changelog);\n};\n\nfunction docs($) {\n\t$.json({ handler: 'docs', url: $.url });\n}\n\nfunction changelog($) {\n\t$.json({ handler: 'changelog' });\n}"}],"test":"const deep = await get('/docs/guide/routing/');\nassert(deep.json && deep.json.handler === 'docs', 'the wildcard matches deep URLs');\nassert(deep.json && deep.json.url === '/docs/guide/routing/', 'the path is available in $.url, got: ' + (deep.json && deep.json.url));\n\nconst fixed = await get('/docs/changelog/');\nassert(fixed.json && fixed.json.handler === 'changelog', 'the fixed route wins over the wildcard');","test_kind":"http"}
{"id":"total5/http-methods","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/http-methods/","markdown_url":"https://forge.totaljs.es/en/total5/http-methods.md","tags":["routing","http"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"8f9b2437eed244296283e9ab74646f9474062468ec08c19aae9dfd872c58c66a","checked_at":"2026-10-20T12:18:11.746Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Handle each HTTP method","description":"Declare routes for GET, POST, PUT, PATCH and DELETE and read the method from $.method.","messages":[{"role":"user","content":"Handle each HTTP method\n\nDeclare routes for GET, POST, PUT, PATCH and DELETE and read the method from $.method."},{"role":"assistant","content":"The first word of a route is the HTTP method: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`. The same URL can have a different handler for each method, and `$.method` tells a shared handler which one was used.\n\n```js controllers/items.js\nexports.install = function() {\n\tROUTE('GET /items/', list);\n\tROUTE('POST /items/', create);\n\tROUTE('PUT /items/', replace);\n\tROUTE('PATCH /items/', change);\n\tROUTE('DELETE /items/', remove);\n};\n\nfunction list($) {\n\t$.json({ action: 'list', method: $.method });\n}\n\nfunction create($) {\n\t$.json({ action: 'create', method: $.method });\n}\n\nfunction replace($) {\n\t$.json({ action: 'replace', method: $.method });\n}\n\nfunction change($) {\n\t$.json({ action: 'change', method: $.method });\n}\n\nfunction remove($) {\n\t$.json({ action: 'remove', method: $.method });\n}\n```"}],"knowledge":{"content_sha256":"99c1b67162ea56e00956e7570e82728a0375c0733a8a1aa59559d50cc05ce85d","source_sha256":"8f9b2437eed244296283e9ab74646f9474062468ec08c19aae9dfd872c58c66a","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["DELETE","GET","HTTP","PATCH","POST","PUT","ROUTE","URL"],"language":"en"},"files":[{"path":"controllers/items.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /items/', list);\n\tROUTE('POST /items/', create);\n\tROUTE('PUT /items/', replace);\n\tROUTE('PATCH /items/', change);\n\tROUTE('DELETE /items/', remove);\n};\n\nfunction list($) {\n\t$.json({ action: 'list', method: $.method });\n}\n\nfunction create($) {\n\t$.json({ action: 'create', method: $.method });\n}\n\nfunction replace($) {\n\t$.json({ action: 'replace', method: $.method });\n}\n\nfunction change($) {\n\t$.json({ action: 'change', method: $.method });\n}\n\nfunction remove($) {\n\t$.json({ action: 'remove', method: $.method });\n}"}],"test":"for (const [method, action] of [['GET', 'list'], ['POST', 'create'], ['PUT', 'replace'], ['PATCH', 'change'], ['DELETE', 'remove']]) {\n\tconst res = await send(method, '/items/', method === 'GET' || method === 'DELETE' ? undefined : {});\n\tassert(res.json && res.json.action === action && res.json.method === method, method + ' reaches ' + action + ', got: ' + res.body);\n}","test_kind":"http"}
{"id":"total5/route-flags","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/route-flags/","markdown_url":"https://forge.totaljs.es/en/total5/route-flags.md","tags":["routing","flags"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"c0236ae1ed3ba838ef216247d2f2e13e3065e0946c12fb0efbbdeb97a96cc9a9","checked_at":"2026-10-20T12:18:43.857Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Restrict a route with flags","description":"Add @flags such as @json, @xml or @upload so a route only matches the requests it is meant for.","messages":[{"role":"user","content":"Restrict a route with flags\n\nAdd @flags such as @json, @xml or @upload so a route only matches the requests it is meant for."},{"role":"assistant","content":"Flags start with `@` and go after the URL. A route only matches when the request satisfies all of its flags; otherwise Total.js tries the next route for the same URL, and answers 404 when none matches. Useful flags:\n\n- `@json` and `@xml`: the body must be JSON or XML\n- `@xhr`: the request must be an AJAX request (`x-requested-with` header)\n- `@upload`: the body must be `multipart/form-data`\n- `@debug` and `@release`: the route exists only in that mode\n- `@mobile` and `@robot`: the client is a mobile browser or a crawler\n\n```js controllers/data.js\nexports.install = function() {\n\tROUTE('POST /data/ @json', json_only);\n\tROUTE('POST /data/ @xml', xml_only);\n\n\t// Careful: a route with flags has a lower priority than the same URL without flags\n\tROUTE('POST /mixed/ @json', json_only);\n\tROUTE('POST /mixed/', anything);\n};\n\nfunction json_only($) {\n\t$.json({ handler: 'json' });\n}\n\nfunction xml_only($) {\n\t$.json({ handler: 'xml' });\n}\n\nfunction anything($) {\n\t$.json({ handler: 'anything' });\n}\n```\n\nNever mix a flagged route and an unflagged route on the same URL: the unflagged one is checked first and takes every request, so the flagged route is never reached."}],"knowledge":{"content_sha256":"2982c373754e84082ffea5091f58ccf444ef6779ee7247e06bc3d914126b8b7c","source_sha256":"c0236ae1ed3ba838ef216247d2f2e13e3065e0946c12fb0efbbdeb97a96cc9a9","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["AJAX","JSON","POST","ROUTE","Total.js","URL","XML"],"language":"en"},"files":[{"path":"controllers/data.js","language":"js","content":"exports.install = function() {\n\tROUTE('POST /data/ @json', json_only);\n\tROUTE('POST /data/ @xml', xml_only);\n\n\t// Careful: a route with flags has a lower priority than the same URL without flags\n\tROUTE('POST /mixed/ @json', json_only);\n\tROUTE('POST /mixed/', anything);\n};\n\nfunction json_only($) {\n\t$.json({ handler: 'json' });\n}\n\nfunction xml_only($) {\n\t$.json({ handler: 'xml' });\n}\n\nfunction anything($) {\n\t$.json({ handler: 'anything' });\n}"}],"test":"const json = await post('/data/', { a: 1 });\nassert(json.json && json.json.handler === 'json', 'JSON body reaches the @json route, got: ' + json.body);\n\nconst xml = await post('/data/', '<a>1</a>', { headers: { 'content-type': 'application/xml' } });\nassert(xml.json && xml.json.handler === 'xml', 'XML body reaches the @xml route, got: ' + xml.body);\n\nconst form = await post('/data/', 'a=1', { headers: { 'content-type': 'application/x-www-form-urlencoded' } });\nassert(form.status === 404, 'a form body matches no route, got: ' + form.status);\n\nconst mixed = await post('/mixed/', { a: 1 });\nassert(mixed.json && mixed.json.handler === 'anything', 'the unflagged route takes the request, got: ' + mixed.body);","test_kind":"http"}
{"id":"total5/query-string-and-body","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/query-string-and-body/","markdown_url":"https://forge.totaljs.es/en/total5/query-string-and-body.md","tags":["routing","request"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"f3a22eddb04b23d3fc8ea2dc3602349850afc6c303f5d8258e79ea9e2c80d054","checked_at":"2026-10-20T12:18:37.925Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Read the query string and the request body","description":"Use $.query for the URL parameters and $.body for JSON or form data in a route handler.","messages":[{"role":"user","content":"Read the query string and the request body\n\nUse $.query for the URL parameters and $.body for JSON or form data in a route handler."},{"role":"assistant","content":"Inside a route handler, `$.query` is the parsed query string and `$.body` is the parsed body. Total.js parses JSON, URL-encoded forms and multipart forms for you, based on the `content-type` of the request.\n\n```js controllers/echo.js\nexports.install = function() {\n\tROUTE('POST /echo/', echo);\n};\n\nfunction echo($) {\n\t$.json({ query: $.query, body: $.body });\n}\n```"}],"knowledge":{"content_sha256":"f77f4ecaf6dbba8493b0302111bc2ae2524d53427a83406f802506c9a05511f3","source_sha256":"f3a22eddb04b23d3fc8ea2dc3602349850afc6c303f5d8258e79ea9e2c80d054","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["JSON","POST","ROUTE","Total.js","URL"],"language":"en"},"files":[{"path":"controllers/echo.js","language":"js","content":"exports.install = function() {\n\tROUTE('POST /echo/', echo);\n};\n\nfunction echo($) {\n\t$.json({ query: $.query, body: $.body });\n}"}],"test":"const json = await post('/echo/?page=2&sort=name', { name: 'Peter', tags: ['a', 'b'] });\nassert(json.json && json.json.query.page === '2' && json.json.query.sort === 'name', 'query string is parsed, got: ' + json.body);\nassert(json.json && json.json.body.name === 'Peter' && json.json.body.tags.length === 2, 'JSON body is parsed');\n\nconst form = await post('/echo/', 'name=Louis&city=Bratislava', { headers: { 'content-type': 'application/x-www-form-urlencoded' } });\nassert(form.json && form.json.body.name === 'Louis' && form.json.body.city === 'Bratislava', 'form body is parsed, got: ' + form.body);","test_kind":"http"}
{"id":"total5/route-limits","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/route-limits/","markdown_url":"https://forge.totaljs.es/en/total5/route-limits.md","tags":["routing","security","limits"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"ada8116776b76437d1f9430553625526c5f18e1ffe64e24c03e296b84b602cc5","checked_at":"2026-10-20T12:18:44.582Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Limit the payload size and the timeout of a route","description":"Append <1kb or <10s to a route to cap the request size or the time the handler may take.","messages":[{"role":"user","content":"Limit the payload size and the timeout of a route\n\nAppend <1kb or <10s to a route to cap the request size or the time the handler may take."},{"role":"assistant","content":"At the end of a route you can add limits that start with `<`. `<2mb`, `<10kb` and `<500b` set the maximum size of the request body; `<30s`, `<5m` and `<1h` set the timeout of the request. A request over the limit is rejected before your handler runs.\n\n```js controllers/limits.js\nexports.install = function() {\n\tROUTE('POST /small/ <1kb', small);\n\tROUTE('POST /big/ <5mb', big);\n};\n\nfunction small($) {\n\t$.json({ ok: true });\n}\n\nfunction big($) {\n\t$.json({ ok: true });\n}\n```"}],"knowledge":{"content_sha256":"a48c8613efaaaca3952b4f51be9ecf1b7c9cc59fb01c62a256cedfec0d2d549b","source_sha256":"ada8116776b76437d1f9430553625526c5f18e1ffe64e24c03e296b84b602cc5","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["POST","ROUTE"],"language":"en"},"files":[{"path":"controllers/limits.js","language":"js","content":"exports.install = function() {\n\tROUTE('POST /small/ <1kb', small);\n\tROUTE('POST /big/ <5mb', big);\n};\n\nfunction small($) {\n\t$.json({ ok: true });\n}\n\nfunction big($) {\n\t$.json({ ok: true });\n}"}],"test":"const payload = JSON.stringify({ text: 'x'.repeat(4000) });\nconst headers = { 'content-type': 'application/json' };\n\nconst tooLarge = await post('/small/', payload, { headers });\nassert(tooLarge.status !== 200, 'a body over 1 KB is rejected, got: ' + tooLarge.status);\n\nconst fine = await post('/big/', payload, { headers });\nassert(fine.status === 200, 'the same body passes on the 5 MB route, got: ' + fine.status);\n\nconst tiny = await post('/small/', { text: 'ok' });\nassert(tiny.status === 200, 'a small body passes on the 1 KB route, got: ' + tiny.status);","test_kind":"http"}
{"id":"total5/actions-with-params","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/actions-with-params/","markdown_url":"https://forge.totaljs.es/en/total5/actions-with-params.md","tags":["actions","routing","validation"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"82a9e71ccaa7c5bd2f21805c6e15747918f68d310a192a7df78789788a8cac03","checked_at":"2026-10-20T12:17:42.577Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Declare typed route parameters in an action","description":"Use the params option of NEWACTION so Total.js validates the URL parameters before your code runs.","messages":[{"role":"user","content":"Declare typed route parameters in an action\n\nUse the params option of NEWACTION so Total.js validates the URL parameters before your code runs."},{"role":"assistant","content":"An action can describe the parameters it expects with `params`, using the same type names as `input`. UIDs are generated with `UID()` and include a checksum, so an arbitrary string is not a valid UID. When a parameter does not match its type, the framework answers with a validation error and your `action` function is not called.\n\n```js definitions/items.js\nNEWACTION('Items/read', {\n\tname: 'Read an item',\n\tparams: 'id:UID',\n\taction: function($) {\n\t\t$.success($.params.id);\n\t}\n});\n```\n\n```js controllers/items.js\nexports.install = function() {\n\tROUTE('GET /items/{id}/ --> *Items/read');\n};\n```"}],"knowledge":{"content_sha256":"4fe73fb1c01439a14bc08ee36ba7ee5d8cc02fd1d830a7477b8660e742faccf3","source_sha256":"82a9e71ccaa7c5bd2f21805c6e15747918f68d310a192a7df78789788a8cac03","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["GET","NEWACTION","ROUTE","UID"],"language":"en"},"files":[{"path":"definitions/items.js","language":"js","content":"NEWACTION('Items/read', {\n\tname: 'Read an item',\n\tparams: 'id:UID',\n\taction: function($) {\n\t\t$.success($.params.id);\n\t}\n});"},{"path":"controllers/items.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /items/{id}/ --> *Items/read');\n};"}],"test":"// UIDs carry a checksum: generate real ones with UID()\nconst uid = 'JYrgvJ1cC61f';\nconst ok = await get('/items/' + uid + '/');\nassert(ok.status === 200, 'valid UID returns 200 (got ' + ok.status + ')');\n\nconst bad = await get('/items/not-a-uid-!/');\nassert(bad.status === 400, 'invalid UID returns 400 (got ' + bad.status + ')');","test_kind":"http"}
{"id":"total5/responses","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/responses/","markdown_url":"https://forge.totaljs.es/en/total5/responses.md","tags":["routing","response"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"8c2f5baa234328cf59538a428307260bf1206ce9c0f86871ad04f79eaf4e5678","checked_at":"2026-10-20T12:18:42.402Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Choose how to respond","description":"Send JSON, HTML, plain text, XML, redirects, empty responses or binary data from a route handler.","messages":[{"role":"user","content":"Choose how to respond\n\nSend JSON, HTML, plain text, XML, redirects, empty responses or binary data from a route handler."},{"role":"assistant","content":"A handler answers the request by calling one of the response methods of `$`. Each one sets the right `content-type` for you.\n\n| Method | Sends |\n|---|---|\n| `$.json(value)` | JSON |\n| `$.html(string)` | HTML |\n| `$.plain(string)` | plain text |\n| `$.xml(string)` | XML |\n| `$.redirect(url, [permanent])` | a redirect (302, or 301 when `permanent` is true) |\n| `$.empty()` | an empty response (status 204) |\n| `$.binary(buffer, type)` | binary data with the given content type |\n\n```js controllers/responses.js\nexports.install = function() {\n\tROUTE('GET /json/', ($) => $.json({ ok: true }));\n\tROUTE('GET /html/', ($) => $.html('<h1>Hello</h1>'));\n\tROUTE('GET /plain/', ($) => $.plain('Hello'));\n\tROUTE('GET /xml/', ($) => $.xml('<root><item>1</item></root>'));\n\tROUTE('GET /redirect/', ($) => $.redirect('/json/'));\n\tROUTE('GET /moved/', ($) => $.redirect('/json/', true));\n\tROUTE('GET /empty/', ($) => $.empty());\n\tROUTE('GET /binary/', ($) => $.binary(Buffer.from('abc'), 'application/octet-stream'));\n};\n```"}],"knowledge":{"content_sha256":"0298973310cdc200347a02f33052883d73657d0a5e6348a855a3a8a783c2a4d7","source_sha256":"8c2f5baa234328cf59538a428307260bf1206ce9c0f86871ad04f79eaf4e5678","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["GET","HTML","JSON","ROUTE","XML"],"language":"en"},"files":[{"path":"controllers/responses.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /json/', ($) => $.json({ ok: true }));\n\tROUTE('GET /html/', ($) => $.html('<h1>Hello</h1>'));\n\tROUTE('GET /plain/', ($) => $.plain('Hello'));\n\tROUTE('GET /xml/', ($) => $.xml('<root><item>1</item></root>'));\n\tROUTE('GET /redirect/', ($) => $.redirect('/json/'));\n\tROUTE('GET /moved/', ($) => $.redirect('/json/', true));\n\tROUTE('GET /empty/', ($) => $.empty());\n\tROUTE('GET /binary/', ($) => $.binary(Buffer.from('abc'), 'application/octet-stream'));\n};"}],"test":"const json = await get('/json/');\nassert(json.headers.get('content-type').startsWith('application/json') && json.json.ok === true, 'json');\n\nconst html = await get('/html/');\nassert(html.headers.get('content-type').startsWith('text/html') && html.body === '<h1>Hello</h1>', 'html');\n\nconst plain = await get('/plain/');\nassert(plain.headers.get('content-type').startsWith('text/plain') && plain.body === 'Hello', 'plain');\n\nconst xml = await get('/xml/');\nassert(xml.headers.get('content-type').includes('xml') && xml.body.includes('<item>1</item>'), 'xml');\n\nconst redirect = await get('/redirect/');\nassert(redirect.status === 302 && redirect.headers.get('location').endsWith('/json/'), 'redirect is a 302, got: ' + redirect.status);\n\nconst moved = await get('/moved/');\nassert(moved.status === 301, 'permanent redirect is a 301, got: ' + moved.status);\n\nconst empty = await get('/empty/');\nassert(empty.status === 204 && empty.body === '', 'empty response is a 204, got: ' + empty.status);\n\nconst binary = await get('/binary/');\nassert(binary.headers.get('content-type') === 'application/octet-stream' && binary.body === 'abc', 'binary');","test_kind":"http"}
{"id":"total5/headers-and-cookies","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/headers-and-cookies/","markdown_url":"https://forge.totaljs.es/en/total5/headers-and-cookies.md","tags":["routing","headers","cookies"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"1c84367c206f696aa8691c9b1efc48de786ae4820c98658d1d9855c98d6e23d9","checked_at":"2026-10-20T12:18:11.017Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Read headers and work with cookies","description":"Read request headers from $.headers, read and write cookies with $.cookie() and set response headers.","messages":[{"role":"user","content":"Read headers and work with cookies\n\nRead request headers from $.headers, read and write cookies with $.cookie() and set response headers."},{"role":"assistant","content":"`$.headers` holds the request headers with lowercase names. `$.cookie(name)` reads a cookie of the request and `$.cookie(name, value, expiration)` writes one to the response. Response headers are set on `$.response.headers`.\n\n```js controllers/headers.js\nexports.install = function() {\n\tROUTE('GET /headers/', headers);\n\tROUTE('GET /visit/', visit);\n};\n\nfunction headers($) {\n\t$.response.headers['x-powered-by-forge'] = 'yes';\n\t$.json({ agent: $.headers['x-agent'] || null });\n}\n\nfunction visit($) {\n\tvar count = ($.cookie('visits') || '0').parseInt() + 1;\n\t$.cookie('visits', count + '', '1 day');\n\t$.json({ visits: count });\n}\n```"}],"knowledge":{"content_sha256":"a51d21c0651683626fd6156136be4c9f8365400b440e26a59bf982bda65251c4","source_sha256":"1c84367c206f696aa8691c9b1efc48de786ae4820c98658d1d9855c98d6e23d9","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["GET","ROUTE"],"language":"en"},"files":[{"path":"controllers/headers.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /headers/', headers);\n\tROUTE('GET /visit/', visit);\n};\n\nfunction headers($) {\n\t$.response.headers['x-powered-by-forge'] = 'yes';\n\t$.json({ agent: $.headers['x-agent'] || null });\n}\n\nfunction visit($) {\n\tvar count = ($.cookie('visits') || '0').parseInt() + 1;\n\t$.cookie('visits', count + '', '1 day');\n\t$.json({ visits: count });\n}"}],"test":"const res = await get('/headers/', { headers: { 'x-agent': 'tester' } });\nassert(res.json && res.json.agent === 'tester', 'a request header is read from $.headers, got: ' + res.body);\nassert(res.headers.get('x-powered-by-forge') === 'yes', 'a response header is set');\n\nconst first = await get('/visit/');\nassert(first.json && first.json.visits === 1, 'first visit');\nassert((first.headers.get('set-cookie') || '').startsWith('visits=1'), 'the cookie is written, got: ' + first.headers.get('set-cookie'));\n\nconst second = await get('/visit/', { headers: { cookie: 'visits=1' } });\nassert(second.json && second.json.visits === 2, 'the cookie is read and updated, got: ' + second.body);","test_kind":"http"}
{"id":"total5/file-upload","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/file-upload/","markdown_url":"https://forge.totaljs.es/en/total5/file-upload.md","tags":["routing","upload","files"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"c78de3a9c6ec9362743e01e914f603baab67386d2f20a7b273d0771f8ff17e5c","checked_at":"2026-10-20T12:18:06.170Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Receive file uploads","description":"Accept multipart/form-data with the @upload flag and read the uploaded files from $.files.","messages":[{"role":"user","content":"Receive file uploads\n\nAccept multipart/form-data with the @upload flag and read the uploaded files from $.files."},{"role":"assistant","content":"Mark the route with `@upload` so it only accepts `multipart/form-data`, and set a size limit that fits the files you expect (here `<5mb`). The uploaded files are in `$.files`; the other fields of the form are in `$.body`. Each file has `name`, `filename`, `size` and `type`, and methods such as `read()`, `copy()` and `move()`.\n\n```js controllers/upload.js\nexports.install = function() {\n\tROUTE('POST /upload/ @upload <5mb', upload);\n};\n\nfunction upload($) {\n\n\tvar file = $.files[0];\n\n\tif (!file) {\n\t\t$.invalid('No file');\n\t\treturn;\n\t}\n\n\tfile.read(function(err, buffer) {\n\t\t$.json({\n\t\t\tfilename: file.filename,\n\t\t\tsize: file.size,\n\t\t\ttype: file.type,\n\t\t\ttext: buffer.toString('utf8'),\n\t\t\ttitle: $.body.title\n\t\t});\n\t});\n}\n```"}],"knowledge":{"content_sha256":"cc8e1202c301ef7d57c6d2a4ded68c22fafe339ba7779514ef55c287d3e83d74","source_sha256":"c78de3a9c6ec9362743e01e914f603baab67386d2f20a7b273d0771f8ff17e5c","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["POST","ROUTE"],"language":"en"},"files":[{"path":"controllers/upload.js","language":"js","content":"exports.install = function() {\n\tROUTE('POST /upload/ @upload <5mb', upload);\n};\n\nfunction upload($) {\n\n\tvar file = $.files[0];\n\n\tif (!file) {\n\t\t$.invalid('No file');\n\t\treturn;\n\t}\n\n\tfile.read(function(err, buffer) {\n\t\t$.json({\n\t\t\tfilename: file.filename,\n\t\t\tsize: file.size,\n\t\t\ttype: file.type,\n\t\t\ttext: buffer.toString('utf8'),\n\t\t\ttitle: $.body.title\n\t\t});\n\t});\n}"}],"test":"const form = new FormData();\nform.append('title', 'My file');\nform.append('file', new Blob(['hello upload'], { type: 'text/plain' }), 'hello.txt');\n\nconst res = await post('/upload/', form);\nassert(res.status === 200, 'upload is accepted (got ' + res.status + ': ' + res.body + ')');\nassert(res.json && res.json.filename === 'hello.txt', 'filename, got: ' + res.body);\nassert(res.json && res.json.size === 12 && res.json.text === 'hello upload', 'size and content');\nassert(res.json && res.json.type === 'text/plain', 'content type');\nassert(res.json && res.json.title === 'My file', 'form fields are in $.body');\n\nconst notMultipart = await post('/upload/', { title: 'no file' });\nassert(notMultipart.status === 404, 'a JSON request does not match the @upload route, got: ' + notMultipart.status);","test_kind":"http"}
{"id":"total5/cors","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/cors/","markdown_url":"https://forge.totaljs.es/en/total5/cors.md","tags":["security","cors","routing"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"c830bff1a36ca654b8c8dde9c19338cc12a0a8c42b7c95ce0b2ba8f44023744a","checked_at":"2026-10-20T12:17:57.167Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Allow cross-origin requests with CORS","description":"Call CORS() to allow one origin, several origins or every origin to use your routes from a browser.","messages":[{"role":"user","content":"Allow cross-origin requests with CORS\n\nCall CORS() to allow one origin, several origins or every origin to use your routes from a browser."},{"role":"assistant","content":"Browsers block requests from other origins unless the server allows them. `CORS(origin)` sets the allowed origins for the whole app: pass an address, a comma-separated list, or nothing to allow every origin (`*`). Prefix an address with `+` to add it to the existing list.\n\n```js definitions/cors.js\nCORS('https://app.example.com');\n```\n\n```js controllers/api.js\nexports.install = function() {\n\tROUTE('GET /api/time/', ($) => $.json({ ok: true }));\n};\n```"}],"knowledge":{"content_sha256":"19d1e5e05b9e75f6423fa945f690b5f170fcdc1065d0ecbc59ba4271dd30724d","source_sha256":"c830bff1a36ca654b8c8dde9c19338cc12a0a8c42b7c95ce0b2ba8f44023744a","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["CORS","GET","ROUTE"],"language":"en"},"files":[{"path":"definitions/cors.js","language":"js","content":"CORS('https://app.example.com');"},{"path":"controllers/api.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /api/time/', ($) => $.json({ ok: true }));\n};"}],"test":"const allowed = await get('/api/time/', { headers: { origin: 'https://app.example.com' } });\nassert(allowed.status === 200, 'the allowed origin gets the response, got: ' + allowed.status);\nassert(allowed.headers.get('access-control-allow-origin') === 'https://app.example.com', 'the origin is echoed, got: ' + allowed.headers.get('access-control-allow-origin'));\n\nconst blocked = await get('/api/time/', { headers: { origin: 'https://evil.example.org' } });\nassert(blocked.status === 400, 'another origin is rejected, got: ' + blocked.status);","test_kind":"http"}
{"id":"total5/view-without-handler","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/view-without-handler/","markdown_url":"https://forge.totaljs.es/en/total5/view-without-handler.md","tags":["routing","views"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"b4f05c4ae7128488f4cdb54765701bfd3a13e986de5f5f77201b21ddf5052b95","checked_at":"2026-10-20T12:19:04.586Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Render a view without writing a handler","description":"A route with no handler renders the view named after its URL, or the view you name.","messages":[{"role":"user","content":"Render a view without writing a handler\n\nA route with no handler renders the view named after its URL, or the view you name."},{"role":"assistant","content":"When a route has no handler, Total.js renders a view for it: `views/about.html` for `/about/` and `views/index.html` for `/`. You can also name the view yourself by passing a string instead of a function.\n\n```html views/layout.html\n<!DOCTYPE html><html><body>@{body}</body></html>\n```\n\n```html views/index.html\n<h1>Home</h1>\n```\n\n```html views/about.html\n<h1>About us</h1>\n```\n\n```html views/contact-page.html\n<h1>Contact</h1>\n```\n\n```js controllers/default.js\nexports.install = function() {\n\tROUTE('GET /');\n\tROUTE('GET /about/');\n\tROUTE('GET /contact/', 'contact-page');\n};\n```"}],"knowledge":{"content_sha256":"32fc1257277ade3f3462f63f6feed151aaea8fd98344211a4400a0b6e7b514a9","source_sha256":"b4f05c4ae7128488f4cdb54765701bfd3a13e986de5f5f77201b21ddf5052b95","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["DOCTYPE","GET","ROUTE","Total.js"],"language":"en"},"files":[{"path":"views/layout.html","language":"html","content":"<!DOCTYPE html><html><body>@{body}</body></html>"},{"path":"views/index.html","language":"html","content":"<h1>Home</h1>"},{"path":"views/about.html","language":"html","content":"<h1>About us</h1>"},{"path":"views/contact-page.html","language":"html","content":"<h1>Contact</h1>"},{"path":"controllers/default.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /');\n\tROUTE('GET /about/');\n\tROUTE('GET /contact/', 'contact-page');\n};"}],"test":"const home = await get('/');\nassert(home.status === 200 && home.body.includes('<h1>Home</h1>'), 'GET / renders views/index.html, got: ' + home.body);\n\nconst about = await get('/about/');\nassert(about.body.includes('<h1>About us</h1>') && about.body.includes('<body>'), 'GET /about/ renders views/about.html inside the layout');\n\nconst contact = await get('/contact/');\nassert(contact.body.includes('<h1>Contact</h1>'), 'a string names the view, got: ' + contact.body);","test_kind":"http"}
{"id":"total5/input-types","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/input-types/","markdown_url":"https://forge.totaljs.es/en/total5/input-types.md","tags":["actions","validation","types"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"8972cfd719caedd5cd1ce9ed3d54e73b7429efbdcce5886e90e9838ffa6d6601","checked_at":"2026-10-20T12:18:15.509Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Pick the right input type for an action","description":"Choose types for input, params, query and output definitions, including conversions and rejected values.","messages":[{"role":"user","content":"Pick the right input type for an action\n\nChoose types for input, params, query and output definitions, including conversions and rejected values."},{"role":"assistant","content":"The `input` of an action is a list of `name:Type` fields separated by commas. A `*` before the name makes the field required. Besides validating, many types **convert** the value, so the action receives clean data:\n\n| Type | Result |\n|---|---|\n| `String`, `Number`, `Boolean`, `Date`, `Object`, `Array` | the basic types (strings are trimmed, numbers and dates are parsed) |\n| `Email`, `Phone`, `URL`, `UID`, `GUID`, `Base64`, `Color`, `Zip` | validated formats |\n| `Lowercase`, `Uppercase`, `Capitalize`, `Capitalize2`, `Name` | text converted to that style: `Capitalize` capitalizes every word, `Capitalize2` only the first letter of the text and lowercases the rest |\n\n```js definitions/types.js\nNEWACTION('Types/exec', {\n\tname: 'Typed input',\n\tinput: '*email:Email, age:Number, active:Boolean, nick:Lowercase, code:Uppercase, title:Capitalize, sentence:Capitalize2, site:URL',\n\taction: function($, model) {\n\t\t$.callback(model);\n\t}\n});\n```\n\n```js controllers/types.js\nexports.install = function() {\n\tROUTE('POST /types/ --> *Types/exec');\n};\n```"}],"knowledge":{"content_sha256":"94e8b431828404458a5dd183a56f8114ec936a5e5936d5487639090ed99358f4","source_sha256":"8972cfd719caedd5cd1ce9ed3d54e73b7429efbdcce5886e90e9838ffa6d6601","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["GUID","NEWACTION","POST","ROUTE","UID","URL"],"language":"en"},"files":[{"path":"definitions/types.js","language":"js","content":"NEWACTION('Types/exec', {\n\tname: 'Typed input',\n\tinput: '*email:Email, age:Number, active:Boolean, nick:Lowercase, code:Uppercase, title:Capitalize, sentence:Capitalize2, site:URL',\n\taction: function($, model) {\n\t\t$.callback(model);\n\t}\n});"},{"path":"controllers/types.js","language":"js","content":"exports.install = function() {\n\tROUTE('POST /types/ --> *Types/exec');\n};"}],"test":"const ok = await post('/types/', { email: 'peter@totaljs.com', age: '42', active: 'true', nick: 'PeTeR', code: 'abc', title: 'hello world', sentence: 'hello wORLD again', site: 'https://totaljs.com' });\nassert(ok.status === 200, 'valid data is accepted, got: ' + ok.status + ' ' + ok.body);\nassert(ok.json && ok.json.age === 42, 'Number converts \"42\" to 42, got: ' + (ok.json && typeof ok.json.age));\nassert(ok.json && ok.json.active === true, 'Boolean converts \"true\" to true');\nassert(ok.json && ok.json.nick === 'peter', 'Lowercase, got: ' + (ok.json && ok.json.nick));\nassert(ok.json && ok.json.code === 'ABC', 'Uppercase');\nassert(ok.json && ok.json.title === 'Hello World', 'Capitalize capitalizes every word, got: ' + (ok.json && ok.json.title));\nassert(ok.json && ok.json.sentence === 'Hello world again', 'Capitalize2 capitalizes the first letter and lowercases the rest, got: ' + (ok.json && ok.json.sentence));\n\nconst missing = await post('/types/', { age: 1 });\nassert(missing.status === 400, 'the required e-mail is missing, got: ' + missing.status);\n\nconst badUrl = await post('/types/', { email: 'peter@totaljs.com', site: 'nope' });\nassert(badUrl.status === 400, 'an invalid URL is rejected, got: ' + badUrl.status);","test_kind":"http"}
{"id":"total5/action-query","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/action-query/","markdown_url":"https://forge.totaljs.es/en/total5/action-query.md","tags":["actions","validation","query"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"5cba740749819daae70a8ff90dbd22fe4d6210ec1ec6ceea70ba608eb1b006c8","checked_at":"2026-10-20T12:17:38.425Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Validate the query string in an action","description":"Declare query in NEWACTION to validate and convert the URL parameters, and read the clean values from $.query.","messages":[{"role":"user","content":"Validate the query string in an action\n\nDeclare query in NEWACTION to validate and convert the URL parameters, and read the clean values from $.query."},{"role":"assistant","content":"Like `input` for the body and `params` for the path, `query` describes the query string. The framework validates it and converts the values (the page number below arrives as a real number); an invalid query answers with a validation error before your code runs.\n\n```js definitions/search.js\nNEWACTION('Search/exec', {\n\tname: 'Search',\n\tquery: 'q:String, page:Number, *sort:Lowercase',\n\taction: function($) {\n\t\t$.callback({ q: $.query.q || '', page: $.query.page || 1, sort: $.query.sort });\n\t}\n});\n```\n\n```js controllers/search.js\nexports.install = function() {\n\tROUTE('GET /search/ --> *Search/exec');\n};\n```"}],"knowledge":{"content_sha256":"77ad7257e53db2b3d6562b8361c9bd5d8bae8913edd81e2448d322a1898445d9","source_sha256":"5cba740749819daae70a8ff90dbd22fe4d6210ec1ec6ceea70ba608eb1b006c8","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["GET","NEWACTION","ROUTE"],"language":"en"},"files":[{"path":"definitions/search.js","language":"js","content":"NEWACTION('Search/exec', {\n\tname: 'Search',\n\tquery: 'q:String, page:Number, *sort:Lowercase',\n\taction: function($) {\n\t\t$.callback({ q: $.query.q || '', page: $.query.page || 1, sort: $.query.sort });\n\t}\n});"},{"path":"controllers/search.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /search/ --> *Search/exec');\n};"}],"test":"const ok = await get('/search/?q=total&page=3&sort=NAME');\nassert(ok.status === 200, 'valid query, got: ' + ok.status + ' ' + ok.body);\nassert(ok.json && ok.json.page === 3, 'page is converted to a number, got: ' + ok.body);\nassert(ok.json && ok.json.sort === 'name', 'sort is lowercased, got: ' + ok.body);\n\nconst missing = await get('/search/?q=total');\nassert(missing.status === 400, 'the required sort is missing, got: ' + missing.status);","test_kind":"http"}
{"id":"total5/action-output","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/action-output/","markdown_url":"https://forge.totaljs.es/en/total5/action-output.md","tags":["actions","security"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"824a78d7cc125dbb312beacade20fbbc78dd03bc57df187d12e8bc11cb6323b9","checked_at":"2026-10-20T12:17:37.703Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Filter the response of an action with output","description":"Declare output so only the listed fields leave the action, for example to never leak a password hash.","messages":[{"role":"user","content":"Filter the response of an action with output\n\nDeclare output so only the listed fields leave the action, for example to never leak a password hash."},{"role":"assistant","content":"`output` has the same syntax as `input`, but it is applied to the **response**: only the declared fields are sent, converted to their types. Declare it on every action that reads from a database and you cannot leak a field by accident.\n\n```js definitions/users.js\nNEWACTION('Users/read', {\n\tname: 'Read a user',\n\toutput: 'id:String, name:String, age:Number',\n\taction: function($) {\n\t\t$.callback({ id: '1', name: 'Peter', age: '42', password: 'secret-hash', token: 'abc' });\n\t}\n});\n```\n\n```js controllers/users.js\nexports.install = function() {\n\tROUTE('GET /user/ --> *Users/read');\n};\n```"}],"knowledge":{"content_sha256":"3cc2aaaa9e55db35473103cb96c6ca1e8480d56cdbfdbf6345ba475fae365801","source_sha256":"824a78d7cc125dbb312beacade20fbbc78dd03bc57df187d12e8bc11cb6323b9","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["GET","NEWACTION","ROUTE"],"language":"en"},"files":[{"path":"definitions/users.js","language":"js","content":"NEWACTION('Users/read', {\n\tname: 'Read a user',\n\toutput: 'id:String, name:String, age:Number',\n\taction: function($) {\n\t\t$.callback({ id: '1', name: 'Peter', age: '42', password: 'secret-hash', token: 'abc' });\n\t}\n});"},{"path":"controllers/users.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /user/ --> *Users/read');\n};"}],"test":"const res = await get('/user/');\nassert(res.status === 200, 'the action answers, got: ' + res.status);\nassert(res.json && res.json.name === 'Peter', 'declared fields are sent, got: ' + res.body);\nassert(res.json && res.json.age === 42, 'values are converted to the declared type');\nassert(res.json && !('password' in res.json) && !('token' in res.json), 'undeclared fields are removed, got: ' + res.body);","test_kind":"http"}
{"id":"total5/action-user-and-permissions","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/action-user-and-permissions/","markdown_url":"https://forge.totaljs.es/en/total5/action-user-and-permissions.md","tags":["actions","auth","security"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"27fb58acb592797b9035d6acc4e2cef97bf820e7024a2a6e6c23b39201fd4ba7","checked_at":"2026-10-20T12:17:40.770Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Require a user or a permission in an action","description":"Use the user and permissions options of NEWACTION so authorization lives next to the action, not in every route.","messages":[{"role":"user","content":"Require a user or a permission in an action\n\nUse the user and permissions options of NEWACTION so authorization lives next to the action, not in every route."},{"role":"assistant","content":"An action can protect itself:\n\n- `user: true` requires an authenticated user (otherwise 401); `user: false` allows only anonymous requests\n- `permissions: 'a,b'` requires the user to have at least one of the listed permissions. Total.js reads `user.permissions` (or `user.roles`), which can be an array of strings or an object\n- users with `sa: true` or `su: true` (super admins) pass every check\n\nThe user comes from `AUTH()`, as in the authentication recipe.\n\n```js definitions/auth.js\nAUTH(function($) {\n\n\tvar users = {\n\t\t'token-reports': { id: '1', name: 'Anna', permissions: ['reports'] },\n\t\t'token-sales': { id: '2', name: 'Boris', permissions: ['sales'] },\n\t\t'token-admin': { id: '3', name: 'Root', sa: true }\n\t};\n\n\tvar user = users[$.cookie('auth')];\n\tuser ? $.success(user) : $.invalid();\n});\n```\n\n```js definitions/reports.js\nNEWACTION('Reports/read', {\n\tname: 'Read reports',\n\tuser: true,\n\tpermissions: 'reports,audit',\n\taction: function($) {\n\t\t$.success($.user.name);\n\t}\n});\n```\n\n```js controllers/reports.js\nexports.install = function() {\n\tROUTE('GET /reports/ --> *Reports/read');\n};\n```"}],"knowledge":{"content_sha256":"007da97a72808f540a04b3dbd16a2c67d6178f57d549cef59c92333c78080b57","source_sha256":"27fb58acb592797b9035d6acc4e2cef97bf820e7024a2a6e6c23b39201fd4ba7","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["AUTH","GET","NEWACTION","ROUTE","Total.js"],"language":"en"},"files":[{"path":"definitions/auth.js","language":"js","content":"AUTH(function($) {\n\n\tvar users = {\n\t\t'token-reports': { id: '1', name: 'Anna', permissions: ['reports'] },\n\t\t'token-sales': { id: '2', name: 'Boris', permissions: ['sales'] },\n\t\t'token-admin': { id: '3', name: 'Root', sa: true }\n\t};\n\n\tvar user = users[$.cookie('auth')];\n\tuser ? $.success(user) : $.invalid();\n});"},{"path":"definitions/reports.js","language":"js","content":"NEWACTION('Reports/read', {\n\tname: 'Read reports',\n\tuser: true,\n\tpermissions: 'reports,audit',\n\taction: function($) {\n\t\t$.success($.user.name);\n\t}\n});"},{"path":"controllers/reports.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /reports/ --> *Reports/read');\n};"}],"test":"const as = token => ({ headers: { cookie: 'auth=' + token } });\n\nconst anonymous = await get('/reports/');\nassert(anonymous.status === 401, 'no user: 401, got: ' + anonymous.status);\n\nconst allowed = await get('/reports/', as('token-reports'));\nassert(allowed.status === 200, 'user with the permission: 200, got: ' + allowed.status);\n\nconst denied = await get('/reports/', as('token-sales'));\nassert(denied.status === 401, 'user without the permission: 401, got: ' + denied.status);\n\nconst admin = await get('/reports/', as('token-admin'));\nassert(admin.status === 200, 'a super admin passes, got: ' + admin.status);","test_kind":"http"}
{"id":"total5/protect-routes-with-auth","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/protect-routes-with-auth/","markdown_url":"https://forge.totaljs.es/en/total5/protect-routes-with-auth.md","tags":["auth","routing","security"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"6bdadb84d82760100a6e4b8ab7a42bb2d1bd33f8a16a593f7c854f906e506b79","checked_at":"2026-10-20T12:18:32.648Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Protect routes with AUTH","description":"Authenticate each request with AUTH() and restrict routes to signed-in or anonymous users with the + and - prefixes.","messages":[{"role":"user","content":"Protect routes with AUTH\n\nAuthenticate each request with AUTH() and restrict routes to signed-in or anonymous users with the + and - prefixes."},{"role":"assistant","content":"`AUTH()` runs for every request. Call `$.success(user)` when the request is authenticated and `$.invalid()` when it is not. The user object is then available as `$.user` in every handler.\n\nPrefix a route with `+` to allow only authenticated users and with `-` to allow only anonymous users. A route without a prefix is open to everybody.\n\n```js definitions/auth.js\nAUTH(function($) {\n\n\t// This demo reads a cookie. In a real app verify a signed session instead.\n\tvar token = $.cookie('auth');\n\n\tif (token === 'secret-token')\n\t\t$.success({ id: '1', name: 'Peter' });\n\telse\n\t\t$.invalid();\n});\n```\n\n```js controllers/private.js\nexports.install = function() {\n\tROUTE('+GET /private/', private_page);\n\tROUTE('-GET /login/', login_page);\n\tROUTE('GET /whoami/', whoami);\n};\n\nfunction private_page($) {\n\t$.json({ user: $.user.name });\n}\n\nfunction login_page($) {\n\t$.json({ message: 'Please sign in' });\n}\n\nfunction whoami($) {\n\t$.json({ user: $.user ? $.user.name : null });\n}\n```"}],"knowledge":{"content_sha256":"e721c95d67f27b8a74853b04e189a43be4690b3d65cd55b70462439479cb17ca","source_sha256":"6bdadb84d82760100a6e4b8ab7a42bb2d1bd33f8a16a593f7c854f906e506b79","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["AUTH","GET","ROUTE"],"language":"en"},"files":[{"path":"definitions/auth.js","language":"js","content":"AUTH(function($) {\n\n\t// This demo reads a cookie. In a real app verify a signed session instead.\n\tvar token = $.cookie('auth');\n\n\tif (token === 'secret-token')\n\t\t$.success({ id: '1', name: 'Peter' });\n\telse\n\t\t$.invalid();\n});"},{"path":"controllers/private.js","language":"js","content":"exports.install = function() {\n\tROUTE('+GET /private/', private_page);\n\tROUTE('-GET /login/', login_page);\n\tROUTE('GET /whoami/', whoami);\n};\n\nfunction private_page($) {\n\t$.json({ user: $.user.name });\n}\n\nfunction login_page($) {\n\t$.json({ message: 'Please sign in' });\n}\n\nfunction whoami($) {\n\t$.json({ user: $.user ? $.user.name : null });\n}"}],"test":"const auth = { headers: { cookie: 'auth=secret-token' } };\n\nconst anonymous = await get('/private/');\nassert(anonymous.status === 401, 'anonymous user gets 401 on a + route (got ' + anonymous.status + ')');\n\nconst signed = await get('/private/', auth);\nassert(signed.status === 200 && signed.json.user === 'Peter', 'signed-in user reaches the + route');\n\nconst login = await get('/login/');\nassert(login.status === 200, 'anonymous user reaches the - route');\n\nconst open = await get('/whoami/');\nassert(open.status === 200 && open.json.user === null, 'open route works without a user');","test_kind":"http"}
{"id":"total5/action-route-option","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/action-route-option/","markdown_url":"https://forge.totaljs.es/en/total5/action-route-option.md","tags":["actions","routing"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"fa5b396e7364a43ae74980659990373311eae563f3b7825b09939316ceb80721","checked_at":"2026-10-20T12:17:39.148Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Declare the route inside the action","description":"Put the route in the route option of NEWACTION and keep the URL, the validation and the code in one place.","messages":[{"role":"user","content":"Declare the route inside the action\n\nPut the route in the route option of NEWACTION and keep the URL, the validation and the code in one place."},{"role":"assistant","content":"Instead of a separate `ROUTE()` line, an action can carry its own route in the `route` option. The action is then the single source of truth for its URL, input and behavior.\n\n```js definitions/ping.js\nNEWACTION('Ping/exec', {\n\tname: 'Ping',\n\troute: 'GET /ping/',\n\taction: function($) {\n\t\t$.success('pong');\n\t}\n});\n```"}],"knowledge":{"content_sha256":"224c93950bd1074c85e1c7f477d4a091e4a185e6f2eb3b71b467071cd1fc78a7","source_sha256":"fa5b396e7364a43ae74980659990373311eae563f3b7825b09939316ceb80721","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["GET","NEWACTION","ROUTE","URL"],"language":"en"},"files":[{"path":"definitions/ping.js","language":"js","content":"NEWACTION('Ping/exec', {\n\tname: 'Ping',\n\troute: 'GET /ping/',\n\taction: function($) {\n\t\t$.success('pong');\n\t}\n});"}],"test":"const res = await get('/ping/');\nassert(res.status === 200, 'the route declared in the action works, got: ' + res.status + ' ' + res.body);\nassert(res.json && res.json.success === true && res.json.value === 'pong', '$.success() wraps the value, got: ' + res.body);","test_kind":"http"}
{"id":"total5/sessions-with-built-in-auth","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/sessions-with-built-in-auth/","markdown_url":"https://forge.totaljs.es/en/total5/sessions-with-built-in-auth.md","tags":["auth","sessions","security"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"f04382399fd7e25a7ba2aaf8f081e63bb9f846f865c9a60e23364d42f44c9c98","checked_at":"2026-10-20T12:18:48.950Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Sign users in with the built-in session auth","description":"Pass an options object to AUTH() to get signed session cookies, a session store and logout without writing the plumbing.","messages":[{"role":"user","content":"Sign users in with the built-in session auth\n\nPass an options object to AUTH() to get signed session cookies, a session store and logout without writing the plumbing."},{"role":"assistant","content":"Instead of a function, `AUTH(options)` accepts an options object and installs a complete cookie-session authentication:\n\n- `secret` signs the cookie, `cookie` is its name and `expire` how long a session stays in memory without activity\n- `onread(meta, next, $)` loads the user when a valid cookie arrives from a session that is not in memory (for example after a restart); call `next(null, user)`, or `next('not found')` to refuse. **Check that the session still exists in your own store here**, otherwise a copied cookie keeps working after the user signed out\n- `onlogout(session)` and `onremove(sessionid)` run when a session is removed: delete it from your store\n- `options.authcookie($, sessionid, userid, expiration)` signs the user in: it writes the signed cookie to the response\n- `options.logout($)` removes the session and the cookie\n\nKeep the options object in a variable: it is the handle you use to sign users in and out. After that, `$.user` is set and `+` and `-` routes work as in the AUTH recipe.\n\n```js definitions/auth.js\nvar users = {\n\t'1': { id: '1', name: 'Anna', password: 'secret' }\n};\n\n// sessionid -> userid. In a real app this is a database table.\nMAIN.sessions = {};\n\nMAIN.auth = {\n\tsecret: 'change-this-secret',\n\tcookie: 'forge_session',\n\texpire: '1 day',\n\tonread: function(meta, next) {\n\t\tvar user = users[MAIN.sessions[meta.sessionid]];\n\t\tuser ? next(null, { id: user.id, name: user.name }) : next('session not found');\n\t},\n\tonlogout: function(session) {\n\t\tdelete MAIN.sessions[session.sessionid];\n\t},\n\tonremove: function(sessionid) {\n\t\tdelete MAIN.sessions[sessionid];\n\t}\n};\n\nAUTH(MAIN.auth);\n```\n\n```js controllers/account.js\nexports.install = function() {\n\tROUTE('POST /login/', login);\n\tROUTE('+POST /logout/', logout);\n\tROUTE('+GET /me/', me);\n};\n\nfunction login($) {\n\n\tvar user = $.body.id && $.body.id === '1' && $.body.password === 'secret' ? { id: '1', name: 'Anna' } : null;\n\n\tif (!user) {\n\t\t$.invalid(401);\n\t\treturn;\n\t}\n\n\tvar sessionid = UID();\n\tMAIN.sessions[sessionid] = user.id;\n\tMAIN.auth.authcookie($, sessionid, user.id, '1 day');\n\t$.json({ name: user.name });\n}\n\nfunction logout($) {\n\tMAIN.auth.logout($);\n\t$.json({ ok: true });\n}\n\nfunction me($) {\n\t$.json({ name: $.user.name });\n}\n```"}],"knowledge":{"content_sha256":"a810e4204b6a2a82ae77c2e03be8e108cd8ed1c1af85c85c4dc4bc6812c8c6ab","source_sha256":"f04382399fd7e25a7ba2aaf8f081e63bb9f846f865c9a60e23364d42f44c9c98","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["AUTH","GET","MAIN","POST","ROUTE","UID"],"language":"en"},"files":[{"path":"definitions/auth.js","language":"js","content":"var users = {\n\t'1': { id: '1', name: 'Anna', password: 'secret' }\n};\n\n// sessionid -> userid. In a real app this is a database table.\nMAIN.sessions = {};\n\nMAIN.auth = {\n\tsecret: 'change-this-secret',\n\tcookie: 'forge_session',\n\texpire: '1 day',\n\tonread: function(meta, next) {\n\t\tvar user = users[MAIN.sessions[meta.sessionid]];\n\t\tuser ? next(null, { id: user.id, name: user.name }) : next('session not found');\n\t},\n\tonlogout: function(session) {\n\t\tdelete MAIN.sessions[session.sessionid];\n\t},\n\tonremove: function(sessionid) {\n\t\tdelete MAIN.sessions[sessionid];\n\t}\n};\n\nAUTH(MAIN.auth);"},{"path":"controllers/account.js","language":"js","content":"exports.install = function() {\n\tROUTE('POST /login/', login);\n\tROUTE('+POST /logout/', logout);\n\tROUTE('+GET /me/', me);\n};\n\nfunction login($) {\n\n\tvar user = $.body.id && $.body.id === '1' && $.body.password === 'secret' ? { id: '1', name: 'Anna' } : null;\n\n\tif (!user) {\n\t\t$.invalid(401);\n\t\treturn;\n\t}\n\n\tvar sessionid = UID();\n\tMAIN.sessions[sessionid] = user.id;\n\tMAIN.auth.authcookie($, sessionid, user.id, '1 day');\n\t$.json({ name: user.name });\n}\n\nfunction logout($) {\n\tMAIN.auth.logout($);\n\t$.json({ ok: true });\n}\n\nfunction me($) {\n\t$.json({ name: $.user.name });\n}"}],"test":"const denied = await get('/me/');\nassert(denied.status === 401, 'no session: 401, got: ' + denied.status);\n\nconst wrong = await post('/login/', { id: '1', password: 'nope' });\nassert(wrong.status === 401, 'wrong password: 401, got: ' + wrong.status);\n\nconst login = await post('/login/', { id: '1', password: 'secret' });\nassert(login.status === 200, 'login works, got: ' + login.status + ' ' + login.body);\n\nconst setcookie = login.headers.get('set-cookie') || '';\nassert(setcookie.startsWith('forge_session='), 'a signed session cookie is written, got: ' + setcookie);\nconst cookie = setcookie.split(';')[0];\n\nconst me = await get('/me/', { headers: { cookie: cookie, 'user-agent': 'forge-test' } });\nassert(me.status === 200 && me.json && me.json.name === 'Anna', 'the cookie identifies the user, got: ' + me.status + ' ' + me.body);\n\nconst forged = await get('/me/', { headers: { cookie: 'forge_session=' + 'a'.repeat(40) } });\nassert(forged.status === 401, 'a forged cookie is rejected, got: ' + forged.status);\n\nconst headers = { cookie: cookie, 'user-agent': 'forge-test' };\nconst out = await send('POST', '/logout/', {}, { headers });\nassert(out.status === 200, 'logout works, got: ' + out.status + ' ' + out.body);\n\nconst replay = await get('/me/', { headers });\nassert(replay.status === 401, 'the old cookie no longer works after logout, got: ' + replay.status);","test_kind":"http"}
{"id":"total5/calling-actions-from-code","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/calling-actions-from-code/","markdown_url":"https://forge.totaljs.es/en/total5/calling-actions-from-code.md","tags":["actions"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"62ff5e6c046b3621d297646c79a63ed9b25a4bebbf91fe8e8fe62d71da11de0f","checked_at":"2026-10-20T12:17:50.987Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Call an action from your own code","description":"Use ACTION() to run an action from a timer, a job or another action, with its own payload, query and user.","messages":[{"role":"user","content":"Call an action from your own code\n\nUse ACTION() to run an action from a timer, a job or another action, with its own payload, query and user."},{"role":"assistant","content":"`ACTION(name, payload)` returns a caller that runs the action with all its validation, exactly as if it came from a route. The second argument is the payload (the input). Chain `.query()` and `.user()` to give it more data, and finish with `.callback(function(err, response) {})` or `.promise()` (use it with `await`). When the data is invalid, `err` is an error builder instead of an exception.\n\n```js definitions/math.js\nNEWACTION('Math/add', {\n\tname: 'Add two numbers',\n\tinput: '*a:Number, *b:Number',\n\taction: function($, model) {\n\t\t$.callback({ sum: model.a + model.b });\n\t}\n});\n```\n\n```js controllers/math.js\nexports.install = function() {\n\tROUTE('GET /sum/', sum);\n\tROUTE('GET /sum-promise/', sum_promise);\n};\n\nfunction sum($) {\n\tACTION('Math/add', { a: '2', b: '3' }).callback(function(err, response) {\n\t\tif (err)\n\t\t\t$.invalid(err);\n\t\telse\n\t\t\t$.json(response);\n\t});\n}\n\nasync function sum_promise($) {\n\ttry {\n\t\tvar response = await ACTION('Math/add', { a: 10, b: 5 }).promise();\n\t\t$.json(response);\n\t} catch (err) {\n\t\t$.invalid(400);\n\t}\n}\n```"}],"knowledge":{"content_sha256":"12356e310300c771a563e19e888ee2e4f6f2e88d6ab607c4ee563fc5cc2ae7d1","source_sha256":"62ff5e6c046b3621d297646c79a63ed9b25a4bebbf91fe8e8fe62d71da11de0f","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["ACTION","GET","NEWACTION","ROUTE"],"language":"en"},"files":[{"path":"definitions/math.js","language":"js","content":"NEWACTION('Math/add', {\n\tname: 'Add two numbers',\n\tinput: '*a:Number, *b:Number',\n\taction: function($, model) {\n\t\t$.callback({ sum: model.a + model.b });\n\t}\n});"},{"path":"controllers/math.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /sum/', sum);\n\tROUTE('GET /sum-promise/', sum_promise);\n};\n\nfunction sum($) {\n\tACTION('Math/add', { a: '2', b: '3' }).callback(function(err, response) {\n\t\tif (err)\n\t\t\t$.invalid(err);\n\t\telse\n\t\t\t$.json(response);\n\t});\n}\n\nasync function sum_promise($) {\n\ttry {\n\t\tvar response = await ACTION('Math/add', { a: 10, b: 5 }).promise();\n\t\t$.json(response);\n\t} catch (err) {\n\t\t$.invalid(400);\n\t}\n}"}],"test":"const callback = await get('/sum/');\nassert(callback.json && callback.json.sum === 5, 'callback style converts and adds, got: ' + callback.body);\n\nconst promise = await get('/sum-promise/');\nassert(promise.json && promise.json.sum === 15, 'promise style, got: ' + promise.body);","test_kind":"http"}
{"id":"total5/action-chaining","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/action-chaining/","markdown_url":"https://forge.totaljs.es/en/total5/action-chaining.md","tags":["actions","routing"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"330387b09ddb0e71762ba39504901a2a07a4783f06a7005a3e47ae6e83866342","checked_at":"2026-10-20T12:17:36.208Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Chain several actions in one route","description":"List several actions after --> to run them in sequence over the same payload and mark which one answers with (response).","messages":[{"role":"user","content":"Chain several actions in one route\n\nList several actions after --> to run them in sequence over the same payload and mark which one answers with (response)."},{"role":"assistant","content":"A route can run more than one action: write them one after another after `-->`. They run in order, each receives the same payload, and the first error stops the chain. Add `(response)` after the action whose result must be sent; without it, the last action answers.\n\n```js definitions/order.js\nNEWACTION('Order/validate', {\n\tname: 'Validate the order',\n\tinput: '*product:String, *quantity:Number',\n\taction: function($, model) {\n\t\tmodel.quantity > 0 ? $.success() : $.invalid('quantity');\n\t}\n});\n\nNEWACTION('Order/save', {\n\tname: 'Save the order',\n\tinput: '*product:String, *quantity:Number',\n\taction: function($, model) {\n\t\t$.callback({ id: 'order-1', product: model.product, quantity: model.quantity });\n\t}\n});\n```\n\n```js controllers/order.js\nexports.install = function() {\n\tROUTE('POST /order/ --> *Order/validate *Order/save (response)');\n};\n```"}],"knowledge":{"content_sha256":"8510b3a47f4f30e20391383a9d2d13e6c952729718e69a8d657359847aa55490","source_sha256":"330387b09ddb0e71762ba39504901a2a07a4783f06a7005a3e47ae6e83866342","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["NEWACTION","POST","ROUTE"],"language":"en"},"files":[{"path":"definitions/order.js","language":"js","content":"NEWACTION('Order/validate', {\n\tname: 'Validate the order',\n\tinput: '*product:String, *quantity:Number',\n\taction: function($, model) {\n\t\tmodel.quantity > 0 ? $.success() : $.invalid('quantity');\n\t}\n});\n\nNEWACTION('Order/save', {\n\tname: 'Save the order',\n\tinput: '*product:String, *quantity:Number',\n\taction: function($, model) {\n\t\t$.callback({ id: 'order-1', product: model.product, quantity: model.quantity });\n\t}\n});"},{"path":"controllers/order.js","language":"js","content":"exports.install = function() {\n\tROUTE('POST /order/ --> *Order/validate *Order/save (response)');\n};"}],"test":"const ok = await post('/order/', { product: 'Book', quantity: 2 });\nassert(ok.status === 200 && ok.json && ok.json.id === 'order-1', 'the chain runs and the marked action answers, got: ' + ok.body);\n\nconst invalid = await post('/order/', { product: 'Book', quantity: 0 });\nassert(invalid.status === 400, 'an error in the first action stops the chain, got: ' + invalid.status);","test_kind":"http"}
{"id":"total5/middleware","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/middleware/","markdown_url":"https://forge.totaljs.es/en/total5/middleware.md","tags":["middleware","routing","security"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"8789d31759b1e7bd6d3e1e5ee731a61e5be05aeb4f29ded4ea5ee4316c50446b","checked_at":"2026-10-20T12:18:24.925Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Add a middleware to a route","description":"Register a middleware with MIDDLEWARE() and attach it to routes with the #name flag.","messages":[{"role":"user","content":"Add a middleware to a route\n\nRegister a middleware with MIDDLEWARE() and attach it to routes with the #name flag."},{"role":"assistant","content":"A middleware is a function that runs before the route handler. Call `next()` to continue or answer the request yourself to stop it. Register it once with `MIDDLEWARE(name, fn)` and attach it to a route with `#name`.\n\n```js definitions/middleware.js\nMIDDLEWARE('apikey', function($, next) {\n\tif ($.headers['x-api-key'] === 'abc123')\n\t\tnext();\n\telse\n\t\t$.invalid(401);\n});\n```\n\n```js controllers/secure.js\nexports.install = function() {\n\tROUTE('GET /secure/ #apikey', secure);\n\tROUTE('GET /open/', open);\n};\n\nfunction secure($) {\n\t$.json({ ok: true });\n}\n\nfunction open($) {\n\t$.json({ ok: true });\n}\n```"}],"knowledge":{"content_sha256":"21a8ac10bfbd464e0e9bceb13feb511303bd458fb154b488fc83b90d57a5a10c","source_sha256":"8789d31759b1e7bd6d3e1e5ee731a61e5be05aeb4f29ded4ea5ee4316c50446b","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["GET","MIDDLEWARE","ROUTE"],"language":"en"},"files":[{"path":"definitions/middleware.js","language":"js","content":"MIDDLEWARE('apikey', function($, next) {\n\tif ($.headers['x-api-key'] === 'abc123')\n\t\tnext();\n\telse\n\t\t$.invalid(401);\n});"},{"path":"controllers/secure.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /secure/ #apikey', secure);\n\tROUTE('GET /open/', open);\n};\n\nfunction secure($) {\n\t$.json({ ok: true });\n}\n\nfunction open($) {\n\t$.json({ ok: true });\n}"}],"test":"const denied = await get('/secure/');\nassert(denied.status === 401, 'request without the key is rejected (got ' + denied.status + ')');\n\nconst allowed = await get('/secure/', { headers: { 'x-api-key': 'abc123' } });\nassert(allowed.status === 200, 'request with the key is accepted');\n\nconst open = await get('/open/');\nassert(open.status === 200, 'route without the middleware stays open');","test_kind":"http"}
{"id":"total5/websocket-chat","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/websocket-chat/","markdown_url":"https://forge.totaljs.es/en/total5/websocket-chat.md","tags":["websocket","realtime"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"aaca87bc60c63c9e8cb80c34ca77e33c5e19461e203157704544f46d3c4e1349","checked_at":"2026-10-20T12:19:06.026Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Build a WebSocket chat","description":"Declare a SOCKET route, listen to messages and broadcast them to every connected client.","messages":[{"role":"user","content":"Build a WebSocket chat\n\nDeclare a SOCKET route, listen to messages and broadcast them to every connected client."},{"role":"assistant","content":"A WebSocket endpoint is declared with `ROUTE('SOCKET /path/', fn)`. Inside the handler `this` is the WebSocket controller: use `self.on('message', ...)` to receive data and `self.send(...)` to broadcast to all connected clients. `client.send(...)` answers only the sender. Objects are sent and received as JSON by default.\n\n```js controllers/chat.js\nexports.install = function() {\n\tROUTE('SOCKET /chat/', socket);\n};\n\nfunction socket() {\n\n\tvar self = this;\n\n\tself.on('message', function(client, message) {\n\t\tself.send({ type: 'message', text: message.text });\n\t});\n}\n```"}],"knowledge":{"content_sha256":"cf99e7bc7621eedaf3fef99ac01abd403fb18681dbe94220c553c2ec6d39bc58","source_sha256":"aaca87bc60c63c9e8cb80c34ca77e33c5e19461e203157704544f46d3c4e1349","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["JSON","ROUTE","SOCKET"],"language":"en"},"files":[{"path":"controllers/chat.js","language":"js","content":"exports.install = function() {\n\tROUTE('SOCKET /chat/', socket);\n};\n\nfunction socket() {\n\n\tvar self = this;\n\n\tself.on('message', function(client, message) {\n\t\tself.send({ type: 'message', text: message.text });\n\t});\n}"}],"test":"const a = await socket('/chat/');\nconst b = await socket('/chat/');\n\na.send({ text: 'hello' });\n\nconst received = await b.next();\nassert(received.type === 'message' && received.text === 'hello', 'the other client receives the broadcast');\n\nconst echo = await a.next();\nassert(echo.text === 'hello', 'the sender receives the broadcast too');","test_kind":"http"}
{"id":"total5/websocket-client","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/websocket-client/","markdown_url":"https://forge.totaljs.es/en/total5/websocket-client.md","tags":["websocket","realtime","integrations"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"95d1b2e0dec912be65e9e857ea69b9de98b6ed10afd7e20ca4fd88c6e8211797","checked_at":"2026-10-20T12:19:06.754Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Connect to a WebSocket server from Node","description":"Use WEBSOCKETCLIENT to open a WebSocket connection from your app, send messages and react to the answers.","messages":[{"role":"user","content":"Connect to a WebSocket server from Node\n\nUse WEBSOCKETCLIENT to open a WebSocket connection from your app, send messages and react to the answers."},{"role":"assistant","content":"`WEBSOCKETCLIENT(function(client) {})` gives you a client. Call `client.connect(url)` with a `ws://` or `wss://` address, listen with `client.on('open' | 'message' | 'close' | 'error', fn)` and send with `client.send(object)`; objects travel as JSON. Use `client.headers` and `client.cookies` to authenticate before connecting, and `client.close()` to disconnect.\n\nThe example connects to an echo socket of its own app; point it at any WebSocket server in real code.\n\n```js controllers/echo.js\nexports.install = function() {\n\tROUTE('SOCKET /echo/', echo);\n\tROUTE('GET /relay/', relay);\n};\n\nfunction echo() {\n\tthis.on('message', function(client, message) {\n\t\tclient.send({ echo: message.text });\n\t});\n}\n\nfunction relay($) {\n\n\tWEBSOCKETCLIENT(function(client) {\n\n\t\tclient.connect($.hostname('/echo/').replace('http', 'ws'));\n\n\t\tclient.on('open', function() {\n\t\t\tclient.send({ text: 'ping' });\n\t\t});\n\n\t\tclient.on('message', function(message) {\n\t\t\tclient.close();\n\t\t\t$.json(message);\n\t\t});\n\n\t\tclient.on('error', function(err) {\n\t\t\t$.invalid(err + '');\n\t\t});\n\t});\n}\n```"}],"knowledge":{"content_sha256":"4f9fb10635b7e3d5c35888a6df54fadfa832a9d2bdc4701c8b2c7c68b702e1b3","source_sha256":"95d1b2e0dec912be65e9e857ea69b9de98b6ed10afd7e20ca4fd88c6e8211797","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["GET","JSON","ROUTE","SOCKET","WEBSOCKETCLIENT"],"language":"en"},"files":[{"path":"controllers/echo.js","language":"js","content":"exports.install = function() {\n\tROUTE('SOCKET /echo/', echo);\n\tROUTE('GET /relay/', relay);\n};\n\nfunction echo() {\n\tthis.on('message', function(client, message) {\n\t\tclient.send({ echo: message.text });\n\t});\n}\n\nfunction relay($) {\n\n\tWEBSOCKETCLIENT(function(client) {\n\n\t\tclient.connect($.hostname('/echo/').replace('http', 'ws'));\n\n\t\tclient.on('open', function() {\n\t\t\tclient.send({ text: 'ping' });\n\t\t});\n\n\t\tclient.on('message', function(message) {\n\t\t\tclient.close();\n\t\t\t$.json(message);\n\t\t});\n\n\t\tclient.on('error', function(err) {\n\t\t\t$.invalid(err + '');\n\t\t});\n\t});\n}"}],"test":"const res = await get('/relay/');\nassert(res.status === 200, 'the relay answers, got: ' + res.status + ' ' + res.body);\nassert(res.json && res.json.echo === 'ping', 'the message went through the WebSocket and came back, got: ' + res.body);","test_kind":"http"}
{"id":"total5/events","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/events/","markdown_url":"https://forge.totaljs.es/en/total5/events.md","tags":["events","architecture"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"68c3a9e4182f956f1ed15f2bd101ff86efe4780edb6e23a2d6ca380a89bebf27","checked_at":"2026-10-20T12:18:04.712Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Decouple your code with events","description":"Emit and listen to application events with EMIT(), ON(), ONCE() and OFF() so modules do not call each other directly.","messages":[{"role":"user","content":"Decouple your code with events\n\nEmit and listen to application events with EMIT(), ON(), ONCE() and OFF() so modules do not call each other directly."},{"role":"assistant","content":"`ON(name, fn)` registers a listener, `EMIT(name, a, b, c...)` calls every listener with the given arguments, `ONCE(name, fn)` listens only for the first emit and `OFF(name, fn)` removes listeners. Events are in-process and synchronous. Use them to let a feature react to something (an order was paid) without the code that causes it knowing about the feature.\n\n```js definitions/audit.js\nMAIN.audit = [];\n\nON('order.paid', function(order) {\n\tMAIN.audit.push('paid ' + order.id);\n});\n\nONCE('order.first', function(order) {\n\tMAIN.audit.push('first ' + order.id);\n});\n```\n\n```js controllers/orders.js\nexports.install = function() {\n\tROUTE('POST /pay/{id}/', pay);\n\tROUTE('GET /audit/', audit);\n};\n\nfunction pay($) {\n\tvar order = { id: $.params.id };\n\tEMIT('order.first', order);\n\tEMIT('order.paid', order);\n\t$.json({ ok: true });\n}\n\nfunction audit($) {\n\t$.json(MAIN.audit);\n}\n```"}],"knowledge":{"content_sha256":"8f8c320254d43e4b540edf1b8cddaf71ba605cf9f39cb70fc33445a8edfc35ee","source_sha256":"68c3a9e4182f956f1ed15f2bd101ff86efe4780edb6e23a2d6ca380a89bebf27","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["EMIT","GET","MAIN","OFF","ONCE","POST","ROUTE"],"language":"en"},"files":[{"path":"definitions/audit.js","language":"js","content":"MAIN.audit = [];\n\nON('order.paid', function(order) {\n\tMAIN.audit.push('paid ' + order.id);\n});\n\nONCE('order.first', function(order) {\n\tMAIN.audit.push('first ' + order.id);\n});"},{"path":"controllers/orders.js","language":"js","content":"exports.install = function() {\n\tROUTE('POST /pay/{id}/', pay);\n\tROUTE('GET /audit/', audit);\n};\n\nfunction pay($) {\n\tvar order = { id: $.params.id };\n\tEMIT('order.first', order);\n\tEMIT('order.paid', order);\n\t$.json({ ok: true });\n}\n\nfunction audit($) {\n\t$.json(MAIN.audit);\n}"}],"test":"await post('/pay/a1/', {});\nawait post('/pay/a2/', {});\nconst res = await get('/audit/');\nassert(res.json && res.json.join() === 'first a1,paid a1,paid a2', 'ON runs every time, ONCE only the first time, got: ' + res.body);","test_kind":"http"}
{"id":"total5/nosql-crud","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/nosql-crud/","markdown_url":"https://forge.totaljs.es/en/total5/nosql-crud.md","tags":["nosql","database","actions"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"b8d585abba651b018ee7e2ad8c15a55cfea08fd6fa89ed80d33d544c9642cda5","checked_at":"2026-10-20T12:18:27.111Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Store and read data with the embedded NoSQL database","description":"Insert and query documents with NOSQL() and expose them through actions, without installing any database.","messages":[{"role":"user","content":"Store and read data with the embedded NoSQL database\n\nInsert and query documents with NOSQL() and expose them through actions, without installing any database."},{"role":"assistant","content":"Total.js ships with an embedded NoSQL database. `NOSQL('name')` opens the database file `databases/name.nosql`, creating it on first use. Operations return a builder: chain `.where()` and finish with `.callback(function(err, response) {})`.\n\n```js definitions/notes.js\nNEWACTION('Notes/insert', {\n\tname: 'Insert a note',\n\tinput: '*text:String',\n\taction: function($, model) {\n\t\tmodel.id = UID();\n\t\tNOSQL('notes').insert(model).callback(function() {\n\t\t\t$.success(model.id);\n\t\t});\n\t}\n});\n\nNEWACTION('Notes/query', {\n\tname: 'List notes',\n\taction: function($) {\n\t\tNOSQL('notes').find().callback(function(err, response) {\n\t\t\t$.callback(response);\n\t\t});\n\t}\n});\n```\n\n```js controllers/notes.js\nexports.install = function() {\n\tROUTE('POST /notes/ --> *Notes/insert');\n\tROUTE('GET /notes/ --> *Notes/query');\n};\n```"}],"knowledge":{"content_sha256":"558ad712b87ce6446e701b9609e02035bc1946784e2a0f1ae6e48d9252551a10","source_sha256":"b8d585abba651b018ee7e2ad8c15a55cfea08fd6fa89ed80d33d544c9642cda5","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["GET","NEWACTION","NOSQL","POST","ROUTE","Total.js","UID"],"language":"en"},"files":[{"path":"definitions/notes.js","language":"js","content":"NEWACTION('Notes/insert', {\n\tname: 'Insert a note',\n\tinput: '*text:String',\n\taction: function($, model) {\n\t\tmodel.id = UID();\n\t\tNOSQL('notes').insert(model).callback(function() {\n\t\t\t$.success(model.id);\n\t\t});\n\t}\n});\n\nNEWACTION('Notes/query', {\n\tname: 'List notes',\n\taction: function($) {\n\t\tNOSQL('notes').find().callback(function(err, response) {\n\t\t\t$.callback(response);\n\t\t});\n\t}\n});"},{"path":"controllers/notes.js","language":"js","content":"exports.install = function() {\n\tROUTE('POST /notes/ --> *Notes/insert');\n\tROUTE('GET /notes/ --> *Notes/query');\n};"}],"test":"const created = await post('/notes/', { text: 'First note' });\nassert(created.status === 200, 'note is created (got ' + created.status + ')');\n\nconst list = await get('/notes/');\nassert(Array.isArray(list.json) && list.json.length === 1, 'one note is stored');\nassert(list.json[0].text === 'First note', 'the stored text is returned');","test_kind":"http"}
{"id":"total5/nosql-queries","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/nosql-queries/","markdown_url":"https://forge.totaljs.es/en/total5/nosql-queries.md","tags":["nosql","database","queries"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"e5f0c02e7a45cdff343764d5e17ced294dd4a1d678dbd2d9f742af70c0a67578","checked_at":"2026-10-20T12:18:27.845Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Query the NoSQL database","description":"Filter, sort, page and project documents with where, in, between, search, sort, take, skip and fields.","messages":[{"role":"user","content":"Query the NoSQL database\n\nFilter, sort, page and project documents with where, in, between, search, sort, take, skip and fields."},{"role":"assistant","content":"`NOSQL('name').find()` returns a query builder. Chain the conditions and finish with `.callback(function(err, response) {})`; `response` is an array of documents. The most useful methods:\n\n- `.where('age', '>', 30)` compares a field (the operator defaults to `=`; use `>`, `<`, `>=`, `<=`, `<>`)\n- `.in('city', ['Bratislava', 'Prague'])` matches any of the values\n- `.between('age', 20, 40)` matches a range\n- `.search('name', 'ann')` searches words inside a text field\n- `.sort('age_desc')` sorts by a field (`_asc` or `_desc`)\n- `.take(10)` and `.skip(20)` page the results\n- `.fields('name,age')` returns only those fields\n- `.first()` returns one document instead of an array\n\n```js controllers/people.js\nexports.install = function() {\n\tROUTE('POST /seed/', seed);\n\tROUTE('GET /older/', older);\n\tROUTE('GET /cities/', cities);\n\tROUTE('GET /range/', range);\n\tROUTE('GET /search/', search);\n\tROUTE('GET /page/', page);\n\tROUTE('GET /names/', names);\n\tROUTE('GET /youngest/', youngest);\n};\n\nfunction seed($) {\n\tvar people = [\n\t\t{ id: '1', name: 'Anna Novak', age: 25, city: 'Bratislava' },\n\t\t{ id: '2', name: 'Boris Kral', age: 35, city: 'Prague' },\n\t\t{ id: '3', name: 'Cyril Dvorak', age: 45, city: 'Vienna' },\n\t\t{ id: '4', name: 'Diana Hall', age: 30, city: 'Bratislava' }\n\t];\n\tpeople.wait(function(item, next) {\n\t\tNOSQL('people').insert(item).callback(next);\n\t}, () => $.json({ count: people.length }));\n}\n\nfunction older($) {\n\tNOSQL('people').find().where('age', '>', 30).sort('age_desc').callback((err, response) => $.json(response));\n}\n\nfunction cities($) {\n\tNOSQL('people').find().in('city', ['Prague', 'Vienna']).sort('name_asc').callback((err, response) => $.json(response));\n}\n\nfunction range($) {\n\tNOSQL('people').find().between('age', 26, 40).sort('age_asc').callback((err, response) => $.json(response));\n}\n\nfunction search($) {\n\tNOSQL('people').find().search('name', 'hall').callback((err, response) => $.json(response));\n}\n\nfunction page($) {\n\tNOSQL('people').find().sort('age_asc').take(2).skip(2).callback((err, response) => $.json(response));\n}\n\nfunction names($) {\n\tNOSQL('people').find().fields('name').sort('name_asc').take(2).callback((err, response) => $.json(response));\n}\n\nfunction youngest($) {\n\tNOSQL('people').find().sort('age_asc').first().callback((err, response) => $.json(response));\n}\n```"}],"knowledge":{"content_sha256":"2b8af3412da934cdbd7d85af6450aca5066598672966481749b634c4c47d8197","source_sha256":"e5f0c02e7a45cdff343764d5e17ced294dd4a1d678dbd2d9f742af70c0a67578","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["GET","NOSQL","POST","ROUTE"],"language":"en"},"files":[{"path":"controllers/people.js","language":"js","content":"exports.install = function() {\n\tROUTE('POST /seed/', seed);\n\tROUTE('GET /older/', older);\n\tROUTE('GET /cities/', cities);\n\tROUTE('GET /range/', range);\n\tROUTE('GET /search/', search);\n\tROUTE('GET /page/', page);\n\tROUTE('GET /names/', names);\n\tROUTE('GET /youngest/', youngest);\n};\n\nfunction seed($) {\n\tvar people = [\n\t\t{ id: '1', name: 'Anna Novak', age: 25, city: 'Bratislava' },\n\t\t{ id: '2', name: 'Boris Kral', age: 35, city: 'Prague' },\n\t\t{ id: '3', name: 'Cyril Dvorak', age: 45, city: 'Vienna' },\n\t\t{ id: '4', name: 'Diana Hall', age: 30, city: 'Bratislava' }\n\t];\n\tpeople.wait(function(item, next) {\n\t\tNOSQL('people').insert(item).callback(next);\n\t}, () => $.json({ count: people.length }));\n}\n\nfunction older($) {\n\tNOSQL('people').find().where('age', '>', 30).sort('age_desc').callback((err, response) => $.json(response));\n}\n\nfunction cities($) {\n\tNOSQL('people').find().in('city', ['Prague', 'Vienna']).sort('name_asc').callback((err, response) => $.json(response));\n}\n\nfunction range($) {\n\tNOSQL('people').find().between('age', 26, 40).sort('age_asc').callback((err, response) => $.json(response));\n}\n\nfunction search($) {\n\tNOSQL('people').find().search('name', 'hall').callback((err, response) => $.json(response));\n}\n\nfunction page($) {\n\tNOSQL('people').find().sort('age_asc').take(2).skip(2).callback((err, response) => $.json(response));\n}\n\nfunction names($) {\n\tNOSQL('people').find().fields('name').sort('name_asc').take(2).callback((err, response) => $.json(response));\n}\n\nfunction youngest($) {\n\tNOSQL('people').find().sort('age_asc').first().callback((err, response) => $.json(response));\n}"}],"test":"await post('/seed/', {});\n\nconst older = await get('/older/');\nassert(older.json && older.json.map(n => n.id).join() === '3,2', 'where > and sort desc, got: ' + older.body);\n\nconst cities = await get('/cities/');\nassert(cities.json && cities.json.map(n => n.id).join() === '2,3', 'in, got: ' + cities.body);\n\nconst range = await get('/range/');\nassert(range.json && range.json.map(n => n.id).join() === '4,2', 'between, got: ' + range.body);\n\nconst search = await get('/search/');\nassert(search.json && search.json.length === 1 && search.json[0].id === '4', 'search, got: ' + search.body);\n\nconst page = await get('/page/');\nassert(page.json && page.json.map(n => n.id).join() === '2,3', 'take and skip, got: ' + page.body);\n\nconst names = await get('/names/');\nassert(names.json && names.json.length === 2 && names.json[0].name === 'Anna Novak' && !('age' in names.json[0]), 'fields, got: ' + names.body);\n\nconst youngest = await get('/youngest/');\nassert(youngest.json && !Array.isArray(youngest.json) && youngest.json.id === '1', 'first returns one document, got: ' + youngest.body);","test_kind":"http"}
{"id":"total5/nosql-update-and-remove","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/nosql-update-and-remove/","markdown_url":"https://forge.totaljs.es/en/total5/nosql-update-and-remove.md","tags":["nosql","database"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"40b5d59139d55d3cdbcc0f0d1ffad986d71fd6294a626a4d4de443bc147eef9e","checked_at":"2026-10-20T12:18:28.779Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Update and remove NoSQL documents","description":"Change documents with update() and where(), delete them with remove(), and read how many were affected.","messages":[{"role":"user","content":"Update and remove NoSQL documents\n\nChange documents with update() and where(), delete them with remove(), and read how many were affected."},{"role":"assistant","content":"`update(data)` and `remove()` work like `find()`: add the conditions with `.where()` and finish with `.callback(function(err, count) {})`. The callback receives the number of documents that were changed or removed. **Always add a condition**: without one the operation applies to every document.\n\n`update(data)` merges the fields of `data` into the matching documents. Use `read()` to get a single document.\n\n```js controllers/tasks.js\nexports.install = function() {\n\tROUTE('POST /tasks/', create);\n\tROUTE('PATCH /tasks/{id}/', done);\n\tROUTE('DELETE /tasks/{id}/', remove);\n\tROUTE('GET /tasks/{id}/', read);\n};\n\nfunction create($) {\n\tvar task = { id: $.body.id, title: $.body.title, done: false };\n\tNOSQL('tasks').insert(task).callback(() => $.json(task));\n}\n\nfunction done($) {\n\tNOSQL('tasks').update({ done: true }).where('id', $.params.id).callback((err, count) => $.json({ updated: count }));\n}\n\nfunction remove($) {\n\tNOSQL('tasks').remove().where('id', $.params.id).callback((err, count) => $.json({ removed: count }));\n}\n\nfunction read($) {\n\tNOSQL('tasks').read().where('id', $.params.id).callback((err, response) => $.json(response || { missing: true }));\n}\n```"}],"knowledge":{"content_sha256":"894a7fd501ee19c762e00adca86b75172e9c997c4b994da7eb426d66e2a487d7","source_sha256":"40b5d59139d55d3cdbcc0f0d1ffad986d71fd6294a626a4d4de443bc147eef9e","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["DELETE","GET","NOSQL","PATCH","POST","ROUTE"],"language":"en"},"files":[{"path":"controllers/tasks.js","language":"js","content":"exports.install = function() {\n\tROUTE('POST /tasks/', create);\n\tROUTE('PATCH /tasks/{id}/', done);\n\tROUTE('DELETE /tasks/{id}/', remove);\n\tROUTE('GET /tasks/{id}/', read);\n};\n\nfunction create($) {\n\tvar task = { id: $.body.id, title: $.body.title, done: false };\n\tNOSQL('tasks').insert(task).callback(() => $.json(task));\n}\n\nfunction done($) {\n\tNOSQL('tasks').update({ done: true }).where('id', $.params.id).callback((err, count) => $.json({ updated: count }));\n}\n\nfunction remove($) {\n\tNOSQL('tasks').remove().where('id', $.params.id).callback((err, count) => $.json({ removed: count }));\n}\n\nfunction read($) {\n\tNOSQL('tasks').read().where('id', $.params.id).callback((err, response) => $.json(response || { missing: true }));\n}"}],"test":"await post('/tasks/', { id: 't1', title: 'Write the recipe' });\n\nconst updated = await send('PATCH', '/tasks/t1/', {});\nassert(updated.json && updated.json.updated === 1, 'update returns the count, got: ' + updated.body);\n\nconst read = await get('/tasks/t1/');\nassert(read.json && read.json.done === true && read.json.title === 'Write the recipe', 'the fields were merged, got: ' + read.body);\n\nconst none = await send('PATCH', '/tasks/unknown/', {});\nassert(none.json && none.json.updated === 0, 'nothing matched, got: ' + none.body);\n\nconst removed = await send('DELETE', '/tasks/t1/');\nassert(removed.json && removed.json.removed === 1, 'remove returns the count, got: ' + removed.body);\n\nconst gone = await get('/tasks/t1/');\nassert(gone.json && gone.json.missing === true, 'the document is gone, got: ' + gone.body);","test_kind":"http"}
{"id":"total5/data-api","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/data-api/","markdown_url":"https://forge.totaljs.es/en/total5/data-api.md","tags":["database","nosql","querybuilder"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"52fdc0f792f0c743a37105db6ec6a2b4e6b286bddc8211b568f2f71ec314105d","checked_at":"2026-10-20T12:17:59.456Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Use the DATA API to switch databases","description":"DATA addresses a table as database/table, so the same query code works with the embedded NoSQL and with other databases.","messages":[{"role":"user","content":"Use the DATA API to switch databases\n\nDATA addresses a table as database/table, so the same query code works with the embedded NoSQL and with other databases."},{"role":"assistant","content":"`DATA` is a database-agnostic entry point to the query builder. The first argument is `driver/table`; `nosql/people` means \"the embedded NoSQL database, table `people`\". The builder methods (`where`, `sort`, `take`, `callback`) are the same you use with `NOSQL()`, so the code of your actions stays the same if you later replace the driver.\n\n| Call | Does |\n|---|---|\n| `DATA.insert(table, doc)` | inserts a document |\n| `DATA.find(table)` | returns a builder for a list |\n| `DATA.read(table)` | returns a builder for one document |\n| `DATA.update(table, data)` | updates documents |\n| `DATA.remove(table)` | removes documents |\n\n```js definitions/people.js\nNEWACTION('People/insert', {\n\tname: 'Insert a person',\n\tinput: '*name:String, age:Number',\n\taction: function($, model) {\n\t\tmodel.id = UID();\n\t\tDATA.insert('nosql/people', model).callback(() => $.success(model.id));\n\t}\n});\n\nNEWACTION('People/query', {\n\tname: 'List people',\n\tquery: 'sort:Lowercase',\n\taction: function($) {\n\t\tDATA.find('nosql/people').sort($.query.sort || 'name_asc').callback((err, response) => $.callback(response));\n\t}\n});\n\nNEWACTION('People/rename', {\n\tname: 'Rename a person',\n\tparams: '*id:UID',\n\tinput: '*name:String',\n\taction: function($, model) {\n\t\tDATA.update('nosql/people', { name: model.name }).where('id', $.params.id).callback((err, count) => $.success(count));\n\t}\n});\n```\n\n```js controllers/people.js\nexports.install = function() {\n\tROUTE('POST /people/ --> *People/insert');\n\tROUTE('GET /people/ --> *People/query');\n\tROUTE('PATCH /people/{id}/ --> *People/rename');\n};\n```"}],"knowledge":{"content_sha256":"fa4d9b6515a2a695cd7f63b74ef0727aad586452f23b903a239efdd0792b4dbf","source_sha256":"52fdc0f792f0c743a37105db6ec6a2b4e6b286bddc8211b568f2f71ec314105d","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["DATA","GET","NEWACTION","NOSQL","PATCH","POST","ROUTE","UID"],"language":"en"},"files":[{"path":"definitions/people.js","language":"js","content":"NEWACTION('People/insert', {\n\tname: 'Insert a person',\n\tinput: '*name:String, age:Number',\n\taction: function($, model) {\n\t\tmodel.id = UID();\n\t\tDATA.insert('nosql/people', model).callback(() => $.success(model.id));\n\t}\n});\n\nNEWACTION('People/query', {\n\tname: 'List people',\n\tquery: 'sort:Lowercase',\n\taction: function($) {\n\t\tDATA.find('nosql/people').sort($.query.sort || 'name_asc').callback((err, response) => $.callback(response));\n\t}\n});\n\nNEWACTION('People/rename', {\n\tname: 'Rename a person',\n\tparams: '*id:UID',\n\tinput: '*name:String',\n\taction: function($, model) {\n\t\tDATA.update('nosql/people', { name: model.name }).where('id', $.params.id).callback((err, count) => $.success(count));\n\t}\n});"},{"path":"controllers/people.js","language":"js","content":"exports.install = function() {\n\tROUTE('POST /people/ --> *People/insert');\n\tROUTE('GET /people/ --> *People/query');\n\tROUTE('PATCH /people/{id}/ --> *People/rename');\n};"}],"test":"const created = await post('/people/', { name: 'Zoe', age: 31 });\nassert(created.status === 200 && created.json && created.json.value, 'insert returns the id, got: ' + created.body);\nawait post('/people/', { name: 'Adam', age: 28 });\n\nconst list = await get('/people/?sort=name_asc');\nassert(list.json && list.json.map(n => n.name).join() === 'Adam,Zoe', 'find with sort, got: ' + list.body);\n\nconst renamed = await send('PATCH', '/people/' + created.json.value + '/', { name: 'Zoey' });\nassert(renamed.json && renamed.json.value === 1, 'update returns the count, got: ' + renamed.body);\n\nconst again = await get('/people/?sort=name_desc');\nassert(again.json && again.json[0].name === 'Zoey', 'the rename is stored, got: ' + again.body);","test_kind":"http"}
{"id":"total5/cache","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/cache/","markdown_url":"https://forge.totaljs.es/en/total5/cache.md","tags":["cache","performance"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"4e3784643330655f31f4b83d7f36571945101239f1422f7d60b809761ce03629","checked_at":"2026-10-20T12:17:50.261Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Cache values in memory","description":"Store values with CACHE.set() and an expiration, read them with CACHE.get() and clear them with remove() or reset().","messages":[{"role":"user","content":"Cache values in memory\n\nStore values with CACHE.set() and an expiration, read them with CACHE.get() and clear them with remove() or reset()."},{"role":"assistant","content":"`CACHE` is an in-memory key-value store with expiration. `CACHE.set(key, value, expire)` stores a value (the expiration is a text such as `'10 seconds'` or `'5 minutes'`, default `'5 minutes'`). `CACHE.get(key, default)` returns the value, or the default once it has expired. `CACHE.remove(key)` deletes one key and `CACHE.reset(search)` deletes every key that contains the text.\n\nUse it for results that are expensive to compute and may be a little stale. The cache lives in the process: it is cleared on restart and is not shared between processes.\n\n```js controllers/cache.js\nexports.install = function() {\n\tROUTE('GET /slow/', slow);\n\tROUTE('DELETE /slow/', clear);\n};\n\nvar computed = 0;\n\nfunction slow($) {\n\n\tvar cached = CACHE.get('slow-result');\n\n\tif (cached) {\n\t\t$.json({ value: cached, cached: true, computed: computed });\n\t\treturn;\n\t}\n\n\tcomputed++;\n\tvar value = 'result-' + computed;\n\tCACHE.set('slow-result', value, '1 second');\n\t$.json({ value: value, cached: false, computed: computed });\n}\n\nfunction clear($) {\n\tCACHE.remove('slow-result');\n\t$.json({ ok: true });\n}\n```"}],"knowledge":{"content_sha256":"f39f6142602f34545a96180b8c66d6dffc02dcddad842094041e48d80b4315ea","source_sha256":"4e3784643330655f31f4b83d7f36571945101239f1422f7d60b809761ce03629","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["CACHE","DELETE","GET","ROUTE"],"language":"en"},"files":[{"path":"controllers/cache.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /slow/', slow);\n\tROUTE('DELETE /slow/', clear);\n};\n\nvar computed = 0;\n\nfunction slow($) {\n\n\tvar cached = CACHE.get('slow-result');\n\n\tif (cached) {\n\t\t$.json({ value: cached, cached: true, computed: computed });\n\t\treturn;\n\t}\n\n\tcomputed++;\n\tvar value = 'result-' + computed;\n\tCACHE.set('slow-result', value, '1 second');\n\t$.json({ value: value, cached: false, computed: computed });\n}\n\nfunction clear($) {\n\tCACHE.remove('slow-result');\n\t$.json({ ok: true });\n}"}],"test":"const first = await get('/slow/');\nassert(first.json && first.json.cached === false && first.json.value === 'result-1', 'first call computes, got: ' + first.body);\n\nconst second = await get('/slow/');\nassert(second.json && second.json.cached === true && second.json.computed === 1, 'second call is served from the cache, got: ' + second.body);\n\nawait send('DELETE', '/slow/');\nconst third = await get('/slow/');\nassert(third.json && third.json.cached === false && third.json.computed === 2, 'remove() clears the key, got: ' + third.body);\n\nawait new Promise(r => setTimeout(r, 1300));\nconst expired = await get('/slow/');\nassert(expired.json && expired.json.cached === false && expired.json.computed === 3, 'the value expires after 1 second, got: ' + expired.body);","test_kind":"http"}
{"id":"total5/memorize","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/memorize/","markdown_url":"https://forge.totaljs.es/en/total5/memorize.md","tags":["storage","settings"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"1948b5f5a9700ef26507cb08b119c2cdc2a925a82342a41fbcf775851751bdfe","checked_at":"2026-10-20T12:18:24.200Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Persist small settings with MEMORIZE","description":"Keep a JSON object on disk that survives restarts, for counters and settings that do not need a database.","messages":[{"role":"user","content":"Persist small settings with MEMORIZE\n\nKeep a JSON object on disk that survives restarts, for counters and settings that do not need a database."},{"role":"assistant","content":"`MEMORIZE(name)` returns a plain object that is saved to `databases/memorize_<name>.json` and loaded again when the app starts. Change it with `data.set(key, value)`, which also schedules the save. Saves are delayed (10 seconds by default) so several changes become one write; pass a delay in milliseconds as the second argument to change it. It is meant for small amounts of data such as counters, flags and settings.\n\n```js definitions/counter.js\nMAIN.counter = MEMORIZE('counter', 100);\n```\n\n```js controllers/counter.js\nexports.install = function() {\n\tROUTE('POST /hit/', hit);\n\tROUTE('GET /hits/', hits);\n};\n\nfunction hit($) {\n\tvar count = (MAIN.counter.hits || 0) + 1;\n\tMAIN.counter.set('hits', count);\n\t$.json({ hits: count });\n}\n\nfunction hits($) {\n\t$.json({ hits: MAIN.counter.hits || 0 });\n}\n```"}],"knowledge":{"content_sha256":"838cecbd016addba2c097481142c15d7028bebdf4bb70ec09759c6a246cfb7ed","source_sha256":"1948b5f5a9700ef26507cb08b119c2cdc2a925a82342a41fbcf775851751bdfe","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["GET","MAIN","MEMORIZE","POST","ROUTE"],"language":"en"},"files":[{"path":"definitions/counter.js","language":"js","content":"MAIN.counter = MEMORIZE('counter', 100);"},{"path":"controllers/counter.js","language":"js","content":"exports.install = function() {\n\tROUTE('POST /hit/', hit);\n\tROUTE('GET /hits/', hits);\n};\n\nfunction hit($) {\n\tvar count = (MAIN.counter.hits || 0) + 1;\n\tMAIN.counter.set('hits', count);\n\t$.json({ hits: count });\n}\n\nfunction hits($) {\n\t$.json({ hits: MAIN.counter.hits || 0 });\n}"}],"test":"await post('/hit/', {});\nawait post('/hit/', {});\nconst res = await get('/hits/');\nassert(res.json && res.json.hits === 2, 'the object keeps the values in memory, got: ' + res.body);","test_kind":"http"}
{"id":"total5/unique-ids-and-hashes","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/unique-ids-and-hashes/","markdown_url":"https://forge.totaljs.es/en/total5/unique-ids-and-hashes.md","tags":["utilities","ids","security"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"e127a8ce7349709b44ac2cddb45d989547ff98e9cb3eeacce84214a3e625590a","checked_at":"2026-10-20T12:18:57.340Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Generate unique ids and hashes","description":"Create ids with UID() and GUID(), and hashes with HASH(), and know which one to use where.","messages":[{"role":"user","content":"Generate unique ids and hashes\n\nCreate ids with UID() and GUID(), and hashes with HASH(), and know which one to use where."},{"role":"assistant","content":"- `UID()` creates a short, time-ordered, unique id (12 characters). It sorts by creation time and is what the `UID` input type validates. Use it as the primary key of your documents.\n- `GUID(length)` creates a random string of the requested length. Use it for tokens and file names.\n- `HASH(value)` returns a fast numeric hash (not cryptographic). Use it for cache keys, ETags and sharding, never for passwords.\n\n```js controllers/ids.js\nexports.install = function() {\n\tROUTE('GET /ids/', ids);\n};\n\nfunction ids($) {\n\n\tvar a = UID();\n\tvar b = UID();\n\n\t$.json({\n\t\tuid: a,\n\t\tuidlength: a.length,\n\t\tvalid: a.isUID(),\n\t\tdifferent: a !== b,\n\t\tguid: GUID(20),\n\t\tsameHash: HASH('total') === HASH('total'),\n\t\totherHash: HASH('total') !== HASH('Total'),\n\t\ttype: typeof(HASH('total'))\n\t});\n}\n```"}],"knowledge":{"content_sha256":"a2d933d0ce2ac0a974dc8fe539259bf770d8fc8c8feedabafebf804b6285bda5","source_sha256":"e127a8ce7349709b44ac2cddb45d989547ff98e9cb3eeacce84214a3e625590a","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["GET","GUID","HASH","ROUTE","UID"],"language":"en"},"files":[{"path":"controllers/ids.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /ids/', ids);\n};\n\nfunction ids($) {\n\n\tvar a = UID();\n\tvar b = UID();\n\n\t$.json({\n\t\tuid: a,\n\t\tuidlength: a.length,\n\t\tvalid: a.isUID(),\n\t\tdifferent: a !== b,\n\t\tguid: GUID(20),\n\t\tsameHash: HASH('total') === HASH('total'),\n\t\totherHash: HASH('total') !== HASH('Total'),\n\t\ttype: typeof(HASH('total'))\n\t});\n}"}],"test":"const res = await get('/ids/');\nassert(res.json && res.json.uidlength === 12 && res.json.valid === true, 'UID is a valid 12 character id, got: ' + res.body);\nassert(res.json && res.json.different === true, 'every UID is different');\nassert(res.json && res.json.guid.length === 20, 'GUID has the requested length, got: ' + (res.json && res.json.guid));\nassert(res.json && res.json.sameHash === true && res.json.otherHash === true && res.json.type === 'number', 'HASH is a stable number');","test_kind":"http"}
{"id":"total5/filecache","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/filecache/","markdown_url":"https://forge.totaljs.es/en/total5/filecache.md","tags":["cache","performance","files"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"c967102b3f752d3929464a88cb89976d93ca7f66fa01c8062e1ecedce89b4e77","checked_at":"2026-10-20T12:18:06.885Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Cache slow results on disk with FILECACHE","description":"Use FILECACHE() to compute a result once and reuse it from a file for a period, even after a restart.","messages":[{"role":"user","content":"Cache slow results on disk with FILECACHE\n\nUse FILECACHE() to compute a result once and reuse it from a file for a period, even after a restart."},{"role":"assistant","content":"`FILECACHE(id, expiration, callback, maker)` looks for a cached file for `id`. If it exists and is younger than `expiration` (text such as `'1 minute'`), the callback receives its content; otherwise it calls `maker(done)` to compute the value, stores it and returns it. The result is serialized as JSON. Unlike `CACHE`, it survives restarts and works for large results.\n\n```js controllers/report.js\nexports.install = function() {\n\tROUTE('GET /report/', report);\n};\n\nvar computed = 0;\n\nfunction report($) {\n\n\tFILECACHE('monthly-report', '1 minute', function(err, response) {\n\t\t$.json({ report: response, computed: computed });\n\t}, function(done) {\n\t\t// The expensive work goes here\n\t\tcomputed++;\n\t\tdone(null, { total: 42, generated: computed });\n\t});\n}\n```"}],"knowledge":{"content_sha256":"199e2b942373007270d2a87c79ec2477773b3bf71f16023c0c31bf2294bcf4a9","source_sha256":"c967102b3f752d3929464a88cb89976d93ca7f66fa01c8062e1ecedce89b4e77","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["CACHE","FILECACHE","GET","JSON","ROUTE"],"language":"en"},"files":[{"path":"controllers/report.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /report/', report);\n};\n\nvar computed = 0;\n\nfunction report($) {\n\n\tFILECACHE('monthly-report', '1 minute', function(err, response) {\n\t\t$.json({ report: response, computed: computed });\n\t}, function(done) {\n\t\t// The expensive work goes here\n\t\tcomputed++;\n\t\tdone(null, { total: 42, generated: computed });\n\t});\n}"}],"test":"const first = await get('/report/');\nassert(first.json && first.json.report && first.json.report.total === 42, 'the first call computes the report, got: ' + first.body);\nassert(first.json && first.json.computed === 1, 'it was computed once');\n\nconst second = await get('/report/');\nassert(second.json && second.json.report.total === 42, 'the second call returns the stored report, got: ' + second.body);\nassert(second.json && second.json.computed === 1, 'and the maker was not called again, got: ' + second.body);","test_kind":"http"}
{"id":"total5/filestorage","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/filestorage/","markdown_url":"https://forge.totaljs.es/en/total5/filestorage.md","tags":["files","upload","storage"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"76710ced2f5226aa537ea66d6d455223325ad93666d4beba7a760601733b04bd","checked_at":"2026-10-20T12:18:07.617Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Store uploaded files with FILESTORAGE","description":"Save uploads in a managed file storage with file.fs() and send them back with $.filefs().","messages":[{"role":"user","content":"Store uploaded files with FILESTORAGE\n\nSave uploads in a managed file storage with file.fs() and send them back with $.filefs()."},{"role":"assistant","content":"`FILESTORAGE(name)` is a file store: files are saved under an id (use `UID()`) and served back by that id. Receive the upload with `@upload`, save it with `file.fs(storage, id, callback)` and answer downloads with `$.filefs(storage, id)`, which sends the stored file with its `content-type`.\n\nThe storage name comes from the config file, so it can change per environment.\n\n```text config\nfs : files\n```\n\n```js controllers/files.js\nexports.install = function() {\n\tROUTE('POST /upload/ @upload <5mb', upload);\n\tROUTE('FILE /downloads/*.*', download);\n};\n\nfunction upload($) {\n\n\tvar file = $.files[0];\n\n\tif (!file) {\n\t\t$.invalid('No file');\n\t\treturn;\n\t}\n\n\tvar id = UID();\n\n\tfile.fs(CONF.fs, id, function(err) {\n\t\tif (err)\n\t\t\t$.invalid(err);\n\t\telse\n\t\t\t$.json({ id: id, url: '/downloads/' + id + '.' + file.extension, filename: file.filename, size: file.size });\n\t});\n}\n\nfunction download($) {\n\t// /downloads/<id>.<extension>\n\tvar name = $.split[1];\n\tvar id = name.substring(0, name.lastIndexOf('.'));\n\t$.filefs(CONF.fs, id);\n}\n```"}],"knowledge":{"content_sha256":"6cff4aa6f8c4f6223f79ebf823911d6bce9231c2d2cab8e006db84ea9817ae7b","source_sha256":"76710ced2f5226aa537ea66d6d455223325ad93666d4beba7a760601733b04bd","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["CONF","FILE","FILESTORAGE","POST","ROUTE","UID"],"language":"en"},"files":[{"path":"config","language":"text","content":"fs : files"},{"path":"controllers/files.js","language":"js","content":"exports.install = function() {\n\tROUTE('POST /upload/ @upload <5mb', upload);\n\tROUTE('FILE /downloads/*.*', download);\n};\n\nfunction upload($) {\n\n\tvar file = $.files[0];\n\n\tif (!file) {\n\t\t$.invalid('No file');\n\t\treturn;\n\t}\n\n\tvar id = UID();\n\n\tfile.fs(CONF.fs, id, function(err) {\n\t\tif (err)\n\t\t\t$.invalid(err);\n\t\telse\n\t\t\t$.json({ id: id, url: '/downloads/' + id + '.' + file.extension, filename: file.filename, size: file.size });\n\t});\n}\n\nfunction download($) {\n\t// /downloads/<id>.<extension>\n\tvar name = $.split[1];\n\tvar id = name.substring(0, name.lastIndexOf('.'));\n\t$.filefs(CONF.fs, id);\n}"}],"test":"const form = new FormData();\nform.append('file', new Blob(['stored content'], { type: 'text/plain' }), 'note.txt');\n\nconst saved = await post('/upload/', form);\nassert(saved.status === 200 && saved.json && saved.json.id, 'the file is stored, got: ' + saved.status + ' ' + saved.body);\nassert(saved.json && saved.json.url.endsWith('.txt'), 'the url has the extension, got: ' + (saved.json && saved.json.url));\n\nconst downloaded = await get(saved.json.url);\nassert(downloaded.status === 200 && downloaded.body === 'stored content', 'the file is served back by id, got: ' + downloaded.status + ' ' + downloaded.body);\nassert((downloaded.headers.get('content-type') || '').startsWith('text/plain'), 'content type is kept, got: ' + downloaded.headers.get('content-type'));\n\nconst missing = await get('/downloads/unknown.txt');\nassert(missing.status === 404, 'an unknown id is a 404, got: ' + missing.status);","test_kind":"http"}
{"id":"total5/views-and-layouts","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/views-and-layouts/","markdown_url":"https://forge.totaljs.es/en/total5/views-and-layouts.md","tags":["views","layout","templates"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"f8d05b0e9be01176f057c0a7c90f3ce7644672e27e3fe615e1d6567a38539c83","checked_at":"2026-10-20T12:19:05.307Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Render a view with a layout","description":"Render an HTML view with a model, loop over data and pass values to the layout.","messages":[{"role":"user","content":"Render a view with a layout\n\nRender an HTML view with a model, loop over data and pass values to the layout."},{"role":"assistant","content":"`$.view(name, model)` renders `views/<name>.html` and wraps it in `views/layout.html`. Inside a view the data is available as `model`; use `@{...}` for expressions, `@{foreach ... in ...}` to loop and `@{body}` in the layout to place the view.\n\nThe layout is rendered without the model. To give it values (a page title, for example) use `repository`: the object returned by `$.view()` exposes it.\n\n```html views/layout.html\n<!DOCTYPE html>\n<html>\n<head><title>@{repository.title}</title></head>\n<body>\n@{body}\n</body>\n</html>\n```\n\n```html views/index.html\n<h1>@{model.name}</h1>\n<ul>\n@{foreach item in model.items}\n\t<li>@{item}</li>\n@{end}\n</ul>\n```\n\n```js controllers/default.js\nexports.install = function() {\n\tROUTE('GET /', home);\n};\n\nfunction home($) {\n\tvar view = $.view('index', { name: 'Forge', items: ['routes', 'actions'] });\n\tview.repository.title = 'Home';\n}\n```"}],"knowledge":{"content_sha256":"3f9754de17a1ccea82afe76a0a86a5df3fbd28ef19fa8d9d1d2b04559ee301c1","source_sha256":"f8d05b0e9be01176f057c0a7c90f3ce7644672e27e3fe615e1d6567a38539c83","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["DOCTYPE","GET","ROUTE"],"language":"en"},"files":[{"path":"views/layout.html","language":"html","content":"<!DOCTYPE html>\n<html>\n<head><title>@{repository.title}</title></head>\n<body>\n@{body}\n</body>\n</html>"},{"path":"views/index.html","language":"html","content":"<h1>@{model.name}</h1>\n<ul>\n@{foreach item in model.items}\n\t<li>@{item}</li>\n@{end}\n</ul>"},{"path":"controllers/default.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /', home);\n};\n\nfunction home($) {\n\tvar view = $.view('index', { name: 'Forge', items: ['routes', 'actions'] });\n\tview.repository.title = 'Home';\n}"}],"test":"const page = await get('/');\nassert(page.status === 200, 'page renders');\nassert(page.body.includes('<title>Home</title>'), 'layout receives the title from repository');\nassert(page.body.includes('<h1>Forge</h1>'), 'view receives the model');\nassert(page.body.includes('<li>actions</li>'), 'foreach renders every item');","test_kind":"http"}
{"id":"total5/view-conditions-and-escaping","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/view-conditions-and-escaping/","markdown_url":"https://forge.totaljs.es/en/total5/view-conditions-and-escaping.md","tags":["views","templates"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"343a64d612271d3298b027ae3c4e7f1fa7ad5b3b45b4d49ab1537131d3418085","checked_at":"2026-10-20T12:19:00.967Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Use conditions and escaping in views","description":"Branch with @{if}, @{else} and @{fi}, and choose between escaped output @{value} and raw HTML @{!value}.","messages":[{"role":"user","content":"Use conditions and escaping in views\n\nBranch with @{if}, @{else} and @{fi}, and choose between escaped output @{value} and raw HTML @{!value}."},{"role":"assistant","content":"Views are HTML with `@{...}` blocks. A condition is `@{if expression}`, optionally `@{else}`, and always closed with `@{fi}`. The expression is plain JavaScript and can use `model` (the data passed to `$.view()`), `repository` (values shared with the layout), `query`, `user`, `config` and `language`.\n\nOutput is **escaped by default**: `@{model.text}` turns `<` into `&lt;`, which protects you from injection. Put a `!` after `@{` to send the raw value (`@{!model.html}`) and do it only with content you trust.\n\n```html views/index.html\n@{layout('')}\n@{if model.admin}\n\t<p class=\"role\">Administrator</p>\n@{else}\n\t<p class=\"role\">Guest</p>\n@{fi}\n@{if model.items.length > 2}\n\t<p class=\"many\">many items</p>\n@{fi}\n<div class=\"escaped\">@{model.text}</div>\n<div class=\"raw\">@{!model.text}</div>\n```\n\n```js controllers/default.js\nexports.install = function() {\n\tROUTE('GET /{role}/', page);\n};\n\nfunction page($) {\n\t$.view('index', { admin: $.params.role === 'admin', items: [1, 2, 3], text: 'Hello <b>World</b>' });\n}\n```"}],"knowledge":{"content_sha256":"90896da172ea4470dd44d3030fece11a2f2328706f1f1a43bb7c6536bf95f7e2","source_sha256":"343a64d612271d3298b027ae3c4e7f1fa7ad5b3b45b4d49ab1537131d3418085","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["GET","HTML","ROUTE"],"language":"en"},"files":[{"path":"views/index.html","language":"html","content":"@{layout('')}\n@{if model.admin}\n\t<p class=\"role\">Administrator</p>\n@{else}\n\t<p class=\"role\">Guest</p>\n@{fi}\n@{if model.items.length > 2}\n\t<p class=\"many\">many items</p>\n@{fi}\n<div class=\"escaped\">@{model.text}</div>\n<div class=\"raw\">@{!model.text}</div>"},{"path":"controllers/default.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /{role}/', page);\n};\n\nfunction page($) {\n\t$.view('index', { admin: $.params.role === 'admin', items: [1, 2, 3], text: 'Hello <b>World</b>' });\n}"}],"test":"const admin = await get('/admin/');\nassert(admin.body.includes('Administrator') && !admin.body.includes('Guest'), 'the if branch, got: ' + admin.body);\nassert(admin.body.includes('class=\"many\"'), 'a condition with an expression');\n\nconst guest = await get('/guest/');\nassert(guest.body.includes('Guest') && !guest.body.includes('Administrator'), 'the else branch, got: ' + guest.body);\n\nassert(admin.body.includes('<div class=\"escaped\">Hello &lt;b&gt;World&lt;/b&gt;</div>'), 'output is escaped, got: ' + admin.body);\nassert(admin.body.includes('<div class=\"raw\">Hello <b>World</b></div>'), 'the ! prefix prints raw HTML');","test_kind":"http"}
{"id":"total5/view-loops","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/view-loops/","markdown_url":"https://forge.totaljs.es/en/total5/view-loops.md","tags":["views","templates"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"b69b8595f1d19f29c090a4ed14f88c05a9a0f7ce059de255cdf38d2b08db3017","checked_at":"2026-10-20T12:19:03.139Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Loop over arrays and objects in views","description":"Repeat markup with @{foreach} for arrays and objects, and use the index of the current item.","messages":[{"role":"user","content":"Loop over arrays and objects in views\n\nRepeat markup with @{foreach} for arrays and objects, and use the index of the current item."},{"role":"assistant","content":"`@{foreach item in expression}` repeats the block for every element of an array, closing with `@{end}`. Inside the loop `index` is the position of the current element (starting at 0). The same syntax iterates the properties of an object: every element has a `key` and a `value`.\n\n```html views/index.html\n@{layout('')}\n<ul class=\"products\">\n@{foreach product in model.products}\n\t<li>@{index}: @{product.name} - @{product.price}</li>\n@{end}\n</ul>\n<dl class=\"fields\">\n@{foreach field in model.meta}\n\t<dt>@{field.key}</dt><dd>@{field.value}</dd>\n@{end}\n</dl>\n```\n\n```js controllers/default.js\nexports.install = function() {\n\tROUTE('GET /', page);\n};\n\nfunction page($) {\n\t$.view('index', {\n\t\tproducts: [{ name: 'Book', price: 12 }, { name: 'Pen', price: 3 }, { name: 'Lamp', price: 40 }],\n\t\tmeta: { color: 'red', size: 'M' }\n\t});\n}\n```"}],"knowledge":{"content_sha256":"adab60f6b0585cd9ac35414a22c15ca854687debd2d504904f69cd77d07e4aff","source_sha256":"b69b8595f1d19f29c090a4ed14f88c05a9a0f7ce059de255cdf38d2b08db3017","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["GET","ROUTE"],"language":"en"},"files":[{"path":"views/index.html","language":"html","content":"@{layout('')}\n<ul class=\"products\">\n@{foreach product in model.products}\n\t<li>@{index}: @{product.name} - @{product.price}</li>\n@{end}\n</ul>\n<dl class=\"fields\">\n@{foreach field in model.meta}\n\t<dt>@{field.key}</dt><dd>@{field.value}</dd>\n@{end}\n</dl>"},{"path":"controllers/default.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /', page);\n};\n\nfunction page($) {\n\t$.view('index', {\n\t\tproducts: [{ name: 'Book', price: 12 }, { name: 'Pen', price: 3 }, { name: 'Lamp', price: 40 }],\n\t\tmeta: { color: 'red', size: 'M' }\n\t});\n}"}],"test":"const page = await get('/');\nassert(page.body.includes('<li>0: Book - 12</li>') && page.body.includes('<li>2: Lamp - 40</li>'), 'array loop with index, got: ' + page.body);\nassert(page.body.includes('<dt>color</dt><dd>red</dd>') && page.body.includes('<dt>size</dt><dd>M</dd>'), 'object loop, got: ' + page.body);","test_kind":"http"}
{"id":"total5/view-includes-and-assets","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/view-includes-and-assets/","markdown_url":"https://forge.totaljs.es/en/total5/view-includes-and-assets.md","tags":["views","templates","assets"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"e41da130a9bf6c47383e5dd4ce2203f23a8a3b686a4fc3a6fc68a74ad0e75353","checked_at":"2026-10-20T12:19:02.419Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Include views and assets","description":"Reuse markup with @{view()} and add scripts and styles with @{import()}.","messages":[{"role":"user","content":"Include views and assets\n\nReuse markup with @{view()} and add scripts and styles with @{import()}."},{"role":"assistant","content":"`@{view(name, model)}` renders another view in place, so repeated pieces (a card, a row, a footer) live in one file. The second argument is the model of the included view.\n\n`@{import('a.js', 'a.css')}` writes the `<script>` and `<link>` tags for files in `public/js/` and `public/css/`. List several files joined with `+` to get one tag per file.\n\n```html views/card.html\n<article class=\"card\"><h2>@{model.title}</h2></article>\n```\n\n```html views/index.html\n@{layout('')}\n@{import('app.js', 'app.css')}\n@{foreach item in model.items}\n\t@{view('card', item)}\n@{end}\n```\n\n```js controllers/default.js\nexports.install = function() {\n\tROUTE('GET /', page);\n};\n\nfunction page($) {\n\t$.view('index', { items: [{ title: 'First' }, { title: 'Second' }] });\n}\n```"}],"knowledge":{"content_sha256":"b4ee4be42f5d663ec94a6951b8a6f6ee0f7e19eb6fa93a8b0274e4cbfbee6246","source_sha256":"e41da130a9bf6c47383e5dd4ce2203f23a8a3b686a4fc3a6fc68a74ad0e75353","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["GET","ROUTE"],"language":"en"},"files":[{"path":"views/card.html","language":"html","content":"<article class=\"card\"><h2>@{model.title}</h2></article>"},{"path":"views/index.html","language":"html","content":"@{layout('')}\n@{import('app.js', 'app.css')}\n@{foreach item in model.items}\n\t@{view('card', item)}\n@{end}"},{"path":"controllers/default.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /', page);\n};\n\nfunction page($) {\n\t$.view('index', { items: [{ title: 'First' }, { title: 'Second' }] });\n}"}],"test":"const page = await get('/');\nassert(page.body.includes('<article class=\"card\"><h2>First</h2></article>') && page.body.includes('<h2>Second</h2>'), 'the included view is rendered for each item, got: ' + page.body);\nassert(page.body.includes('<script src=\"/js/app.js\"></script>'), 'import writes the script tag, got: ' + page.body);\nassert(page.body.includes('<link rel=\"stylesheet\" href=\"/css/app.css\" />'), 'import writes the link tag');","test_kind":"http"}
{"id":"total5/view-sections","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/view-sections/","markdown_url":"https://forge.totaljs.es/en/total5/view-sections.md","tags":["views","layout","templates"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"fbc42542a056beb395a3096593219b44083b448a7a854f962d4c87268f591f0c","checked_at":"2026-10-20T12:19:03.860Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Fill sections of the layout from a view","description":"Declare a section in a view with @{section} and print it in the layout with @{section('name')}, and pick another layout per request.","messages":[{"role":"user","content":"Fill sections of the layout from a view\n\nDeclare a section in a view with @{section} and print it in the layout with @{section('name')}, and pick another layout per request."},{"role":"assistant","content":"A view can send markup to a named place of the layout. In the view write `@{section name}...@{end}`; in the layout print it with `@{section('name')}`. This is how a page adds its own scripts or footer without the layout knowing about it.\n\nTo use a different layout for a request call `$.layout('name')` before `$.view()` (it renders `views/name.html` as the layout).\n\n```html views/layout.html\n<html><body>@{body}<footer>@{section('footer')}</footer></body></html>\n```\n\n```html views/layout-wide.html\n<html><body class=\"wide\">@{body}</body></html>\n```\n\n```html views/index.html\n@{section footer}\n\t<span>Footer from the view</span>\n@{end}\n<h1>Home</h1>\n```\n\n```js controllers/default.js\nexports.install = function() {\n\tROUTE('GET /', home);\n\tROUTE('GET /wide/', wide);\n};\n\nfunction home($) {\n\t$.view('index');\n}\n\nfunction wide($) {\n\t$.layout('layout-wide');\n\t$.view('index');\n}\n```"}],"knowledge":{"content_sha256":"7141532df90da0c4a7379b60398902258ee91cf9be67a8060622d2fb6d40b7c0","source_sha256":"fbc42542a056beb395a3096593219b44083b448a7a854f962d4c87268f591f0c","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["GET","ROUTE"],"language":"en"},"files":[{"path":"views/layout.html","language":"html","content":"<html><body>@{body}<footer>@{section('footer')}</footer></body></html>"},{"path":"views/layout-wide.html","language":"html","content":"<html><body class=\"wide\">@{body}</body></html>"},{"path":"views/index.html","language":"html","content":"@{section footer}\n\t<span>Footer from the view</span>\n@{end}\n<h1>Home</h1>"},{"path":"controllers/default.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /', home);\n\tROUTE('GET /wide/', wide);\n};\n\nfunction home($) {\n\t$.view('index');\n}\n\nfunction wide($) {\n\t$.layout('layout-wide');\n\t$.view('index');\n}"}],"test":"const home = await get('/');\nassert(home.body.includes('<h1>Home</h1>') && /<footer>\\s*<span>Footer from the view<\\/span>\\s*<\\/footer>/.test(home.body), 'the section is printed in the layout, got: ' + home.body);\n\nconst wide = await get('/wide/');\nassert(wide.body.includes('<body class=\"wide\">') && !wide.body.includes('Footer from the view'), 'another layout is used, got: ' + wide.body);","test_kind":"http"}
{"id":"total5/view-helpers-and-config","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/view-helpers-and-config/","markdown_url":"https://forge.totaljs.es/en/total5/view-helpers-and-config.md","tags":["views","templates","config"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"b25d7a305b80cb435592def790e925b587533e44a1a3fbdbc153bac69e1a0961","checked_at":"2026-10-20T12:19:01.694Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Add view helpers and read the config in views","description":"Register functions in DEF.helpers to call them from views, and read config values with @{'%key'}.","messages":[{"role":"user","content":"Add view helpers and read the config in views\n\nRegister functions in DEF.helpers to call them from views, and read config values with @{'%key'}."},{"role":"assistant","content":"Functions registered in `DEF.helpers` are available in every view as `helpers.name(...)`. Use them for formatting that you repeat (prices, dates, labels).\n\nSettings from the `config` file are printed with `@{'%key'}`, for example `@{'%name'}` for the `name` setting, so the view does not need access to `CONF`.\n\n```text config\nname : Forge shop\n```\n\n```js definitions/helpers.js\nDEF.helpers.price = function(value) {\n\treturn '$' + value.toFixed(2);\n};\n```\n\n```html views/index.html\n@{layout('')}\n<h1>@{'%name'}</h1>\n<p class=\"price\">@{helpers.price(model.price)}</p>\n```\n\n```js controllers/default.js\nexports.install = function() {\n\tROUTE('GET /', page);\n};\n\nfunction page($) {\n\t$.view('index', { price: 9.5 });\n}\n```"}],"knowledge":{"content_sha256":"21035b147dccc83278a6a3e3d128e697c065d93638b9972b1cd8fb9d7b7c6583","source_sha256":"b25d7a305b80cb435592def790e925b587533e44a1a3fbdbc153bac69e1a0961","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["CONF","DEF","GET","ROUTE"],"language":"en"},"files":[{"path":"config","language":"text","content":"name : Forge shop"},{"path":"definitions/helpers.js","language":"js","content":"DEF.helpers.price = function(value) {\n\treturn '$' + value.toFixed(2);\n};"},{"path":"views/index.html","language":"html","content":"@{layout('')}\n<h1>@{'%name'}</h1>\n<p class=\"price\">@{helpers.price(model.price)}</p>"},{"path":"controllers/default.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /', page);\n};\n\nfunction page($) {\n\t$.view('index', { price: 9.5 });\n}"}],"test":"const page = await get('/');\nassert(page.body.includes('<h1>Forge shop</h1>'), 'the config value is printed, got: ' + page.body);\nassert(page.body.includes('<p class=\"price\">$9.50</p>'), 'the helper is called, got: ' + page.body);","test_kind":"http"}
{"id":"total5/markdown","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/markdown/","markdown_url":"https://forge.totaljs.es/en/total5/markdown.md","tags":["content","markdown"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"0af84b2f2dd8450d5dd95d1f39ae25b630b6c3e733cc2bae474863b7973da92e","checked_at":"2026-10-20T12:18:23.475Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Convert Markdown to HTML","description":"Use the String.markdown() prototype to render Markdown text into HTML, for content stored by users or in files.","messages":[{"role":"user","content":"Convert Markdown to HTML\n\nUse the String.markdown() prototype to render Markdown text into HTML, for content stored by users or in files."},{"role":"assistant","content":"Total.js adds `markdown()` to strings. It turns headlines, emphasis, lists, links, code and tables into HTML and escapes raw HTML tags in the text. Pass an options object to choose what is converted; the useful ones are `ul`, `links`, `formatting`, `headlines`, `code`, `tables` and `linetag` (the tag used for paragraphs).\n\n```js controllers/content.js\nexports.install = function() {\n\tROUTE('POST /markdown/', render);\n};\n\nfunction render($) {\n\tvar html = ($.body.text || '').markdown({ linetag: 'p', ul: true, links: true, formatting: true, headlines: true, code: true });\n\t$.html(html);\n}\n```"}],"knowledge":{"content_sha256":"885d1a76f84bdf5cea7d15fd5a85731160d087f50ccb2d047bac6313fcd84bf0","source_sha256":"0af84b2f2dd8450d5dd95d1f39ae25b630b6c3e733cc2bae474863b7973da92e","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["HTML","POST","ROUTE","Total.js"],"language":"en"},"files":[{"path":"controllers/content.js","language":"js","content":"exports.install = function() {\n\tROUTE('POST /markdown/', render);\n};\n\nfunction render($) {\n\tvar html = ($.body.text || '').markdown({ linetag: 'p', ul: true, links: true, formatting: true, headlines: true, code: true });\n\t$.html(html);\n}"}],"test":"const text = '# Title\\n\\nSome **bold** and `code`.\\n\\n- one\\n- two\\n\\n<script>alert(1)</script>';\nconst res = await post('/markdown/', { text });\nassert(res.status === 200, 'it renders, got: ' + res.status);\nassert(/<h1[^>]*>Title<\\/h1>/.test(res.body), 'a headline becomes <h1>, got: ' + res.body);\nassert(res.body.includes('<b>bold</b>') || res.body.includes('<strong>bold</strong>'), 'bold, got: ' + res.body);\nassert(res.body.includes('<code>code</code>'), 'inline code');\nassert(res.body.includes('<li') && res.body.includes('one'), 'list items');\nassert(!res.body.includes('<script>'), 'raw HTML in the text is escaped, got: ' + res.body);","test_kind":"http"}
{"id":"total5/tangular-templates","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/tangular-templates/","markdown_url":"https://forge.totaljs.es/en/total5/tangular-templates.md","tags":["templates","content"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"5412caddf6668397264f0ce239d432a0e3412b816a0ce6e9f12a45609ef68b5a","checked_at":"2026-10-20T12:18:50.392Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Render text with Tangular templates","description":"Use Tangular.render() to fill templates for e-mails, documents and messages with conditions, loops and helpers.","messages":[{"role":"user","content":"Render text with Tangular templates\n\nUse Tangular.render() to fill templates for e-mails, documents and messages with conditions, loops and helpers."},{"role":"assistant","content":"Tangular is the small template language of Total.js (it also runs in the browser with jComponent). Write `{{ value }}` to print an escaped value, `{{ if expression }}...{{ fi }}` for conditions and `{{ foreach item in list }}...{{ end }}` for loops. `Tangular.render(template, model)` returns the text; use `Tangular.compile(template)` once and call the result many times for hot paths.\n\nVariables are read from the model, so `{{ user.name }}` reads `model.user.name`. Output is HTML-escaped; add the `raw` helper (`{{ html | raw }}`) only for trusted content.\n\n```js controllers/mail.js\nexports.install = function() {\n\tROUTE('POST /preview/', preview);\n};\n\nvar template = 'Hello {{ name }}!{{ if vip }} Thanks for being a VIP.{{ fi }}\\n{{ foreach item in items }}- {{ item.title }}\\n{{ end }}';\n\nfunction preview($) {\n\t$.plain(Tangular.render(template, $.body));\n}\n```"}],"knowledge":{"content_sha256":"a9ede48ee69cee4e1176df474051b6296e98174aab3a8d9a816017d0bf6e43e1","source_sha256":"5412caddf6668397264f0ce239d432a0e3412b816a0ce6e9f12a45609ef68b5a","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["HTML","POST","ROUTE","Tangular.compile","Tangular.render","Total.js","VIP"],"language":"en"},"files":[{"path":"controllers/mail.js","language":"js","content":"exports.install = function() {\n\tROUTE('POST /preview/', preview);\n};\n\nvar template = 'Hello {{ name }}!{{ if vip }} Thanks for being a VIP.{{ fi }}\\n{{ foreach item in items }}- {{ item.title }}\\n{{ end }}';\n\nfunction preview($) {\n\t$.plain(Tangular.render(template, $.body));\n}"}],"test":"const res = await post('/preview/', { name: 'Peter <b>', vip: true, items: [{ title: 'Book' }, { title: 'Pen' }] });\nassert(res.status === 200, 'it renders, got: ' + res.status + ' ' + res.body);\nassert(res.body.startsWith('Hello Peter &lt;b&gt;!'), 'values are escaped, got: ' + res.body);\nassert(res.body.includes('Thanks for being a VIP.'), 'the condition is true');\nassert(res.body.includes('- Book') && res.body.includes('- Pen'), 'the loop renders every item, got: ' + res.body);\n\nconst guest = await post('/preview/', { name: 'Anna', vip: false, items: [] });\nassert(!guest.body.includes('VIP'), 'the condition is false, got: ' + guest.body);","test_kind":"http"}
{"id":"total5/template-files","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/template-files/","markdown_url":"https://forge.totaljs.es/en/total5/template-files.md","tags":["templates","content","tangular"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"8a315e0825810e25bbb3893a2309e9b441f78865bd56a941d303cb64cb1ae224","checked_at":"2026-10-20T12:18:51.120Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Render templates from text, files and URLs with TEMPLATE","description":"Use TEMPLATE() to render a Tangular template that is a text, a file of the templates folder or a URL, with its own helpers and a default model.","messages":[{"role":"user","content":"Render templates from text, files and URLs with TEMPLATE\n\nUse TEMPLATE() to render a Tangular template that is a text, a file of the templates folder or a URL, with its own helpers and a default model."},{"role":"assistant","content":"`TEMPLATE(source, model, [$])` renders a Tangular template and returns a **promise** with the text. The `source` can be:\n\n- a text that contains `{{ ... }}`: it is the template itself\n- the name of a file of the `templates` folder: `TEMPLATE('invoice', model)` reads `templates/invoice.html` (a `~` at the start makes the name an absolute path)\n- an `http://` or `https://` address, which the source of `total5` downloads (not checked on this page)\n\n`Ta` is another name of `Tangular`. Unlike `Tangular.render()`, the model is available as **`value`**: write `{{ value.name }}`. A template file can also carry two blocks that `TEMPLATE` reads and removes from the output: a `<script>` that adds helpers to `Thelpers` and a `<script type=\"text/json\">` with the default model, which is used when no model is passed.\n\n```html templates/invoice.html\n<script>\n\tThelpers.money = function(value) {\n\t\treturn (value || 0).toFixed(2) + ' EUR';\n\t};\n</script>\n<script type=\"text/json\">{ \"customer\": \"Anonymous\", \"total\": 0 }</script>\n<h1>Invoice for {{ value.customer }}</h1>\n<p>Total: {{ value.total | money() }}</p>\n```\n\n```js controllers/documents.js\nexports.install = function() {\n\tROUTE('GET /text/', text);\n\tROUTE('GET /file/', file);\n\tROUTE('GET /default/', withdefault);\n\tROUTE('GET /absolute/', absolute);\n\tROUTE('GET /alias/', alias);\n};\n\nasync function text($) {\n\t$.plain(await TEMPLATE('Hello {{ value.name }}!', { name: $.query.name || 'world' }));\n}\n\nasync function file($) {\n\t$.plain(await TEMPLATE('invoice', { customer: 'Ana', total: 12.5 }));\n}\n\nfunction alias($) {\n\t$.json({ same: Ta === Tangular, rendered: Ta.render('{{ name }}', { name: 'x' }) });\n}\n\nasync function absolute($) {\n\tconst file = require('path').join(__dirname, '..', 'templates', 'invoice.html');\n\t$.plain(await TEMPLATE('~' + file, { customer: 'Eva', total: 1 }));\n}\n\nasync function withdefault($) {\n\t$.plain(await TEMPLATE('invoice'));\n}\n```"}],"knowledge":{"content_sha256":"7d7a663bd832f60cf0afe8b1bebf2b069ca53cc69f36fdcd1231553574746d28","source_sha256":"8a315e0825810e25bbb3893a2309e9b441f78865bd56a941d303cb64cb1ae224","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["EUR","GET","ROUTE","TEMPLATE","Tangular.render"],"language":"en"},"files":[{"path":"templates/invoice.html","language":"html","content":"<script>\n\tThelpers.money = function(value) {\n\t\treturn (value || 0).toFixed(2) + ' EUR';\n\t};\n</script>\n<script type=\"text/json\">{ \"customer\": \"Anonymous\", \"total\": 0 }</script>\n<h1>Invoice for {{ value.customer }}</h1>\n<p>Total: {{ value.total | money() }}</p>"},{"path":"controllers/documents.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /text/', text);\n\tROUTE('GET /file/', file);\n\tROUTE('GET /default/', withdefault);\n\tROUTE('GET /absolute/', absolute);\n\tROUTE('GET /alias/', alias);\n};\n\nasync function text($) {\n\t$.plain(await TEMPLATE('Hello {{ value.name }}!', { name: $.query.name || 'world' }));\n}\n\nasync function file($) {\n\t$.plain(await TEMPLATE('invoice', { customer: 'Ana', total: 12.5 }));\n}\n\nfunction alias($) {\n\t$.json({ same: Ta === Tangular, rendered: Ta.render('{{ name }}', { name: 'x' }) });\n}\n\nasync function absolute($) {\n\tconst file = require('path').join(__dirname, '..', 'templates', 'invoice.html');\n\t$.plain(await TEMPLATE('~' + file, { customer: 'Eva', total: 1 }));\n}\n\nasync function withdefault($) {\n\t$.plain(await TEMPLATE('invoice'));\n}"}],"test":"const text = await get('/text/?name=Peter');\nassert(text.body === 'Hello Peter!', 'a text is the template, got: ' + text.body);\n\nconst file = await get('/file/');\nassert(file.body.includes('<h1>Invoice for Ana</h1>'), 'a name reads templates/invoice.html, got: ' + file.body);\nassert(file.body.includes('Total: 12.50 EUR'), 'the helpers of the script block are available, got: ' + file.body);\nassert(!file.body.includes('Thelpers') && !file.body.includes('text/json'), 'the helper and model blocks are removed from the output, got: ' + file.body);\n\nconst alias = await get('/alias/');\nassert(alias.json.same === true && alias.json.rendered === 'x', 'Ta is Tangular, got: ' + alias.body);\n\nconst abs = await get('/absolute/');\nassert(abs.body.includes('Invoice for Eva'), 'a name that starts with ~ is an absolute path, got: ' + abs.body);\n\nconst def = await get('/default/');\nassert(def.body.includes('Invoice for Anonymous') && def.body.includes('Total: 0.00 EUR'), 'the default model is used without a model, got: ' + def.body);","test_kind":"http"}
{"id":"total5/diffarr-sync-lists","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/diffarr-sync-lists/","markdown_url":"https://forge.totaljs.es/en/total5/diffarr-sync-lists.md","tags":["utilities","arrays","forms"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"ee5803e155620ea770c9028c486d81f152188e50dcd4c0464ef8c39641fe1b6a","checked_at":"2026-10-20T12:18:01.204Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Compare two lists with DIFFARR: what to add, update and remove","description":"Use DIFFARR() to compare the items saved in the database with the items of a form and get the ones to add, to update and to remove.","messages":[{"role":"user","content":"Compare two lists with DIFFARR: what to add, update and remove\n\nUse DIFFARR() to compare the items saved in the database with the items of a form and get the ones to add, to update and to remove."},{"role":"assistant","content":"`DIFFARR(property, saved, submitted)` compares two arrays of objects by one property and returns an object with three lists:\n\n- `add`: the objects of `submitted` whose property is **not** in `saved`\n- `upd`: for the objects that are in both, `{ db, form }` with the saved object and the submitted one\n- `rem`: the **values of the property** (not the objects) of the items of `saved` that are not in `submitted`\n\nIt is the usual step of saving an editable list: the form sends the whole list and you need to know what was created, what changed and what was deleted. The values are compared with `==`, so `1` and `'1'` are the same item.\n\n```js controllers/lists.js\nconst saved = [\n\t{ id: 1, name: 'Book' },\n\t{ id: 2, name: 'Pen' },\n\t{ id: 3, name: 'Lamp' }\n];\n\nexports.install = function() {\n\tROUTE('POST /sync/', sync);\n};\n\nfunction sync($) {\n\tconst diff = DIFFARR('id', saved, $.body.items);\n\t$.json({\n\t\tadd: diff.add,\n\t\tupdate: diff.upd.map(n => ({ id: n.db.id, from: n.db.name, to: n.form.name })),\n\t\tremove: diff.rem\n\t});\n}\n```"}],"knowledge":{"content_sha256":"a12236697023df4617897c5ec8a240897c9b3ae8fe6c8ec55b1717c1d1f9bdaf","source_sha256":"ee5803e155620ea770c9028c486d81f152188e50dcd4c0464ef8c39641fe1b6a","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["DIFFARR","POST","ROUTE"],"language":"en"},"files":[{"path":"controllers/lists.js","language":"js","content":"const saved = [\n\t{ id: 1, name: 'Book' },\n\t{ id: 2, name: 'Pen' },\n\t{ id: 3, name: 'Lamp' }\n];\n\nexports.install = function() {\n\tROUTE('POST /sync/', sync);\n};\n\nfunction sync($) {\n\tconst diff = DIFFARR('id', saved, $.body.items);\n\t$.json({\n\t\tadd: diff.add,\n\t\tupdate: diff.upd.map(n => ({ id: n.db.id, from: n.db.name, to: n.form.name })),\n\t\tremove: diff.rem\n\t});\n}"}],"test":"const res = await post('/sync/', { items: [{ id: 1, name: 'Book' }, { id: '2', name: 'Pencil' }, { id: 4, name: 'Desk' }] });\nassert(res.status === 200, 'it answers, got: ' + res.status + ' ' + res.body);\nassert(res.json.add.length === 1 && res.json.add[0].id === 4, 'add has the new item, got: ' + JSON.stringify(res.json.add));\nassert(res.json.remove.length === 1 && res.json.remove[0] === 3, 'rem has the values of the removed ids, got: ' + JSON.stringify(res.json.remove));\nassert(res.json.update.length === 2, 'upd has the items that are in both, got: ' + JSON.stringify(res.json.update));\nconst pen = res.json.update.find(n => n.id === 2);\nassert(pen && pen.from === 'Pen' && pen.to === 'Pencil', 'upd pairs the saved and the submitted object, and 2 == \"2\", got: ' + JSON.stringify(pen));","test_kind":"http"}
{"id":"total5/query-builder-db","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/query-builder-db/","markdown_url":"https://forge.totaljs.es/en/total5/query-builder-db.md","tags":["database","querybuilder"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"71d280d625418867fc26d6446c72e13f72d0ad843023abf857b94393ee970d5a","checked_at":"2026-10-20T12:18:36.484Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Query builder: DB() and NEWDB() with your own database driver","description":"Build queries with DB().find().where().sort() and let a driver registered with NEWDB() run them, while a registered driver implements supported database operations.","messages":[{"role":"user","content":"Query builder: DB() and NEWDB() with your own database driver\n\nBuild queries with DB().find().where().sort() and let a driver registered with NEWDB() run them, while a registered driver implements supported database operations."},{"role":"assistant","content":"`DB()` builds a query without knowing which database will run it, and `NEWDB()` registers the **driver** that runs it. `total5` itself has no SQL driver: the query builder collects what you ask for into an `options` object and calls your driver with it, and the driver (yours, or one that comes in a module) translates `options` into SQL, a NoSQL call or anything else.\n\n- `NEWDB(driver)` registers the default driver and `NEWDB('name', driver)` a named one. The driver is `function(options, callback)`: answer with `callback(error, response)`. A table written as `name/table` (`DB().find('audit/log')`) uses the driver `name` and the table `log`; without a prefix the driver is `default`.\n- The operations start the query: `find(table)` (an array), `read(table)` (one object, or `null` when there is none), `list(table)` (a page of an array: `response.page`, `limit` and `pages` are added to what the driver returns, so the driver should set `count`) and `insert(table, data)`. The builder also has `count`, `update`, `remove` and `query`; they work the same way (the driver gets `options.exec` with that name).\n- Refine the query with: `where(name, [comparer], value)` (`=` when there is no comparer), `fields('a,b')`, `sort(name, [true])` (`true` for descending), `take(n)` and `page(page, limit)`. Other modifiers include `id`, `in`, `skip`, `between` and `search`.\n- Finish with `.callback($)`: the result goes to the response of the action `$`, or to a function `(err, response)` that you pass instead.\n\nFor the complete native operation catalogue, upsert/error behavior and filter shapes, see [query-builder-contract](/en/total5/query-builder-contract/). For SQL CRUD, commit and rollback, see [query-builder-sqlite](/en/total5/query-builder-sqlite/). The options the driver receives are plain data. The example driver stores records in memory and exposes the last `options` object received.\n\n```js definitions/db.js\nconst users = [\n\t{ id: 1, name: 'Ann', age: 31 },\n\t{ id: 2, name: 'Bob', age: 17 },\n\t{ id: 3, name: 'Cy', age: 45 }\n];\n\nF.seen = [];\n\n// The default driver: it understands only \"age >= value\" and \"id = value\", which is all this example asks\nNEWDB(function(options, callback) {\n\n\tF.seen.push(JSON.parse(JSON.stringify(options)));\n\n\tlet rows = users.slice();\n\n\tfor (let f of options.filter) {\n\t\tif (f.type === 'where' && f.name === 'age' && f.comparer === '>=')\n\t\t\trows = rows.filter(n => n.age >= f.value);\n\t\telse if (f.type === 'where' && f.name === 'id')\n\t\t\trows = rows.filter(n => n.id === f.value);\n\t}\n\n\tswitch (options.exec) {\n\t\tcase 'find':\n\t\tcase 'read':\n\t\t\tif (options.sort && options.sort[0] === 'name_desc')\n\t\t\t\trows.sort((a, b) => a.name < b.name ? 1 : -1);\n\t\t\tif (options.take)\n\t\t\t\trows = rows.slice(0, options.take);\n\t\t\treturn callback(null, rows);\n\t\tcase 'list':\n\t\t\trows.count = rows.length;\n\t\t\treturn callback(null, rows);\n\t\tcase 'insert':\n\t\t\tusers.push(Object.assign({ id: users.length + 1 }, options.payload));\n\t\t\treturn callback(null, users[users.length - 1]);\n\t\tdefault:\n\t\t\treturn callback('The driver does not know ' + options.exec);\n\t}\n});\n\n// A second driver, used by the tables that start with \"audit/\"\nNEWDB('audit', function(options, callback) {\n\tF.seen.push({ driver: 'audit', table: options.table });\n\tcallback(null, [{ event: 'login' }]);\n});\n```\n\n```js controllers/users.js\nexports.install = function() {\n\tROUTE('GET /adults/', adults);\n\tROUTE('GET /user/{id}/', user);\n\tROUTE('POST /users/', create);\n\tROUTE('GET /page/', page);\n\tROUTE('GET /audit/', audit);\n\tROUTE('GET /seen/', seen);\n};\n\nfunction adults($) {\n\tDB().find('users').where('age', '>=', 18).fields('id,name').sort('name', true).take(5).callback($);\n}\n\nfunction user($) {\n\tDB().read('users').where('id', +$.params.id).callback($);\n}\n\nfunction create($) {\n\tDB().insert('users', { name: $.body.name, age: $.body.age }).callback($);\n}\n\nfunction page($) {\n\tDB().list('users').page(1, 2).callback(function(err, response) {\n\t\t$.json({ err: err, count: response.count, page: response.page, limit: response.limit, pages: response.pages });\n\t});\n}\n\nfunction audit($) {\n\tDB().find('audit/log').callback($);\n}\n\nfunction seen($) {\n\t$.json(F.seen);\n}\n```"}],"knowledge":{"content_sha256":"4bef065e707876cf8653fcd1df4979bebd7327aa58465f355fefd40b2d4ca631","source_sha256":"71d280d625418867fc26d6446c72e13f72d0ad843023abf857b94393ee970d5a","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["CRUD","GET","JSON","NEWDB","POST","ROUTE","SQL"],"language":"en"},"files":[{"path":"definitions/db.js","language":"js","content":"const users = [\n\t{ id: 1, name: 'Ann', age: 31 },\n\t{ id: 2, name: 'Bob', age: 17 },\n\t{ id: 3, name: 'Cy', age: 45 }\n];\n\nF.seen = [];\n\n// The default driver: it understands only \"age >= value\" and \"id = value\", which is all this example asks\nNEWDB(function(options, callback) {\n\n\tF.seen.push(JSON.parse(JSON.stringify(options)));\n\n\tlet rows = users.slice();\n\n\tfor (let f of options.filter) {\n\t\tif (f.type === 'where' && f.name === 'age' && f.comparer === '>=')\n\t\t\trows = rows.filter(n => n.age >= f.value);\n\t\telse if (f.type === 'where' && f.name === 'id')\n\t\t\trows = rows.filter(n => n.id === f.value);\n\t}\n\n\tswitch (options.exec) {\n\t\tcase 'find':\n\t\tcase 'read':\n\t\t\tif (options.sort && options.sort[0] === 'name_desc')\n\t\t\t\trows.sort((a, b) => a.name < b.name ? 1 : -1);\n\t\t\tif (options.take)\n\t\t\t\trows = rows.slice(0, options.take);\n\t\t\treturn callback(null, rows);\n\t\tcase 'list':\n\t\t\trows.count = rows.length;\n\t\t\treturn callback(null, rows);\n\t\tcase 'insert':\n\t\t\tusers.push(Object.assign({ id: users.length + 1 }, options.payload));\n\t\t\treturn callback(null, users[users.length - 1]);\n\t\tdefault:\n\t\t\treturn callback('The driver does not know ' + options.exec);\n\t}\n});\n\n// A second driver, used by the tables that start with \"audit/\"\nNEWDB('audit', function(options, callback) {\n\tF.seen.push({ driver: 'audit', table: options.table });\n\tcallback(null, [{ event: 'login' }]);\n});"},{"path":"controllers/users.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /adults/', adults);\n\tROUTE('GET /user/{id}/', user);\n\tROUTE('POST /users/', create);\n\tROUTE('GET /page/', page);\n\tROUTE('GET /audit/', audit);\n\tROUTE('GET /seen/', seen);\n};\n\nfunction adults($) {\n\tDB().find('users').where('age', '>=', 18).fields('id,name').sort('name', true).take(5).callback($);\n}\n\nfunction user($) {\n\tDB().read('users').where('id', +$.params.id).callback($);\n}\n\nfunction create($) {\n\tDB().insert('users', { name: $.body.name, age: $.body.age }).callback($);\n}\n\nfunction page($) {\n\tDB().list('users').page(1, 2).callback(function(err, response) {\n\t\t$.json({ err: err, count: response.count, page: response.page, limit: response.limit, pages: response.pages });\n\t});\n}\n\nfunction audit($) {\n\tDB().find('audit/log').callback($);\n}\n\nfunction seen($) {\n\t$.json(F.seen);\n}"}],"test":"const adults = await get('/adults/');\nassert(adults.json.length === 2 && adults.json[0].name === 'Cy' && adults.json[1].name === 'Ann', 'the driver filtered and sorted what the builder asked, got: ' + adults.body);\n\nconst user = await get('/user/2/');\nassert(user.json && user.json.name === 'Bob', 'read answers one object, got: ' + user.body);\nconst nobody = await get('/user/99/');\nassert(nobody.status === 200 && nobody.body === 'null', 'read of a missing row answers null, got: ' + nobody.status + ' ' + nobody.body);\n\nconst created = await post('/users/', { name: 'Dan', age: 20 });\nassert(created.json && created.json.name === 'Dan' && created.json.id === 4, 'insert goes to the driver with the payload, got: ' + created.body);\n\nconst page = await get('/page/');\nassert(page.json.page === 1 && page.json.limit === 2 && page.json.pages === 2 && page.json.count === 4, 'list adds page, limit and pages, got: ' + page.body);\n\nconst audit = await get('/audit/');\nassert(audit.json[0].event === 'login', 'a table with a prefix uses the driver with that name, got: ' + audit.body);\n\nconst seen = (await get('/seen/')).json;\nconst first = seen[0];\nassert(first.exec === 'find' && first.table === 'users', 'the driver receives the operation and the table, got: ' + JSON.stringify(first));\nassert(first.filter.length === 1 && first.filter[0].type === 'where' && first.filter[0].name === 'age' && first.filter[0].comparer === '>=' && first.filter[0].value === 18, 'where is in options.filter, got: ' + JSON.stringify(first.filter));\nassert(first.fields.join() === 'id,name' && first.sort.join() === 'name_desc' && first.take === 5, 'fields, sort and take are in the options, got: ' + JSON.stringify(first));\nconst insert = seen.find(n => n.exec === 'insert');\nassert(insert && insert.payload.name === 'Dan', 'insert carries the payload, got: ' + JSON.stringify(insert));\nassert(seen.find(n => n.driver === 'audit' && n.table === 'log'), 'the audit driver got the table without the prefix, got: ' + JSON.stringify(seen));","test_kind":"http"}
{"id":"total5/configuration-file","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/configuration-file/","markdown_url":"https://forge.totaljs.es/en/total5/configuration-file.md","tags":["config","settings"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"dd869106a5fdd5fb7ef6658337117815f57f180caddcad81199c26d97ebb96f2","checked_at":"2026-10-20T12:17:55.688Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Configure the app with the config file","description":"Keep settings in a config file and read them through CONF.","messages":[{"role":"user","content":"Configure the app with the config file\n\nKeep settings in a config file and read them through CONF."},{"role":"assistant","content":"The `config` file in the root of the project holds one `key : value` setting per line. At runtime the values are available in the global `CONF` object. Values are strings unless you declare a type in brackets after the key: `limit (number) : 10` or `debug (boolean) : true`.\n\n```text config\nname       : Forge demo\nlimit (number) : 10\ndebug (boolean) : true\n```\n\n```js controllers/settings.js\nexports.install = function() {\n\tROUTE('GET /settings/', settings);\n};\n\nfunction settings($) {\n\t$.json({ name: CONF.name, limit: CONF.limit, debug: CONF.debug });\n}\n```\n\nIn a view you can read the same values with `@{'%name'}`."}],"knowledge":{"content_sha256":"bd1905565ada99b1bad4e9ad56a8a762ec12643f80e1058a0b2fd82f985c920b","source_sha256":"dd869106a5fdd5fb7ef6658337117815f57f180caddcad81199c26d97ebb96f2","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["CONF","GET","ROUTE"],"language":"en"},"files":[{"path":"config","language":"text","content":"name       : Forge demo\nlimit (number) : 10\ndebug (boolean) : true"},{"path":"controllers/settings.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /settings/', settings);\n};\n\nfunction settings($) {\n\t$.json({ name: CONF.name, limit: CONF.limit, debug: CONF.debug });\n}"}],"test":"const res = await get('/settings/');\nassert(res.json.name === 'Forge demo', 'string value');\nassert(res.json.limit === 10, 'number type is applied');\nassert(res.json.debug === true, 'boolean type is applied');","test_kind":"http"}
{"id":"total5/encrypt-and-decrypt","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/encrypt-and-decrypt/","markdown_url":"https://forge.totaljs.es/en/total5/encrypt-and-decrypt.md","tags":["security","crypto"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"338f3af419d60e5e3fbd27f3134081fc0750047ed181d4e3c8dafa4f085f56ac","checked_at":"2026-10-20T12:18:01.933Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Encrypt and decrypt data with a key","description":"Turn objects into opaque tokens with ENCRYPT() and read them back with DECRYPT(), for signed links and short-lived tokens.","messages":[{"role":"user","content":"Encrypt and decrypt data with a key\n\nTurn objects into opaque tokens with ENCRYPT() and read them back with DECRYPT(), for signed links and short-lived tokens."},{"role":"assistant","content":"`ENCRYPT(value, key)` serializes a value to JSON and returns an opaque text. `DECRYPT(text, key)` returns the original value, or `null` when the text was changed, is not valid, or was encrypted with another key. The `secret` setting of your config is mixed into the key, so tokens made on one installation do not open on another.\n\nUse it for password reset links, invitation tokens and anything you need to hand to a client and verify later without storing it. Put an expiration inside the value (a timestamp) and check it after decrypting.\n\n```text config\nsecret : change-this-secret-in-production\n```\n\n```js controllers/tokens.js\nexports.install = function() {\n\tROUTE('GET /token/', create);\n\tROUTE('GET /verify/', verify);\n};\n\nfunction create($) {\n\tvar token = ENCRYPT({ userid: '123', expire: Date.now() + 60000 }, 'reset-password');\n\t$.json({ token: token });\n}\n\nfunction verify($) {\n\n\tvar data = DECRYPT($.query.token || '', 'reset-password');\n\n\tif (!data || data.expire < Date.now()) {\n\t\t$.invalid(401);\n\t\treturn;\n\t}\n\n\t$.json({ userid: data.userid });\n}\n```"}],"knowledge":{"content_sha256":"304fb571fa9f2b8a3e29e7adac76abcaedffa2c5b3c1cfb2b34d5cc169dc4be1","source_sha256":"338f3af419d60e5e3fbd27f3134081fc0750047ed181d4e3c8dafa4f085f56ac","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["DECRYPT","Date.now","ENCRYPT","GET","JSON","ROUTE"],"language":"en"},"files":[{"path":"config","language":"text","content":"secret : change-this-secret-in-production"},{"path":"controllers/tokens.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /token/', create);\n\tROUTE('GET /verify/', verify);\n};\n\nfunction create($) {\n\tvar token = ENCRYPT({ userid: '123', expire: Date.now() + 60000 }, 'reset-password');\n\t$.json({ token: token });\n}\n\nfunction verify($) {\n\n\tvar data = DECRYPT($.query.token || '', 'reset-password');\n\n\tif (!data || data.expire < Date.now()) {\n\t\t$.invalid(401);\n\t\treturn;\n\t}\n\n\t$.json({ userid: data.userid });\n}"}],"test":"const created = await get('/token/');\nassert(created.json && typeof(created.json.token) === 'string' && created.json.token.length > 10, 'a token is created, got: ' + created.body);\n\nconst token = created.json.token;\nconst valid = await get('/verify/?token=' + encodeURIComponent(token));\nassert(valid.status === 200 && valid.json.userid === '123', 'the token is read back, got: ' + valid.status + ' ' + valid.body);\n\nconst changed = await get('/verify/?token=' + encodeURIComponent(token.slice(0, -2) + 'xx'));\nassert(changed.status === 401, 'a modified token is rejected, got: ' + changed.status);\n\nconst nothing = await get('/verify/');\nassert(nothing.status === 401, 'a missing token is rejected, got: ' + nothing.status);","test_kind":"http"}
{"id":"total5/api-drivers","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/api-drivers/","markdown_url":"https://forge.totaljs.es/en/total5/api-drivers.md","tags":["api","services","integrations"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"0829924b91418bcacaeab50871bc300f29dd03cae2341ae266efe2048a83da56","checked_at":"2026-10-20T12:17:45.767Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Call external services with API() and NEWAPI() drivers","description":"Register a driver for a service with NEWAPI() and call it from actions with API(name, schema, data), with the same callback, error and promise style as the query builder.","messages":[{"role":"user","content":"Call external services with API() and NEWAPI() drivers\n\nRegister a driver for a service with NEWAPI() and call it from actions with API(name, schema, data), with the same callback, error and promise style as the query builder."},{"role":"assistant","content":"`API(name, schema, data, [$])` calls an operation of an external service and `NEWAPI()` registers the **driver** that makes the call, so the code of your actions does not depend on how the service is reached (HTTP, a library, a queue...).\n\n- `NEWAPI('name', driver)` registers the driver of the service `name` (`'a,b'` registers one driver for two names, and `NEWAPI(driver)` registers the `default` one). `NEWAPI('name', config, driver)` also stores a `config` object that every call receives as `options.config`.\n- The driver is `function(options, next)`. `options.name` is the service, `options.schema` the operation, `options.data` the data and `options.config` the configuration. Answer with `next(error, response)`.\n- `API('name', 'operation', data)` returns a builder: finish it with `.callback($)` (the response goes to the action `$`), `.callback(function(err, response) {})` or `await ....promise($)`. `.configure({ key: value })` adds to `options.config` for that call, and `.error('text')` makes an empty answer fail with that message.\n- If no driver is registered for the name the call fails with `API is not initialized`.\n\n```js definitions/api.js\n// The driver of a fake weather service: in a real one this would be an HTTP request\nNEWAPI('weather', { unit: 'C' }, function(options, next) {\n\tif (options.schema === 'forecast')\n\t\tnext(null, { city: options.data.city, temperature: 21, unit: options.config.unit, service: options.name });\n\telse\n\t\tnext('Unknown operation ' + options.schema);\n});\n```\n\n```js controllers/weather.js\nexports.install = function() {\n\tROUTE('GET /forecast/', forecast);\n\tROUTE('GET /forecast-fahrenheit/', fahrenheit);\n\tROUTE('GET /unknown/', unknown);\n\tROUTE('GET /missing/', missing);\n\tROUTE('GET /promise/', promise);\n};\n\nfunction forecast($) {\n\tAPI('weather', 'forecast', { city: $.query.city }).callback($);\n}\n\nfunction fahrenheit($) {\n\tAPI('weather', 'forecast', { city: $.query.city }).configure({ unit: 'F' }).callback($);\n}\n\nfunction unknown($) {\n\tAPI('weather', 'rain', {}).callback($);\n}\n\nfunction missing($) {\n\tAPI('nobody', 'forecast', {}).callback($);\n}\n\nasync function promise($) {\n\tconst data = await API('weather', 'forecast', { city: 'Oslo' }).promise($);\n\t$.json({ city: data.city, same: true });\n}\n\n```"}],"knowledge":{"content_sha256":"a619fa8d5764560b83f75ce806ad4bb4214635ebaaf86d1ae315dd25a11c21bf","source_sha256":"0829924b91418bcacaeab50871bc300f29dd03cae2341ae266efe2048a83da56","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["API","GET","HTTP","NEWAPI","ROUTE"],"language":"en"},"files":[{"path":"definitions/api.js","language":"js","content":"// The driver of a fake weather service: in a real one this would be an HTTP request\nNEWAPI('weather', { unit: 'C' }, function(options, next) {\n\tif (options.schema === 'forecast')\n\t\tnext(null, { city: options.data.city, temperature: 21, unit: options.config.unit, service: options.name });\n\telse\n\t\tnext('Unknown operation ' + options.schema);\n});"},{"path":"controllers/weather.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /forecast/', forecast);\n\tROUTE('GET /forecast-fahrenheit/', fahrenheit);\n\tROUTE('GET /unknown/', unknown);\n\tROUTE('GET /missing/', missing);\n\tROUTE('GET /promise/', promise);\n};\n\nfunction forecast($) {\n\tAPI('weather', 'forecast', { city: $.query.city }).callback($);\n}\n\nfunction fahrenheit($) {\n\tAPI('weather', 'forecast', { city: $.query.city }).configure({ unit: 'F' }).callback($);\n}\n\nfunction unknown($) {\n\tAPI('weather', 'rain', {}).callback($);\n}\n\nfunction missing($) {\n\tAPI('nobody', 'forecast', {}).callback($);\n}\n\nasync function promise($) {\n\tconst data = await API('weather', 'forecast', { city: 'Oslo' }).promise($);\n\t$.json({ city: data.city, same: true });\n}\n"}],"test":"const res = await get('/forecast/?city=Madrid');\nassert(res.json.city === 'Madrid' && res.json.temperature === 21, 'API calls the driver and gives its response to the action, got: ' + res.body);\nassert(res.json.unit === 'C' && res.json.service === 'weather', 'the config of NEWAPI and the name arrive in options, got: ' + res.body);\n\nconst f = await get('/forecast-fahrenheit/?city=Madrid');\nassert(f.json.unit === 'F', 'configure() overrides the config of one call, got: ' + f.body);\n\nconst bad = await get('/unknown/');\nassert(bad.status === 400 && bad.body.includes('Unknown operation rain'), 'the error of the driver becomes the error of the action, got: ' + bad.status + ' ' + bad.body);\n\nconst none = await get('/missing/');\nassert(none.status === 400 && none.body.includes('API is not initialized'), 'a service without a driver fails, got: ' + none.status + ' ' + none.body);\n\nconst p = await get('/promise/');\nassert(p.json.city === 'Oslo', 'promise() works, got: ' + p.body);","test_kind":"http"}
{"id":"total5/ai-models","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/ai-models/","markdown_url":"https://forge.totaljs.es/en/total5/ai-models.md","tags":["ai","llm","integrations"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"317c0dcba35cb75db9cc6b22b5966cc32ce964cc98a1733c603d62088e9e2a8f","checked_at":"2026-10-20T12:17:43.308Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Talk to language models with AIMODEL(), NEWAIMODEL() and AIPARSER()","description":"Build a chat request with AIMODEL(), let a driver registered with NEWAIMODEL() send it to a provider, and read streamed answers with AIPARSER().","messages":[{"role":"user","content":"Talk to language models with AIMODEL(), NEWAIMODEL() and AIPARSER()\n\nBuild a chat request with AIMODEL(), let a driver registered with NEWAIMODEL() send it to a provider, and read streamed answers with AIPARSER()."},{"role":"assistant","content":"`total5` does not ship a connection to any provider. It gives you three pieces that work together:\n\n- `NEWAIMODEL('name', config, driver)` registers the **driver** of one or more models (`'a,b'` registers the same driver for two names). The driver is `function(ai, done)`: `ai.payload` has `model`, `messages`, `tools`, `prompt` and `think`, `ai.config` is the configuration and `ai.options.stream` is the function the caller passed to `stream()` (or `undefined`). Answer with `done(error, response)`.\n- `AIMODEL('name')` starts a request for a registered model and returns a builder. `system(text)`, `user(text)` and `assistant(text)` append a message, `prompt(text)`, `tool(definition)` and `think()` fill the rest of the payload, and `configure({ ... })` adds to `ai.config` for this call only. Finish it with `callback(fn)`, `await promise($)` or `stream(fn)`; the driver runs after the current turn, so you can chain everything first.\n- `AIPARSER(provider)` returns a parser for the **streamed** answer of a provider (the source of `total5` knows `openai`, `claude`/`anthropic`, `gemini`/`google`, `ollama` and `openai_responses`, and reads any other name as a generic format; only `openai` is run below). `write(chunk)` takes pieces of the stream (it keeps incomplete lines until the next piece), `end()` finishes it, and both return `{ content, thinking, tools, reasoning }`. `parser.on('content', fn)` is called with every part of the text as it arrives.\n\nA model that was not registered answers with the error `AI model not found.`. The example registers a local driver that returns a response from the supplied messages.\n\n```js definitions/ai.js\nNEWAIMODEL('echo', { prefix: 'Echo' }, function(ai, done) {\n\n\tconst last = ai.payload.messages.filter(n => n.role === 'user').pop();\n\tconst text = ai.config.prefix + ': ' + (last ? last.content : '');\n\n\t// A real driver would send ai.payload to the provider and send the pieces to ai.options.stream\n\tif (ai.options.stream) {\n\t\tfor (const word of text.split(' '))\n\t\t\tai.options.stream(word + ' ');\n\t}\n\n\tdone(null, { content: text, roles: ai.payload.messages.map(n => n.role), tools: ai.payload.tools.length, think: !!ai.payload.think });\n});\n```\n\n```js controllers/ai.js\nexports.install = function() {\n\tROUTE('POST /ask/', ask);\n\tROUTE('GET /ask-config/', askconfig);\n\tROUTE('GET /ask-stream/', askstream);\n\tROUTE('GET /ask-missing/', missing);\n\tROUTE('GET /parse/', parse);\n};\n\nasync function ask($) {\n\tconst response = await AIMODEL('echo').system('You are brief.').user($.body.question).tool({ name: 'weather' }).think().promise($);\n\t$.json(response);\n}\n\nfunction askconfig($) {\n\tAIMODEL('echo').user('hello').configure({ prefix: 'Custom' }).callback($.callback());\n}\n\nfunction askstream($) {\n\tconst parts = [];\n\tAIMODEL('echo').user('one two three').stream(part => parts.push(part)).callback(function(err, response) {\n\t\t$.json({ parts: parts, content: response.content });\n\t});\n}\n\nfunction missing($) {\n\tAIMODEL('nobody').user('hi').callback($.callback());\n}\n\nfunction parse($) {\n\tconst parser = AIPARSER('openai');\n\tconst events = [];\n\tparser.on('content', e => events.push(e.delta));\n\n\t// the second piece starts in the middle of a line\n\tparser.write('data: {\"choices\":[{\"delta\":{\"content\":\"Hel\"}}]}\\n\\ndata: {\"choices\":[{\"delta\":{\"con');\n\tparser.write('tent\":\"lo\"}}]}\\n\\ndata: [DONE]\\n');\n\tconst output = parser.end();\n\n\t$.json({ output: output, events: events });\n}\n```"}],"knowledge":{"content_sha256":"7c29008f55a6bdf6dc0904f945e8272bb1a3a32c18781f7f4039c6da23dfb8e9","source_sha256":"317c0dcba35cb75db9cc6b22b5966cc32ce964cc98a1733c603d62088e9e2a8f","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["AIMODEL","AIPARSER","DONE","GET","NEWAIMODEL","POST","ROUTE"],"language":"en"},"files":[{"path":"definitions/ai.js","language":"js","content":"NEWAIMODEL('echo', { prefix: 'Echo' }, function(ai, done) {\n\n\tconst last = ai.payload.messages.filter(n => n.role === 'user').pop();\n\tconst text = ai.config.prefix + ': ' + (last ? last.content : '');\n\n\t// A real driver would send ai.payload to the provider and send the pieces to ai.options.stream\n\tif (ai.options.stream) {\n\t\tfor (const word of text.split(' '))\n\t\t\tai.options.stream(word + ' ');\n\t}\n\n\tdone(null, { content: text, roles: ai.payload.messages.map(n => n.role), tools: ai.payload.tools.length, think: !!ai.payload.think });\n});"},{"path":"controllers/ai.js","language":"js","content":"exports.install = function() {\n\tROUTE('POST /ask/', ask);\n\tROUTE('GET /ask-config/', askconfig);\n\tROUTE('GET /ask-stream/', askstream);\n\tROUTE('GET /ask-missing/', missing);\n\tROUTE('GET /parse/', parse);\n};\n\nasync function ask($) {\n\tconst response = await AIMODEL('echo').system('You are brief.').user($.body.question).tool({ name: 'weather' }).think().promise($);\n\t$.json(response);\n}\n\nfunction askconfig($) {\n\tAIMODEL('echo').user('hello').configure({ prefix: 'Custom' }).callback($.callback());\n}\n\nfunction askstream($) {\n\tconst parts = [];\n\tAIMODEL('echo').user('one two three').stream(part => parts.push(part)).callback(function(err, response) {\n\t\t$.json({ parts: parts, content: response.content });\n\t});\n}\n\nfunction missing($) {\n\tAIMODEL('nobody').user('hi').callback($.callback());\n}\n\nfunction parse($) {\n\tconst parser = AIPARSER('openai');\n\tconst events = [];\n\tparser.on('content', e => events.push(e.delta));\n\n\t// the second piece starts in the middle of a line\n\tparser.write('data: {\"choices\":[{\"delta\":{\"content\":\"Hel\"}}]}\\n\\ndata: {\"choices\":[{\"delta\":{\"con');\n\tparser.write('tent\":\"lo\"}}]}\\n\\ndata: [DONE]\\n');\n\tconst output = parser.end();\n\n\t$.json({ output: output, events: events });\n}"}],"test":"const a = await post('/ask/', { question: 'Is it raining?' });\nassert(a.json.content === 'Echo: Is it raining?', 'the driver answers from the messages, got: ' + a.body);\nassert(a.json.roles.join() === 'system,user' && a.json.tools === 1 && a.json.think === true, 'system, user, tool and think reach the payload, got: ' + a.body);\n\nconst c = await get('/ask-config/');\nassert(c.json.content === 'Custom: hello', 'configure() overrides the config of NEWAIMODEL for one call, got: ' + c.body);\n\nconst s = await get('/ask-stream/');\nassert(s.json.parts.join('') === 'Echo: one two three ' && s.json.parts.length === 4, 'the driver sends the pieces to the function of stream(), got: ' + s.body);\n\nconst m = await get('/ask-missing/');\nassert(m.status === 400 && m.body.includes('AI model not found.'), 'a model without a driver fails, got: ' + m.status + ' ' + m.body);\n\nconst p = await get('/parse/');\nassert(p.json.output.content === 'Hello' && p.json.output.reasoning === 'content' && p.json.output.tools.length === 0, 'AIPARSER joins the pieces of the stream, got: ' + p.body);\nassert(p.json.events.join('|') === 'Hel|lo', 'the content event is called with every part, got: ' + p.body);","test_kind":"http"}
{"id":"total5/rate-limiting","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/rate-limiting/","markdown_url":"https://forge.totaljs.es/en/total5/rate-limiting.md","tags":["security","rate-limiting"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"bd96c0be1f15ffde165c4051e9f0cdee9202026185f41fcb56ff04aad525ef60","checked_at":"2026-10-20T12:18:38.660Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Limit failed attempts with BLOCKED","description":"Count attempts per IP address with BLOCKED() and answer 429 once the limit is reached, for logins and password resets.","messages":[{"role":"user","content":"Limit failed attempts with BLOCKED\n\nCount attempts per IP address with BLOCKED() and answer 429 once the limit is reached, for logins and password resets."},{"role":"assistant","content":"`BLOCKED($, limit, expiration)` counts calls per IP address. It returns `true` once the address has made more than `limit` calls within the expiration (default: 5 calls in 15 minutes), and `undefined` before that. Call it at the start of a sensitive handler and stop when it returns `true`. To forgive an address after a successful login call `BLOCKED($, -1)`.\n\nThe counters live in the memory of the process; behind a reverse proxy make sure the real client address reaches Total.js, otherwise everybody shares one counter.\n\n```js controllers/login.js\nexports.install = function() {\n\tROUTE('POST /login/', login);\n};\n\nfunction login($) {\n\n\tif (BLOCKED($, 3, '1 minute')) {\n\t\t$.invalid(429);\n\t\treturn;\n\t}\n\n\tif ($.body.password === 'correct') {\n\t\tBLOCKED($, -1);\n\t\t$.json({ ok: true });\n\t} else\n\t\t$.invalid(401);\n}\n```"}],"knowledge":{"content_sha256":"7e8fac3c4d653cf0d6f7b29e6b60cac123f37a0805deb8184eb6ac990cb10230","source_sha256":"bd96c0be1f15ffde165c4051e9f0cdee9202026185f41fcb56ff04aad525ef60","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["BLOCKED","POST","ROUTE","Total.js"],"language":"en"},"files":[{"path":"controllers/login.js","language":"js","content":"exports.install = function() {\n\tROUTE('POST /login/', login);\n};\n\nfunction login($) {\n\n\tif (BLOCKED($, 3, '1 minute')) {\n\t\t$.invalid(429);\n\t\treturn;\n\t}\n\n\tif ($.body.password === 'correct') {\n\t\tBLOCKED($, -1);\n\t\t$.json({ ok: true });\n\t} else\n\t\t$.invalid(401);\n}"}],"test":"for (let i = 0; i < 3; i++) {\n\tconst wrong = await post('/login/', { password: 'wrong' });\n\tassert(wrong.status === 401, 'attempt ' + (i + 1) + ' is checked, got: ' + wrong.status);\n}\n\nconst stillAllowed = await post('/login/', { password: 'wrong' });\nassert(stillAllowed.status === 401, 'the fourth call is still allowed with limit 3, got: ' + stillAllowed.status);\n\nconst blocked = await post('/login/', { password: 'wrong' });\nassert(blocked.status === 429, 'after the limit the address is blocked, got: ' + blocked.status);\n\nconst evenCorrect = await post('/login/', { password: 'correct' });\nassert(evenCorrect.status === 429, 'a correct password does not help while blocked, got: ' + evenCorrect.status);","test_kind":"http"}
{"id":"total5/utility-globals","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/utility-globals/","markdown_url":"https://forge.totaljs.es/en/total5/utility-globals.md","tags":["globals","utilities","helpers"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"fa03c8387682080adc14fc28ece1160062b6700ef5fbdf87179098ae265f58e0","checked_at":"2026-10-20T12:18:59.524Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"The small global helpers: MAIN, REPO, FUNC, NOW, CLONE, COPY, QUERIFY and PROMISIFY","description":"Use application globals, dates, deep copies, query strings and promise helpers.","messages":[{"role":"user","content":"The small global helpers: MAIN, REPO, FUNC, NOW, CLONE, COPY, QUERIFY and PROMISIFY\n\nUse application globals, dates, deep copies, query strings and promise helpers."},{"role":"assistant","content":"`total5` creates a few global objects for your own use and a few helpers that save a line of code. Nothing here needs setup.\n\n- `MAIN`, `REPO` and `FUNC` are empty objects that the framework creates once: keep application data in `MAIN`, other data in `REPO` and your own functions in `FUNC`. `TEMP` is an object for temporary data that the framework replaces from time to time, so do not keep anything in it that you need later.\n- `NOW` is the current date as a `Date`. It is refreshed about once a minute (and by some calls), so use `new Date()` when you need the exact time.\n- `EMPTYARRAY` and `EMPTYOBJECT` are frozen empty values to return instead of creating new ones: they cannot be changed.\n- `MODS` and `PLUGINS` are objects with the loaded modules and plugins by id. `F` and `Total` are the same object, the framework, and `print` is `console.log`. `NOOP` is a function that does nothing.\n- `REQUIRE(name)` is `require` that also resolves packages from the `node_modules` of the application and accepts names such as `node:path`.\n- `CLONE(value)` makes a deep copy: nested objects, arrays and dates are copied. `COPY(from, to)` copies the properties of the first object into the second and returns the second.\n- `QUERIFY([url], object)` builds a query string from an object: empty values and `null` are skipped and an array becomes a comma separated value. With an url it returns `url?...`, without one it returns `?...`.\n- `PROMISIFY(fn, a, b, c)` calls `fn(a, b, c, callback)` and returns a promise that resolves with the response of the callback or rejects with its error.\n\n```js controllers/helpers.js\nMAIN.started = 'yes';\nREPO.counter = 1;\nFUNC.double = n => n * 2;\n\nexports.install = function() {\n\tROUTE('GET /helpers/', helpers);\n\tROUTE('GET /promisify/', promisify);\n};\n\nfunction helpers($) {\n\n\tconst source = { a: 1, nested: { b: [1, 2] }, date: new Date(2026, 0, 5) };\n\tconst cloned = CLONE(source);\n\tconst target = { x: 1 };\n\tconst returned = COPY({ y: 2 }, target);\n\n\t$.json({\n\t\tshared: [MAIN.started, REPO.counter, FUNC.double(4), typeof TEMP],\n\t\tnow: [NOW instanceof Date, Math.abs(Date.now() - NOW.getTime()) < 120000],\n\t\temptyarray: [EMPTYARRAY.length, Object.isFrozen(EMPTYARRAY)],\n\t\temptyobject: [Object.keys(EMPTYOBJECT).length, Object.isFrozen(EMPTYOBJECT)],\n\t\tregistries: [typeof MODS, typeof PLUGINS],\n\t\tsame: [F === Total, typeof print, NOOP() === undefined],\n\t\trequire: typeof REQUIRE('node:path').join,\n\t\tclone: [cloned !== source, cloned.nested !== source.nested, cloned.nested.b !== source.nested.b, cloned.date instanceof Date, cloned.nested.b.join()],\n\t\tcopy: [returned === target, target.x, target.y],\n\t\tquerify: QUERIFY('/search/', { q: 'a b', page: 2, empty: '', nothing: null, list: [1, 2] }),\n\t\tquerify2: QUERIFY({ q: 'x' })\n\t});\n}\n\nasync function promisify($) {\n\n\tconst add = (a, b, callback) => setImmediate(callback, null, a + b);\n\tconst fail = (callback) => setImmediate(callback, 'It failed');\n\n\tconst sum = await PROMISIFY(add, 2, 3);\n\tlet error = null;\n\n\ttry {\n\t\tawait PROMISIFY(fail);\n\t} catch (e) {\n\t\terror = e.message || String(e);\n\t}\n\n\t$.json({ sum: sum, error: error });\n}\n```"}],"knowledge":{"content_sha256":"fe1db1d0b9705cf0ab8ab5aae2d10d62c6f0ca3c79286a1c96bcb884b0dc7f02","source_sha256":"fa03c8387682080adc14fc28ece1160062b6700ef5fbdf87179098ae265f58e0","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["CLONE","COPY","Date.now","EMPTYARRAY","EMPTYOBJECT","FUNC","GET","MAIN","MODS","NOOP","NOW","PLUGINS","PROMISIFY","QUERIFY","REPO","REQUIRE","ROUTE","TEMP"],"language":"en"},"files":[{"path":"controllers/helpers.js","language":"js","content":"MAIN.started = 'yes';\nREPO.counter = 1;\nFUNC.double = n => n * 2;\n\nexports.install = function() {\n\tROUTE('GET /helpers/', helpers);\n\tROUTE('GET /promisify/', promisify);\n};\n\nfunction helpers($) {\n\n\tconst source = { a: 1, nested: { b: [1, 2] }, date: new Date(2026, 0, 5) };\n\tconst cloned = CLONE(source);\n\tconst target = { x: 1 };\n\tconst returned = COPY({ y: 2 }, target);\n\n\t$.json({\n\t\tshared: [MAIN.started, REPO.counter, FUNC.double(4), typeof TEMP],\n\t\tnow: [NOW instanceof Date, Math.abs(Date.now() - NOW.getTime()) < 120000],\n\t\temptyarray: [EMPTYARRAY.length, Object.isFrozen(EMPTYARRAY)],\n\t\temptyobject: [Object.keys(EMPTYOBJECT).length, Object.isFrozen(EMPTYOBJECT)],\n\t\tregistries: [typeof MODS, typeof PLUGINS],\n\t\tsame: [F === Total, typeof print, NOOP() === undefined],\n\t\trequire: typeof REQUIRE('node:path').join,\n\t\tclone: [cloned !== source, cloned.nested !== source.nested, cloned.nested.b !== source.nested.b, cloned.date instanceof Date, cloned.nested.b.join()],\n\t\tcopy: [returned === target, target.x, target.y],\n\t\tquerify: QUERIFY('/search/', { q: 'a b', page: 2, empty: '', nothing: null, list: [1, 2] }),\n\t\tquerify2: QUERIFY({ q: 'x' })\n\t});\n}\n\nasync function promisify($) {\n\n\tconst add = (a, b, callback) => setImmediate(callback, null, a + b);\n\tconst fail = (callback) => setImmediate(callback, 'It failed');\n\n\tconst sum = await PROMISIFY(add, 2, 3);\n\tlet error = null;\n\n\ttry {\n\t\tawait PROMISIFY(fail);\n\t} catch (e) {\n\t\terror = e.message || String(e);\n\t}\n\n\t$.json({ sum: sum, error: error });\n}"}],"test":"const r = (await get('/helpers/')).json;\nassert(r.shared.join() === 'yes,1,8,object', 'MAIN, REPO and FUNC keep what you put in them, got: ' + r.shared);\nassert(r.now[0] && r.now[1], 'NOW is a recent Date, got: ' + r.now);\nassert(r.emptyarray.join() === '0,true' && r.emptyobject.join() === '0,true', 'the empty values are frozen');\nassert(r.registries.join() === 'object,object' && r.same.join() === 'true,function,true' && r.require === 'function', 'MODS, PLUGINS, F, print, NOOP and REQUIRE, got: ' + JSON.stringify(r));\nassert(r.clone.join() === 'true,true,true,true,1,2', 'CLONE is deep, got: ' + r.clone);\nassert(r.copy.join() === 'true,1,2', 'COPY adds the properties to the second object and returns it, got: ' + r.copy);\nassert(r.querify === '/search/?q=a%20b&page=2&list=1%2C2', 'QUERIFY skips empty values and joins arrays, got: ' + r.querify);\nassert(r.querify2 === '?q=x', 'QUERIFY without an url starts with ?, got: ' + r.querify2);\n\nconst p = (await get('/promisify/')).json;\nassert(p.sum === 5, 'PROMISIFY resolves with the response of the callback, got: ' + JSON.stringify(p));\nassert(p.error === 'It failed', 'PROMISIFY rejects with the error of the callback, got: ' + JSON.stringify(p));","test_kind":"http"}
{"id":"total5/errors-and-audit","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/errors-and-audit/","markdown_url":"https://forge.totaljs.es/en/total5/errors-and-audit.md","tags":["errors","logging","audit"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"2cacccdf81ff4e89abc56933a484c2cd2924a81daa22c30af91ba1dc545764fa","checked_at":"2026-10-20T12:18:03.991Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Collect errors with ErrorBuilder, log them with ERROR() and keep an audit trail with AUDIT()","description":"Answer with several errors at once using ErrorBuilder, the SUCCESS() answer, how ERROR() records failures, and how AUDIT() appends who did what to a log file.","messages":[{"role":"user","content":"Collect errors with ErrorBuilder, log them with ERROR() and keep an audit trail with AUDIT()\n\nAnswer with several errors at once using ErrorBuilder, the SUCCESS() answer, how ERROR() records failures, and how AUDIT() appends who did what to a log file."},{"role":"assistant","content":"- **`ErrorBuilder`** collects errors. `push(message, [path], [index])` adds one (`path` is the field it belongs to), `push(404)` adds the text of a status code and sets the status of the answer, `length` counts them, `output()` returns them as an array of `{ error, path, index }` and `toString()` joins the messages. `$.invalid(builder)` answers with that array and the status of the builder (400 unless a status code was pushed).\n- **`SUCCESS(value)`** returns the object for a successful operation: `{ success: true, value: value }`.\n- **`ERROR(name)`** returns a callback for `function(err)` APIs: when it receives an error it records it under `name`, and it does nothing when the error is empty. `ERROR()` is the same callback without a name. A recorded error is printed to the console, counted in `F.stats.error`, and the last ten are kept in `F.errors` as `{ error, name, date }`.\n- **`AUDIT($, message, type)`** appends one JSON line to `logs/audit.log` with who and where (`ip`, `ua`, `url`, and the user when there is one), the `message`, the `type`, the `params`, `query` and `model` of the request in `data`, the name of the application in `app` and the time in `dtcreated`. `AUDIT('name', $, message, type)` writes to `logs/name.log` instead.\n\n```js controllers/reports.js\nexports.install = function() {\n\tROUTE('GET /builder/', builder);\n\tROUTE('GET /builder404/', builder404);\n\tROUTE('GET /success/', success);\n\tROUTE('GET /error/', error);\n\tROUTE('GET /audit/', audit);\n\tROUTE('GET /audit-read/{name}/', auditread);\n};\n\nfunction builder($) {\n\tconst errors = new ErrorBuilder();\n\terrors.push('Name is required', 'name');\n\terrors.push('Age is too low', 'age', 2);\n\t$.invalid(errors);\n}\n\nfunction builder404($) {\n\tconst errors = new ErrorBuilder();\n\terrors.push(404);\n\t$.invalid(errors);\n}\n\nfunction success($) {\n\t$.json(SUCCESS('saved'));\n}\n\nfunction error($) {\n\tconst before = F.stats.error;\n\tERROR('mypart')('Something broke');\n\tERROR('mypart')(null);\n\tERROR()('Without a name');\n\t$.json({ counted: F.stats.error - before, last: F.errors.slice(-2).map(n => ({ error: n.error, name: n.name || null, date: n.date instanceof Date || typeof n.date === 'string' })) });\n}\n\nfunction audit($) {\n\tAUDIT($, 'Opened the report', 'info');\n\tAUDIT('security', $, 'Checked the access');\n\t$.json({ ok: true });\n}\n\nfunction auditread($) {\n\tsetTimeout(function() {\n\t\tconst lines = require('fs').readFileSync(PATH.logs($.params.name + '.log'), 'utf8').trim().split('\\n');\n\t\t$.json(lines.map(n => JSON.parse(n)));\n\t}, 300);\n}\n```"}],"knowledge":{"content_sha256":"ccd28753dcc2801ab0c0c92f984123b5941dfcfca8fdb41cbdcbf591eba8a958","source_sha256":"2cacccdf81ff4e89abc56933a484c2cd2924a81daa22c30af91ba1dc545764fa","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["API","AUDIT","ERROR","GET","JSON","PATH","ROUTE","SUCCESS"],"language":"en"},"files":[{"path":"controllers/reports.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /builder/', builder);\n\tROUTE('GET /builder404/', builder404);\n\tROUTE('GET /success/', success);\n\tROUTE('GET /error/', error);\n\tROUTE('GET /audit/', audit);\n\tROUTE('GET /audit-read/{name}/', auditread);\n};\n\nfunction builder($) {\n\tconst errors = new ErrorBuilder();\n\terrors.push('Name is required', 'name');\n\terrors.push('Age is too low', 'age', 2);\n\t$.invalid(errors);\n}\n\nfunction builder404($) {\n\tconst errors = new ErrorBuilder();\n\terrors.push(404);\n\t$.invalid(errors);\n}\n\nfunction success($) {\n\t$.json(SUCCESS('saved'));\n}\n\nfunction error($) {\n\tconst before = F.stats.error;\n\tERROR('mypart')('Something broke');\n\tERROR('mypart')(null);\n\tERROR()('Without a name');\n\t$.json({ counted: F.stats.error - before, last: F.errors.slice(-2).map(n => ({ error: n.error, name: n.name || null, date: n.date instanceof Date || typeof n.date === 'string' })) });\n}\n\nfunction audit($) {\n\tAUDIT($, 'Opened the report', 'info');\n\tAUDIT('security', $, 'Checked the access');\n\t$.json({ ok: true });\n}\n\nfunction auditread($) {\n\tsetTimeout(function() {\n\t\tconst lines = require('fs').readFileSync(PATH.logs($.params.name + '.log'), 'utf8').trim().split('\\n');\n\t\t$.json(lines.map(n => JSON.parse(n)));\n\t}, 300);\n}"}],"test":"const a = await get('/builder/');\nassert(a.status === 400 && a.json.length === 2, 'invalid() answers 400 with every error, got: ' + a.status + ' ' + a.body);\nassert(a.json[0].error === 'Name is required' && a.json[0].path === 'name' && a.json[1].index === 2, 'each error has its message, path and index, got: ' + a.body);\n\nconst b = await get('/builder404/');\nassert(b.status === 404 && b.json[0].error === '404: Not Found', 'push(404) sets the status and the text, got: ' + b.status + ' ' + b.body);\n\nconst c = await get('/success/');\nassert(c.json.success === true && c.json.value === 'saved', 'SUCCESS() builds the answer, got: ' + c.body);\n\nconst d = await get('/error/');\nassert(d.json.counted === 2, 'two errors were recorded and the empty one was ignored, got: ' + d.body);\nassert(d.json.last[0].error === 'Something broke' && d.json.last[0].name === 'mypart' && d.json.last[1].error === 'Without a name' && d.json.last[1].name === null, 'F.errors keeps the error and its name, got: ' + d.body);\n\nawait get('/audit/?page=2');\nconst log = (await get('/audit-read/audit/')).json;\nconst line = log[log.length - 1];\nassert(line.message === 'Opened the report' && line.type === 'info' && line.url === '/audit/' && line.ip === '127.0.0.1', 'AUDIT() appends a line to logs/audit.log, got: ' + JSON.stringify(line));\nassert(JSON.parse(line.data).query.page === '2' && line.app && line.dtcreated, 'the line has the request data, the app and the time, got: ' + JSON.stringify(line));\n\nconst named = (await get('/audit-read/security/')).json;\nassert(named[named.length - 1].message === 'Checked the access', 'AUDIT(name, ...) writes to logs/name.log, got: ' + JSON.stringify(named));","test_kind":"http"}
{"id":"total5/debounce-timers","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/debounce-timers/","markdown_url":"https://forge.totaljs.es/en/total5/debounce-timers.md","tags":["timers","debounce","utilities"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"ac9fa27f27e7cc8bfcd8806c6e2db8a6dd6be437dda6f29b107d686816ec3723","checked_at":"2026-10-20T12:18:00.477Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Debounce work with setTimeout2() and clearTimeout2()","description":"Use setTimeout2() to run a function once after the calls stop coming, replacing the previous timer with the same id, and cancel it with clearTimeout2().","messages":[{"role":"user","content":"Debounce work with setTimeout2() and clearTimeout2()\n\nUse setTimeout2() to run a function once after the calls stop coming, replacing the previous timer with the same id, and cancel it with clearTimeout2()."},{"role":"assistant","content":"`setTimeout2(id, callback, timeout)` is `setTimeout` with a name. When you call it again with the **same id** before the time is over, the previous timer is cancelled and replaced: the callback runs once, after the calls stop. It is the usual way to save, reload or recalculate only once when many events arrive together (a burst of changes, typing, a flood of messages).\n\n`clearTimeout2(id)` cancels the timer of that id and returns `true` when there was one and `false` when there was not.\n\nThe full form is `setTimeout2(id, callback, timeout, limit, a, b, c, d, e)`: the values after `limit` are passed to the callback. **Do not use `limit`** in `total5` 0.0.19: the idea is to run the callback at once after `limit` replacements, but the counter never starts (it is `undefined` and becomes `NaN`), so the callback is only ever called when the time is over. The example calls it ten times with a limit of 3 and the callback did not run early.\n\n```js controllers/timers.js\nexports.install = function() {\n\tROUTE('GET /burst/', burst);\n};\n\nasync function burst($) {\n\n\tconst log = [];\n\n\t// three calls with the same id: only the last one runs\n\tsetTimeout2('save', () => log.push('first'), 100);\n\tsetTimeout2('save', () => log.push('second'), 100);\n\tsetTimeout2('save', () => log.push('third'), 100);\n\n\t// a timer that is cancelled before it runs\n\tsetTimeout2('gone', () => log.push('gone'), 100);\n\tconst cancelled = clearTimeout2('gone');\n\tconst nothing = clearTimeout2('never-set');\n\n\t// the values after the limit reach the callback\n\tsetTimeout2('args', (a, b) => log.push(a + b), 50, 0, 'x', 'y');\n\n\t// the limit option does not run the callback early\n\tlet early = 0;\n\tfor (let i = 0; i < 10; i++)\n\t\tsetTimeout2('limited', () => early++, 1000, 3);\n\tconst stopped = clearTimeout2('limited');\n\n\tawait new Promise(resolve => setTimeout(resolve, 300));\n\t$.json({ log: log, cancelled: cancelled, nothing: nothing, early: early, stopped: stopped });\n}\n```"}],"knowledge":{"content_sha256":"e0a20f3b90203a51ae4c6bbdfe81c1fc468b6bba946d9f8046b8b7ad35a02bac","source_sha256":"ac9fa27f27e7cc8bfcd8806c6e2db8a6dd6be437dda6f29b107d686816ec3723","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["GET","ROUTE"],"language":"en"},"files":[{"path":"controllers/timers.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /burst/', burst);\n};\n\nasync function burst($) {\n\n\tconst log = [];\n\n\t// three calls with the same id: only the last one runs\n\tsetTimeout2('save', () => log.push('first'), 100);\n\tsetTimeout2('save', () => log.push('second'), 100);\n\tsetTimeout2('save', () => log.push('third'), 100);\n\n\t// a timer that is cancelled before it runs\n\tsetTimeout2('gone', () => log.push('gone'), 100);\n\tconst cancelled = clearTimeout2('gone');\n\tconst nothing = clearTimeout2('never-set');\n\n\t// the values after the limit reach the callback\n\tsetTimeout2('args', (a, b) => log.push(a + b), 50, 0, 'x', 'y');\n\n\t// the limit option does not run the callback early\n\tlet early = 0;\n\tfor (let i = 0; i < 10; i++)\n\t\tsetTimeout2('limited', () => early++, 1000, 3);\n\tconst stopped = clearTimeout2('limited');\n\n\tawait new Promise(resolve => setTimeout(resolve, 300));\n\t$.json({ log: log, cancelled: cancelled, nothing: nothing, early: early, stopped: stopped });\n}"}],"test":"const r = (await get('/burst/')).json;\nassert(r.log.join() === 'xy,third', 'the values after the limit reach the callback (xy) and only the last call with the same id runs (third), got: ' + JSON.stringify(r));\nassert(r.cancelled === true && r.nothing === false, 'clearTimeout2 tells whether a timer existed, got: ' + JSON.stringify(r));\nassert(r.early === 0 && r.stopped === true, 'limit does not run the callback early in this version, got: ' + JSON.stringify(r));","test_kind":"http"}
{"id":"total5/request-bound-tokens","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/request-bound-tokens/","markdown_url":"https://forge.totaljs.es/en/total5/request-bound-tokens.md","tags":["security","encryption","tokens"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"841efbd078d11db3abd1bff30036a96222816c93c387d115d83ac61dd619993f","checked_at":"2026-10-20T12:18:41.678Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Tokens tied to a browser with ENCRYPTREQ() and DECRYPTREQ()","description":"Encrypt a value together with the user agent (and optionally the IP address) of the request, and read it back only from the same client.","messages":[{"role":"user","content":"Tokens tied to a browser with ENCRYPTREQ() and DECRYPTREQ()\n\nEncrypt a value together with the user agent (and optionally the IP address) of the request, and read it back only from the same client."},{"role":"assistant","content":"`ENCRYPTREQ($, value, key, [strict])` encrypts a value **together with the user agent of the request** and returns a text token. `DECRYPTREQ($, token, key)` reads the value back, and returns nothing (`undefined`) when the key is wrong, the token is empty, or the request comes from a different client. With `strict` set to `true` the IP address of the request is part of the token too, so it also stops working when the address changes.\n\nUse it for values that you hand to the browser and expect back from the same browser: a short-lived link, a download address, a form token. It is not a replacement for a session: the user agent is easy to copy. For tokens that do not depend on the request see the encrypt and decrypt recipe.\n\n```js controllers/tokens.js\nexports.install = function() {\n\tROUTE('GET /issue/', issue);\n\tROUTE('GET /read/', read);\n\tROUTE('GET /read-wrong-key/', wrongkey);\n};\n\nfunction issue($) {\n\t$.json({ token: ENCRYPTREQ($, { id: 5 }, 'my-secret'), strict: ENCRYPTREQ($, 'only-here', 'my-secret', true) });\n}\n\nfunction read($) {\n\t$.json({ value: DECRYPTREQ($, $.query.token, 'my-secret') || null });\n}\n\nfunction wrongkey($) {\n\t$.json({ value: DECRYPTREQ($, $.query.token, 'another-secret') || null });\n}\n```"}],"knowledge":{"content_sha256":"8cdff67bb59806a632b3b63151bc64dc8416ae5d8892adb0e9296bb52e288e0a","source_sha256":"841efbd078d11db3abd1bff30036a96222816c93c387d115d83ac61dd619993f","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["DECRYPTREQ","ENCRYPTREQ","GET","ROUTE"],"language":"en"},"files":[{"path":"controllers/tokens.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /issue/', issue);\n\tROUTE('GET /read/', read);\n\tROUTE('GET /read-wrong-key/', wrongkey);\n};\n\nfunction issue($) {\n\t$.json({ token: ENCRYPTREQ($, { id: 5 }, 'my-secret'), strict: ENCRYPTREQ($, 'only-here', 'my-secret', true) });\n}\n\nfunction read($) {\n\t$.json({ value: DECRYPTREQ($, $.query.token, 'my-secret') || null });\n}\n\nfunction wrongkey($) {\n\t$.json({ value: DECRYPTREQ($, $.query.token, 'another-secret') || null });\n}"}],"test":"const ua = { headers: { 'user-agent': 'Browser A' } };\nconst other = { headers: { 'user-agent': 'Browser B' } };\n\nconst issued = (await get('/issue/', ua)).json;\nassert(typeof issued.token === 'string' && issued.token.length > 20, 'a token is a text, got: ' + JSON.stringify(issued));\n\nconst same = (await get('/read/?token=' + encodeURIComponent(issued.token), ua)).json;\nassert(same.value && same.value.id === 5, 'the same user agent reads the value back, got: ' + JSON.stringify(same));\n\nconst different = (await get('/read/?token=' + encodeURIComponent(issued.token), other)).json;\nassert(different.value === null, 'another user agent gets nothing, got: ' + JSON.stringify(different));\n\nconst strict = (await get('/read/?token=' + encodeURIComponent(issued.strict), ua)).json;\nassert(strict.value === 'only-here', 'a strict token works from the same address, got: ' + JSON.stringify(strict));\n\nconst key = (await get('/read-wrong-key/?token=' + encodeURIComponent(issued.token), ua)).json;\nassert(key.value === null, 'a wrong key gets nothing, got: ' + JSON.stringify(key));\n\nconst empty = (await get('/read/?token=', ua)).json;\nassert(empty.value === null, 'an empty token gets nothing, got: ' + JSON.stringify(empty));","test_kind":"http"}
{"id":"total5/backup-restore-shell","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/backup-restore-shell/","markdown_url":"https://forge.totaljs.es/en/total5/backup-restore-shell.md","tags":["backup","shell","files"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"089cdbc9448369ecc1ae15ae23ae3878f15004f11e80ea6d8b7070ec8ad7e087","checked_at":"2026-10-20T12:17:48.240Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Back up a folder with BACKUP() and RESTORE(), and run commands with SHELL()","description":"Pack a directory into one file and unpack it again with BACKUP() and RESTORE(), and run a shell command in the project folder with SHELL().","messages":[{"role":"user","content":"Back up a folder with BACKUP() and RESTORE(), and run commands with SHELL()\n\nPack a directory into one file and unpack it again with BACKUP() and RESTORE(), and run a shell command in the project folder with SHELL()."},{"role":"assistant","content":"- `BACKUP(filename, directory, [callback], [filter])` packs a directory (with its subdirectories) into **one file** and resolves with `{ filename, files, size }`. An existing file with that name is replaced. Without a callback it returns a promise.\n- `RESTORE(filename, directory, [callback], [filter])` unpacks a file made with `BACKUP()` into a directory, creating the folders it needs, and resolves with `{ files, size, path }`.\n- `SHELL(command, [callback], [cwd])` runs a shell command and resolves with its output (`stdout`). The folder is the one of the project, or the text you pass as the last argument. When the command fails the promise is rejected with an error whose `code` is the exit code. With a callback it is called as `callback(err, stdout, stderr)`.\n\n`SHELL()` runs **exactly the text you give it**: never build the command from what a user sent.\n\n```js controllers/backups.js\nconst Fs = require('fs');\nconst Path = require('path');\n\nexports.install = function() {\n\tROUTE('GET /backup/', backup);\n\tROUTE('GET /shell/', shell);\n};\n\nasync function backup($) {\n\n\t// a folder with a file and a subfolder\n\tconst source = PATH.tmp('source');\n\tFs.rmSync(source, { recursive: true, force: true });\n\tFs.mkdirSync(Path.join(source, 'sub'), { recursive: true });\n\tFs.writeFileSync(Path.join(source, 'a.txt'), 'hello');\n\tFs.writeFileSync(Path.join(source, 'sub', 'b.txt'), 'world');\n\n\tconst file = PATH.tmp('folder.backup');\n\tconst packed = await BACKUP(file, source);\n\n\tconst target = PATH.tmp('restored');\n\tFs.rmSync(target, { recursive: true, force: true });\n\tconst unpacked = await RESTORE(file, target);\n\n\t$.json({\n\t\tpacked: [packed.files, packed.filename === file, Fs.existsSync(file)],\n\t\tunpacked: [unpacked.files, unpacked.size, unpacked.path === target],\n\t\tcontent: [Fs.readFileSync(Path.join(target, 'a.txt'), 'utf8'), Fs.readFileSync(Path.join(target, 'sub', 'b.txt'), 'utf8')]\n\t});\n}\n\nasync function shell($) {\n\n\tconst output = await SHELL('echo hello');\n\tconst where = await SHELL('pwd', PATH.tmp());\n\n\tlet code = null;\n\ttry {\n\t\tawait SHELL('exit 3');\n\t} catch (e) {\n\t\tcode = e.code;\n\t}\n\n\tSHELL('echo from callback', (err, stdout) => $.json({ output: output, where: where.trim() === PATH.tmp() || where.trim().endsWith('/tmp'), code: code, callback: stdout }));\n}\n```"}],"knowledge":{"content_sha256":"951ae399addf9763c314144b8f259adde20d5bd8d856698108de9e75dce43fde","source_sha256":"089cdbc9448369ecc1ae15ae23ae3878f15004f11e80ea6d8b7070ec8ad7e087","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["BACKUP","GET","PATH","RESTORE","ROUTE","SHELL"],"language":"en"},"files":[{"path":"controllers/backups.js","language":"js","content":"const Fs = require('fs');\nconst Path = require('path');\n\nexports.install = function() {\n\tROUTE('GET /backup/', backup);\n\tROUTE('GET /shell/', shell);\n};\n\nasync function backup($) {\n\n\t// a folder with a file and a subfolder\n\tconst source = PATH.tmp('source');\n\tFs.rmSync(source, { recursive: true, force: true });\n\tFs.mkdirSync(Path.join(source, 'sub'), { recursive: true });\n\tFs.writeFileSync(Path.join(source, 'a.txt'), 'hello');\n\tFs.writeFileSync(Path.join(source, 'sub', 'b.txt'), 'world');\n\n\tconst file = PATH.tmp('folder.backup');\n\tconst packed = await BACKUP(file, source);\n\n\tconst target = PATH.tmp('restored');\n\tFs.rmSync(target, { recursive: true, force: true });\n\tconst unpacked = await RESTORE(file, target);\n\n\t$.json({\n\t\tpacked: [packed.files, packed.filename === file, Fs.existsSync(file)],\n\t\tunpacked: [unpacked.files, unpacked.size, unpacked.path === target],\n\t\tcontent: [Fs.readFileSync(Path.join(target, 'a.txt'), 'utf8'), Fs.readFileSync(Path.join(target, 'sub', 'b.txt'), 'utf8')]\n\t});\n}\n\nasync function shell($) {\n\n\tconst output = await SHELL('echo hello');\n\tconst where = await SHELL('pwd', PATH.tmp());\n\n\tlet code = null;\n\ttry {\n\t\tawait SHELL('exit 3');\n\t} catch (e) {\n\t\tcode = e.code;\n\t}\n\n\tSHELL('echo from callback', (err, stdout) => $.json({ output: output, where: where.trim() === PATH.tmp() || where.trim().endsWith('/tmp'), code: code, callback: stdout }));\n}"}],"test":"const b = (await get('/backup/')).json;\nassert(b.packed[0] === 2 && b.packed[1] === true && b.packed[2] === true, 'BACKUP packs both files into the file, got: ' + JSON.stringify(b));\nassert(b.unpacked[0] === 2 && b.unpacked[1] === 10 && b.unpacked[2] === true, 'RESTORE unpacks the two files (10 bytes), got: ' + JSON.stringify(b));\nassert(b.content.join() === 'hello,world', 'the files come back with their content and folders, got: ' + JSON.stringify(b));\n\nconst s = (await get('/shell/')).json;\nassert(s.output === 'hello\\n', 'SHELL resolves with stdout, got: ' + JSON.stringify(s));\nassert(s.where === true, 'the last argument is the folder, got: ' + JSON.stringify(s));\nassert(s.code === 3, 'a failing command rejects with the exit code, got: ' + JSON.stringify(s));\nassert(s.callback === 'from callback\\n', 'the callback gets stdout, got: ' + JSON.stringify(s));","test_kind":"http"}
{"id":"total5/pause-server","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/pause-server/","markdown_url":"https://forge.totaljs.es/en/total5/pause-server.md","tags":["maintenance","server","availability"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"869cac7a28f190c1708e38b0ede74d61dbc5d4563f6d92517871a9bf2e3783a0","checked_at":"2026-10-20T12:18:31.914Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Put the application in maintenance with PAUSESERVER()","description":"Pause the server by name so that every request gets a 503 page, resume it, and know that without a second argument the call toggles.","messages":[{"role":"user","content":"Put the application in maintenance with PAUSESERVER()\n\nPause the server by name so that every request gets a 503 page, resume it, and know that without a second argument the call toggles."},{"role":"assistant","content":"`PAUSESERVER(name, true)` pauses the server and `PAUSESERVER(name, false)` resumes it. While **at least one name** is paused, every request is answered with the status **503** and the built-in maintenance page (a request made with `xhr` gets a 503 error answer instead of the page). The name only identifies who paused it, so that two parts of the application can pause and resume independently: the server answers again when all the names were resumed.\n\nWithout the second argument the call **toggles**: `PAUSESERVER(name)` pauses when the name was not paused and resumes when it was, and returns the new state (`true` when paused).\n\nBecause a paused server answers 503 to everything, including the route that would resume it, resume from your own code: a timer, a signal, a task that finishes. The example pauses for a short time and resumes by itself.\n\n```js controllers/maintenance.js\nexports.install = function() {\n\tROUTE('GET /maintenance/', maintenance);\n\tROUTE('GET /toggle/', toggle);\n\tROUTE('GET /ping/', ping);\n};\n\nfunction maintenance($) {\n\tconst state = PAUSESERVER('maintenance', true);\n\tsetTimeout(() => PAUSESERVER('maintenance', false), 600);\n\t$.json({ paused: state });\n}\n\nfunction toggle($) {\n\tconst first = PAUSESERVER('toggled');\n\tconst second = PAUSESERVER('toggled');\n\t$.json({ first: first, second: second });\n}\n\nfunction ping($) {\n\t$.json({ ok: true });\n}\n```"}],"knowledge":{"content_sha256":"9906e044fb40f45c1560ab417e7d4840431f15fe4159546344939e06b8075510","source_sha256":"869cac7a28f190c1708e38b0ede74d61dbc5d4563f6d92517871a9bf2e3783a0","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["GET","PAUSESERVER","ROUTE"],"language":"en"},"files":[{"path":"controllers/maintenance.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /maintenance/', maintenance);\n\tROUTE('GET /toggle/', toggle);\n\tROUTE('GET /ping/', ping);\n};\n\nfunction maintenance($) {\n\tconst state = PAUSESERVER('maintenance', true);\n\tsetTimeout(() => PAUSESERVER('maintenance', false), 600);\n\t$.json({ paused: state });\n}\n\nfunction toggle($) {\n\tconst first = PAUSESERVER('toggled');\n\tconst second = PAUSESERVER('toggled');\n\t$.json({ first: first, second: second });\n}\n\nfunction ping($) {\n\t$.json({ ok: true });\n}"}],"test":"const started = await get('/maintenance/');\nassert(started.json.paused === true, 'the call that pauses answers before the pause starts, got: ' + started.body);\n\nconst during = await get('/ping/');\nassert(during.status === 503, 'a paused server answers 503, got: ' + during.status + ' ' + during.body.substring(0, 80));\n\nconst xhr = await get('/ping/', { headers: { 'x-requested-with': 'XMLHttpRequest' } });\nassert(xhr.status === 503, 'an xhr request gets 503 too, got: ' + xhr.status);\n\nawait new Promise(resolve => setTimeout(resolve, 900));\nconst after = await get('/ping/');\nassert(after.status === 200 && after.json.ok === true, 'the server answers again after the name was resumed, got: ' + after.status + ' ' + after.body);\n\nconst toggled = (await get('/toggle/')).json;\nassert(toggled.first === true && toggled.second === false, 'without the second argument the call toggles and returns the new state, got: ' + JSON.stringify(toggled));","test_kind":"http"}
{"id":"total5/macros","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/macros/","markdown_url":"https://forge.totaljs.es/en/total5/macros.md","tags":["macros","rules","scripting"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"27b330bb07d4fee16f363e4d6e3808da53d102c942f5ad0439ad2e5b6c6eb3b4","checked_at":"2026-10-20T12:18:22.755Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Business rules as text with NEWMACRO()","description":"Compile small rules written in a simple language (IF, ELSE, AND, OR, RETURN, helper calls) into a function that you run with a model, so the rules can live in a file or a database.","messages":[{"role":"user","content":"Business rules as text with NEWMACRO()\n\nCompile small rules written in a simple language (IF, ELSE, AND, OR, RETURN, helper calls) into a function that you run with a model, so the rules can live in a file or a database."},{"role":"assistant","content":"`NEWMACRO(text, [nocompile], [isasync])` turns a few lines of a small language into a function `function(model, helpers)`. It is meant for rules that change more often than the code: a discount, a price, a permission check, a validation that someone who does not write JavaScript maintains in a file or in a database.\n\nThe language, as it was checked:\n\n- `IF condition`, `ELSE IF condition`, `ELSE` and `FI` (the end of the condition) work as in a text-based rule. Conditions use `>`, `<`, `>=`, `<=`, `=` and `AND` / `OR`.\n- `RETURN value` ends the macro with a value. `YES` and `NO` (also `TRUE`, `FALSE`, `OK`) are booleans, text between quotes is text and numbers are numbers.\n- A word is a **property of the model**, written in lower case: `age` reads `model.age`, and `total = price * qty` writes `model.total`.\n- `NAME(arguments)` calls a **helper**, the function with that name (lower case) in the `helpers` object you pass; inside it `this` is the model. With `isasync` set to `true` the macro is an async function and awaits the helpers.\n- `#name` is a temporary variable of the run: `#temp = num + 1` and `RETURN #temp`.\n- Use names of **several letters**: a property or a variable of one letter was not recognised in the checks.\n\n`NEWMACRO()` compiles the text with `new Function`, so a macro is code: only compile text that you wrote or that a trusted person maintains, never text that a visitor sent. With `nocompile` set to `true` it returns the JavaScript that it generates instead of the function, which is useful to see what a rule does.\n\n```js controllers/rules.js\nconst helpers = {\n\tpercent: function(value, rate) {\n\t\treturn value * rate / 100;\n\t}\n};\n\nconst age = NEWMACRO(\"IF age >= 18\\n\\tRETURN 'adult'\\nELSE\\n\\tRETURN 'minor'\\nFI\");\n\nconst level = NEWMACRO(\"IF points > 100\\n\\tRETURN 'gold'\\nELSE IF points > 50\\n\\tRETURN 'silver'\\nELSE\\n\\tRETURN 'bronze'\\nFI\");\n\nconst access = NEWMACRO(\"IF active AND score >= 5\\n\\tRETURN yes\\nFI\\nRETURN no\");\n\nconst total = NEWMACRO(\"total = price * qty\\nIF qty >= 10\\n\\ttotal = total - PERCENT(total, 10)\\nFI\\nRETURN total\");\n\nconst temporary = NEWMACRO(\"#next = count + 1\\nRETURN #next\");\n\nconst slow = NEWMACRO(\"RETURN LOOKUP(code)\", false, true);\n\nexports.install = function() {\n\tROUTE('POST /rules/', rules);\n\tROUTE('GET /source/', source);\n};\n\nasync function rules($) {\n\tconst model = $.body;\n\t$.json({\n\t\tage: age(model, helpers),\n\t\tlevel: level(model, helpers),\n\t\taccess: access(model, helpers),\n\t\ttotal: total(model, helpers),\n\t\tmodel_total: model.total,\n\t\ttemporary: temporary(model, helpers),\n\t\tslow: await slow(model, { lookup: async code => 'found ' + code })\n\t});\n}\n\nfunction source($) {\n\t$.plain(NEWMACRO(\"IF points > 100\\n\\tRETURN 'gold'\\nFI\", true));\n}\n```"}],"knowledge":{"content_sha256":"d802bfef67ede8fdf42be2807b173b3732349293d75dcc93f6bd3a325d48c417","source_sha256":"27b330bb07d4fee16f363e4d6e3808da53d102c942f5ad0439ad2e5b6c6eb3b4","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["AND","ELSE","FALSE","GET","LOOKUP","NAME","NEWMACRO","PERCENT","POST","RETURN","ROUTE","TRUE","YES"],"language":"en"},"files":[{"path":"controllers/rules.js","language":"js","content":"const helpers = {\n\tpercent: function(value, rate) {\n\t\treturn value * rate / 100;\n\t}\n};\n\nconst age = NEWMACRO(\"IF age >= 18\\n\\tRETURN 'adult'\\nELSE\\n\\tRETURN 'minor'\\nFI\");\n\nconst level = NEWMACRO(\"IF points > 100\\n\\tRETURN 'gold'\\nELSE IF points > 50\\n\\tRETURN 'silver'\\nELSE\\n\\tRETURN 'bronze'\\nFI\");\n\nconst access = NEWMACRO(\"IF active AND score >= 5\\n\\tRETURN yes\\nFI\\nRETURN no\");\n\nconst total = NEWMACRO(\"total = price * qty\\nIF qty >= 10\\n\\ttotal = total - PERCENT(total, 10)\\nFI\\nRETURN total\");\n\nconst temporary = NEWMACRO(\"#next = count + 1\\nRETURN #next\");\n\nconst slow = NEWMACRO(\"RETURN LOOKUP(code)\", false, true);\n\nexports.install = function() {\n\tROUTE('POST /rules/', rules);\n\tROUTE('GET /source/', source);\n};\n\nasync function rules($) {\n\tconst model = $.body;\n\t$.json({\n\t\tage: age(model, helpers),\n\t\tlevel: level(model, helpers),\n\t\taccess: access(model, helpers),\n\t\ttotal: total(model, helpers),\n\t\tmodel_total: model.total,\n\t\ttemporary: temporary(model, helpers),\n\t\tslow: await slow(model, { lookup: async code => 'found ' + code })\n\t});\n}\n\nfunction source($) {\n\t$.plain(NEWMACRO(\"IF points > 100\\n\\tRETURN 'gold'\\nFI\", true));\n}"}],"test":"const a = (await post('/rules/', { age: 20, points: 120, active: true, score: 7, price: 4, qty: 3, count: 4, code: 'X1' })).json;\nassert(a.age === 'adult' && a.level === 'gold', 'IF, ELSE and RETURN, got: ' + JSON.stringify(a));\nassert(a.access === true, 'AND with YES and NO, got: ' + JSON.stringify(a));\nassert(a.total === 12 && a.model_total === 12, 'an assignment writes the model and RETURN gives the value, got: ' + JSON.stringify(a));\nassert(a.temporary === 5, 'a #temporary variable, got: ' + JSON.stringify(a));\nassert(a.slow === 'found X1', 'with isasync the macro awaits the helpers, got: ' + JSON.stringify(a));\n\nconst b = (await post('/rules/', { age: 10, points: 70, active: true, score: 2, price: 5, qty: 10, count: 0, code: 'Y2' })).json;\nassert(b.age === 'minor' && b.level === 'silver', 'ELSE and ELSE IF, got: ' + JSON.stringify(b));\nassert(b.access === false, 'the condition is false so RETURN no runs, got: ' + JSON.stringify(b));\nassert(b.total === 45, 'a helper is called with arguments (50 minus 10 percent), got: ' + JSON.stringify(b));\n\nconst c = (await post('/rules/', { age: 30, points: 10, active: false, score: 9, price: 1, qty: 1, count: 1, code: 'Z' })).json;\nassert(c.level === 'bronze', 'the last ELSE, got: ' + JSON.stringify(c));\n\nconst source = await get('/source/');\nassert(source.body.includes('if(model.points>100)') && source.body.includes('return \"gold\"'), 'nocompile returns the JavaScript, got: ' + source.body);","test_kind":"http"}
{"id":"total5/static-files","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/static-files/","markdown_url":"https://forge.totaljs.es/en/total5/static-files.md","tags":["static","files"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"91db733a335ac706d785d9a56648f84cd54a024077a09e79d6814b97a5b69009","checked_at":"2026-10-20T12:18:49.673Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Serve static files from the public folder","description":"Files in the public directory are served automatically, no route required.","messages":[{"role":"user","content":"Serve static files from the public folder\n\nFiles in the public directory are served automatically, no route required."},{"role":"assistant","content":"Everything inside the `public` directory is served as a static file, and the path inside the folder becomes the URL. No route is needed: `public/hello.txt` is available at `/hello.txt`.\n\n```text public/hello.txt\nHello from the public folder\n```"}],"knowledge":{"content_sha256":"01f26d711f253f04cfbb130f67bd1276c91264e83102714f80c7553ad9a9bb4d","source_sha256":"91db733a335ac706d785d9a56648f84cd54a024077a09e79d6814b97a5b69009","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["URL"],"language":"en"},"files":[{"path":"public/hello.txt","language":"text","content":"Hello from the public folder"}],"test":"const file = await get('/hello.txt');\nassert(file.status === 200, 'file is served (got ' + file.status + ')');\nassert(file.body.trim() === 'Hello from the public folder', 'content matches');\n\nconst missing = await get('/missing.txt');\nassert(missing.status === 404, 'a missing file returns 404');","test_kind":"http"}
{"id":"total5/ajax-requests","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/ajax-requests/","markdown_url":"https://forge.totaljs.es/en/total5/ajax-requests.md","tags":["http","requests","ajax"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"b515d47b7e0c45989e7d223d028a4e9302ccab1a91415592a197c129e87f163b","checked_at":"2026-10-20T12:17:45.039Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Call another address with AJAX() from the server","description":"Use AJAX() to call an HTTP address with a JSON body and get the parsed answer, as a promise or with a callback, and know what happens with errors and XML.","messages":[{"role":"user","content":"Call another address with AJAX() from the server\n\nUse AJAX() to call an HTTP address with a JSON body and get the parsed answer, as a promise or with a callback, and know what happens with errors and XML."},{"role":"assistant","content":"`AJAX('METHOD url', [data], [callback])` makes an HTTP request and gives you the **parsed** answer. It is the short form of `REQUEST()` for APIs that talk JSON: the same call that jComponent has in the browser, so the code of the page and of the server look alike.\n\n- The address starts with an optional method: `AJAX('POST https://example.com/api/', data)`. Without a method it is a `GET`.\n- `data`, when given, is sent as a **JSON body**. The request carries the header `x-requested-with: XMLHttpRequest`, so on the other side `$.xhr` is `true`.\n- The answer is parsed by its content type: JSON becomes an object, `application/x-www-form-urlencoded` becomes an object, anything else arrives as text.\n- When the status is 400 or more, the promise is **rejected** (or the callback gets the error first) with the parsed body of the answer as the error. A callback is called as `callback(err, value, response)`.\n- Without a callback it returns a promise.\n\n**For XML answers use `REQUEST()`** and parse the body yourself: `AJAX()` is made for JSON and text answers.\n\nThe example calls routes of the same application, whose address `$.hostname('/')` gives, so that it needs no other server.\n\n```js controllers/calls.js\nexports.install = function() {\n\tROUTE('GET /api/hello/', hello);\n\tROUTE('POST /api/echo/', echo);\n\tROUTE('GET /api/text/', text);\n\tROUTE('GET /api/xml/', xml);\n\tROUTE('GET /api/fail/', fail);\n\tROUTE('GET /calls/', calls);\n};\n\nfunction hello($) {\n\t$.json({ hello: 'world', xhr: $.xhr });\n}\n\nfunction echo($) {\n\t$.json({ received: $.body, xhr: $.xhr });\n}\n\nfunction text($) {\n\t$.plain('plain text');\n}\n\nfunction xml($) {\n\t$.xml('<a><b>1</b></a>');\n}\n\nfunction fail($) {\n\t$.invalid('It is wrong');\n}\n\nasync function calls($) {\n\n\tconst base = $.hostname('/');\n\tconst out = {};\n\n\tout.hello = await AJAX('GET ' + base + 'api/hello/');\n\tout.nomethod = await AJAX(base + 'api/hello/');\n\tout.echo = await AJAX('POST ' + base + 'api/echo/', { a: 1 });\n\tout.text = await AJAX(base + 'api/text/');\n\n\ttry {\n\t\tawait AJAX(base + 'api/fail/');\n\t} catch (err) {\n\t\tout.error = err.message || err;\n\t}\n\n\tout.callback = await new Promise(resolve => AJAX(base + 'api/hello/', (err, value, response) => resolve({ err: err, hello: value.hello, status: response.status })));\n\n\tout.xml = await Promise.race([\n\t\tAJAX(base + 'api/xml/').then(() => 'answered', () => 'rejected'),\n\t\tnew Promise(resolve => setTimeout(() => resolve('never answered'), 1000))\n\t]);\n\n\t$.json(out);\n}\n```"}],"knowledge":{"content_sha256":"761c3549df81c70a95b4da31e4770d745833c6240cfc45d2c310f79cd7d43d8f","source_sha256":"b515d47b7e0c45989e7d223d028a4e9302ccab1a91415592a197c129e87f163b","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["AJAX","API","GET","HTTP","JSON","METHOD","POST","REQUEST","ROUTE","XML","XMLH"],"language":"en"},"files":[{"path":"controllers/calls.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /api/hello/', hello);\n\tROUTE('POST /api/echo/', echo);\n\tROUTE('GET /api/text/', text);\n\tROUTE('GET /api/xml/', xml);\n\tROUTE('GET /api/fail/', fail);\n\tROUTE('GET /calls/', calls);\n};\n\nfunction hello($) {\n\t$.json({ hello: 'world', xhr: $.xhr });\n}\n\nfunction echo($) {\n\t$.json({ received: $.body, xhr: $.xhr });\n}\n\nfunction text($) {\n\t$.plain('plain text');\n}\n\nfunction xml($) {\n\t$.xml('<a><b>1</b></a>');\n}\n\nfunction fail($) {\n\t$.invalid('It is wrong');\n}\n\nasync function calls($) {\n\n\tconst base = $.hostname('/');\n\tconst out = {};\n\n\tout.hello = await AJAX('GET ' + base + 'api/hello/');\n\tout.nomethod = await AJAX(base + 'api/hello/');\n\tout.echo = await AJAX('POST ' + base + 'api/echo/', { a: 1 });\n\tout.text = await AJAX(base + 'api/text/');\n\n\ttry {\n\t\tawait AJAX(base + 'api/fail/');\n\t} catch (err) {\n\t\tout.error = err.message || err;\n\t}\n\n\tout.callback = await new Promise(resolve => AJAX(base + 'api/hello/', (err, value, response) => resolve({ err: err, hello: value.hello, status: response.status })));\n\n\tout.xml = await Promise.race([\n\t\tAJAX(base + 'api/xml/').then(() => 'answered', () => 'rejected'),\n\t\tnew Promise(resolve => setTimeout(() => resolve('never answered'), 1000))\n\t]);\n\n\t$.json(out);\n}"}],"test":"const r = (await get('/calls/')).json;\nassert(r.hello.hello === 'world' && r.hello.xhr === true, 'a JSON answer is parsed and the request is an xhr, got: ' + JSON.stringify(r.hello));\nassert(r.nomethod.hello === 'world', 'without a method it is a GET, got: ' + JSON.stringify(r.nomethod));\nassert(r.echo.received.a === 1 && r.echo.xhr === true, 'data is sent as a JSON body, got: ' + JSON.stringify(r.echo));\nassert(r.text === 'plain text', 'a text answer arrives as text, got: ' + JSON.stringify(r.text));\nassert(r.error === 'It is wrong', 'a status of 400 or more rejects with the body of the answer, got: ' + JSON.stringify(r.error));\nassert(r.callback.err === null && r.callback.hello === 'world' && r.callback.status === 200, 'the callback gets (err, value, response), got: ' + JSON.stringify(r.callback));\nassert(r.xml === 'never answered', 'an XML answer never settles in this version, got: ' + JSON.stringify(r.xml));","test_kind":"http"}
{"id":"total5/restbuilder","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/restbuilder/","markdown_url":"https://forge.totaljs.es/en/total5/restbuilder.md","tags":["integrations","http","restbuilder"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"3513fd8391a8acbb2e6fb07309941621cee190f65234e2148fb7b3f7ac782a3c","checked_at":"2026-10-20T12:18:43.131Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Call other HTTP APIs with RESTBuilder","description":"Make GET and POST requests to another service, set headers and a timeout, and read the status and the parsed response.","messages":[{"role":"user","content":"Call other HTTP APIs with RESTBuilder\n\nMake GET and POST requests to another service, set headers and a timeout, and read the status and the parsed response."},{"role":"assistant","content":"`RESTBuilder.GET(url)`, `.POST(url, data)`, `.PUT`, `.PATCH` and `.DELETE` build a request. Data of a POST is sent as JSON. Chain `.header(name, value)`, `.cookie(name, value)`, `.timeout(milliseconds)` and `.auth(user, password)` before `.exec(function(err, response, output) {})` (or `.promise()` for `await`).\n\n- `response` is the body, parsed automatically when it is JSON\n- `output.status` is the HTTP status and `output.headers` the response headers\n- `err` is set for network errors and for HTTP error statuses\n\nThe example calls two routes of its own app so it can run anywhere; use the address of the real service in your code. `$.hostname(path)` builds the full URL of this server.\n\n```js controllers/service.js\nexports.install = function() {\n\tROUTE('GET /api/hello/', hello);\n\tROUTE('POST /api/sum/', sum);\n\tROUTE('GET /call/', call);\n};\n\nfunction hello($) {\n\t$.json({ message: 'hello', agent: $.headers['x-agent'] || null });\n}\n\nfunction sum($) {\n\t$.json({ sum: $.body.a + $.body.b });\n}\n\nasync function call($) {\n\n\tvar greeting = await RESTBuilder.GET($.hostname('/api/hello/')).header('x-agent', 'forge').timeout(5000).promise();\n\tvar total = await RESTBuilder.POST($.hostname('/api/sum/'), { a: 20, b: 22 }).promise();\n\n\tRESTBuilder.GET($.hostname('/api/missing/')).exec(function(err, response, output) {\n\t\t$.json({ greeting: greeting, total: total, missing: output.status });\n\t});\n}\n```"}],"knowledge":{"content_sha256":"12c13b9eec2ed90f374077ed6b677b5de7172b1bd97ef4b605591216bfc1e24c","source_sha256":"3513fd8391a8acbb2e6fb07309941621cee190f65234e2148fb7b3f7ac782a3c","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["DELETE","GET","HTTP","JSON","PATCH","POST","PUT","RESTB","ROUTE","URL"],"language":"en"},"files":[{"path":"controllers/service.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /api/hello/', hello);\n\tROUTE('POST /api/sum/', sum);\n\tROUTE('GET /call/', call);\n};\n\nfunction hello($) {\n\t$.json({ message: 'hello', agent: $.headers['x-agent'] || null });\n}\n\nfunction sum($) {\n\t$.json({ sum: $.body.a + $.body.b });\n}\n\nasync function call($) {\n\n\tvar greeting = await RESTBuilder.GET($.hostname('/api/hello/')).header('x-agent', 'forge').timeout(5000).promise();\n\tvar total = await RESTBuilder.POST($.hostname('/api/sum/'), { a: 20, b: 22 }).promise();\n\n\tRESTBuilder.GET($.hostname('/api/missing/')).exec(function(err, response, output) {\n\t\t$.json({ greeting: greeting, total: total, missing: output.status });\n\t});\n}"}],"test":"const res = await get('/call/');\nassert(res.status === 200, 'the route answers, got: ' + res.status + ' ' + res.body);\nassert(res.json && res.json.greeting.message === 'hello', 'GET returns the parsed JSON, got: ' + res.body);\nassert(res.json && res.json.greeting.agent === 'forge', 'the header was sent');\nassert(res.json && res.json.total.sum === 42, 'POST sends JSON and parses the answer');\nassert(res.json && res.json.missing === 404, 'output.status holds the HTTP status, got: ' + res.body);","test_kind":"http"}
{"id":"total5/cleanup-streams","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/cleanup-streams/","markdown_url":"https://forge.totaljs.es/en/total5/cleanup-streams.md","tags":["streams","files","cleanup"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"a62e865440d0791020e5ddf4ab6b7f8d10379488ca706ab69cfeec30c81a6d37","checked_at":"2026-10-20T12:17:51.714Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Release a stream when it ends with CLEANUP()","description":"Use CLEANUP() to destroy a stream as soon as it has finished or failed, so that file handles are not left open, with a callback or as a promise.","messages":[{"role":"user","content":"Release a stream when it ends with CLEANUP()\n\nUse CLEANUP() to destroy a stream as soon as it has finished or failed, so that file handles are not left open, with a callback or as a promise."},{"role":"assistant","content":"`CLEANUP(stream, [callback])` waits until a stream has **finished** (it ended, it was closed or it failed) and then destroys it, which frees the file handle or the socket behind it. With a callback it is called after that; without one it returns a promise that resolves at that moment. It is the call to make after you hand a stream to something that reads it (a response, a pipe, an upload) when you want to be sure that nothing stays open if the other side stops early.\n\n```js controllers/streams.js\nconst Fs = require('fs');\nconst { PassThrough } = require('stream');\n\nexports.install = function() {\n\tROUTE('GET /copy/', copy);\n\tROUTE('GET /copy-callback/', copycallback);\n};\n\nasync function copy($) {\n\n\tconst file = PATH.tmp('cleanup.txt');\n\tFs.writeFileSync(file, 'some text to read');\n\n\tconst stream = Fs.createReadStream(file);\n\tconst chunks = [];\n\n\t// a sink that reads everything\n\tconst sink = new PassThrough();\n\tsink.on('data', chunk => chunks.push(chunk));\n\tstream.pipe(sink);\n\n\tconst before = stream.destroyed;\n\tawait CLEANUP(stream);\n\n\t$.json({ before: before, after: stream.destroyed, content: Buffer.concat(chunks).toString() });\n}\n\nfunction copycallback($) {\n\n\tconst file = PATH.tmp('cleanup.txt');\n\tFs.writeFileSync(file, 'again');\n\n\tconst stream = Fs.createReadStream(file);\n\tstream.resume();\n\n\tCLEANUP(stream, () => $.json({ destroyed: stream.destroyed }));\n}\n```"}],"knowledge":{"content_sha256":"dcaf443c6e5b758eb7c3a4ea92ba9c0289551a3b46dcb0c1e3d642c51e142f37","source_sha256":"a62e865440d0791020e5ddf4ab6b7f8d10379488ca706ab69cfeec30c81a6d37","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["CLEANUP","GET","PATH","ROUTE"],"language":"en"},"files":[{"path":"controllers/streams.js","language":"js","content":"const Fs = require('fs');\nconst { PassThrough } = require('stream');\n\nexports.install = function() {\n\tROUTE('GET /copy/', copy);\n\tROUTE('GET /copy-callback/', copycallback);\n};\n\nasync function copy($) {\n\n\tconst file = PATH.tmp('cleanup.txt');\n\tFs.writeFileSync(file, 'some text to read');\n\n\tconst stream = Fs.createReadStream(file);\n\tconst chunks = [];\n\n\t// a sink that reads everything\n\tconst sink = new PassThrough();\n\tsink.on('data', chunk => chunks.push(chunk));\n\tstream.pipe(sink);\n\n\tconst before = stream.destroyed;\n\tawait CLEANUP(stream);\n\n\t$.json({ before: before, after: stream.destroyed, content: Buffer.concat(chunks).toString() });\n}\n\nfunction copycallback($) {\n\n\tconst file = PATH.tmp('cleanup.txt');\n\tFs.writeFileSync(file, 'again');\n\n\tconst stream = Fs.createReadStream(file);\n\tstream.resume();\n\n\tCLEANUP(stream, () => $.json({ destroyed: stream.destroyed }));\n}"}],"test":"const a = (await get('/copy/')).json;\nassert(a.before === false && a.after === true, 'the stream is destroyed after it finished, got: ' + JSON.stringify(a));\nassert(a.content === 'some text to read', 'the data was read before that, got: ' + JSON.stringify(a));\n\nconst b = (await get('/copy-callback/')).json;\nassert(b.destroyed === true, 'the callback runs after the stream was destroyed, got: ' + JSON.stringify(b));","test_kind":"http"}
{"id":"total5/localization","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/localization/","markdown_url":"https://forge.totaljs.es/en/total5/localization.md","tags":["localization","i18n","views"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"44ea354d0edb8f1e4dac9d810ac619928ed026230cb6d03f752d1cac89bdc701","checked_at":"2026-10-20T12:18:21.298Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Translate a view into several languages","description":"Mark texts with @(...) in views and provide translations in resource files selected by LOCALIZE().","messages":[{"role":"user","content":"Translate a view into several languages\n\nMark texts with @(...) in views and provide translations in resource files selected by LOCALIZE()."},{"role":"assistant","content":"Wrap any text of a view in `@(...)` and Total.js replaces it with the translation for the current language. Translations live in `resources/<language>.resource`; each line is `key : translation`. The key is `T` plus the hash of the original text in base 36, which `total5` can generate for you. Texts without a translation stay as written, so the view text acts as the default language.\n\n`LOCALIZE()` decides the language of each request. Here it is read from the query string, but it can be a cookie, a header or a URL segment.\n\n```js definitions/localization.js\nLOCALIZE(function($) {\n\treturn $.query.lang;\n});\n```\n\n```text resources/sk.resource\n// Total.js translation file\nT4oxobw : Vitajte v obchode\n```\n\n```html views/index.html\n@{layout('')}\n<h1>@(Welcome to the shop)</h1>\n```\n\n```js controllers/default.js\nexports.install = function() {\n\tROUTE('GET /', home);\n};\n\nfunction home($) {\n\t$.view('index');\n}\n```"}],"knowledge":{"content_sha256":"46d3cf632dac9d720b148f223066319e74bd1173b10dbf1d0d75d85facde901e","source_sha256":"44ea354d0edb8f1e4dac9d810ac619928ed026230cb6d03f752d1cac89bdc701","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["GET","LOCALIZE","ROUTE","Total.js","URL"],"language":"en"},"files":[{"path":"definitions/localization.js","language":"js","content":"LOCALIZE(function($) {\n\treturn $.query.lang;\n});"},{"path":"resources/sk.resource","language":"text","content":"// Total.js translation file\nT4oxobw : Vitajte v obchode"},{"path":"views/index.html","language":"html","content":"@{layout('')}\n<h1>@(Welcome to the shop)</h1>"},{"path":"controllers/default.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /', home);\n};\n\nfunction home($) {\n\t$.view('index');\n}"}],"test":"const english = await get('/');\nassert(english.body.includes('Welcome to the shop'), 'default language shows the original text');\n\nconst slovak = await get('/?lang=sk');\nassert(slovak.body.includes('Vitajte v obchode'), 'sk language shows the translation (got ' + slovak.body.trim() + ')');","test_kind":"http"}
{"id":"total5/cron-jobs","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/cron-jobs/","markdown_url":"https://forge.totaljs.es/en/total5/cron-jobs.md","tags":["cron","scheduler","jobs"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"69c78c33ca0b27d088349e5a5a7f180ec348f9c63cf78184c8c2d2b6b480be6f","checked_at":"2026-10-20T12:17:57.887Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Run code on a schedule with CRON","description":"Register a function with CRON() and a five-field cron line, and know when the check runs and how to remove a job.","messages":[{"role":"user","content":"Run code on a schedule with CRON\n\nRegister a function with CRON() and a five-field cron line, and know when the check runs and how to remove a job."},{"role":"assistant","content":"`CRON(line, fn)` runs `fn(date)` whenever the current time matches `line`. The line has five fields, as in Unix cron: `minute hour day-of-month month day-of-week`. Each field accepts `*` (any value), `1,5,9` (a list), `1-5` (a range) or an exact number. Day names such as `mon` or `fri` work in the last field.\n\n**Write the values as a list**, `0,15,30,45`: it matches exactly the times you name.\n\nTotal.js checks the registered jobs once a minute, so the finest resolution is one minute. `CRON()` returns an object: `job.remove()` unregisters it and `job.check(date)` tells you whether a date matches the line, which is also the easiest way to test a schedule without waiting for it.\n\n```js controllers/cron.js\nexports.install = function() {\n\tROUTE('GET /cron/matches/', matches);\n\tROUTE('GET /cron/runs/', runs);\n};\n\nvar counter = { runs: 0 };\n\n// Every 15 minutes, from Monday to Friday, between 09:00 and 17:59\nvar job = CRON('0,15,30,45 9-17 * * 1-5', function(date) {\n\tcounter.runs++;\n});\n\nfunction matches($) {\n\n\tvar dates = {\n\t\tmonday_0930: new Date(2026, 0, 5, 9, 30),\n\t\tmonday_0931: new Date(2026, 0, 5, 9, 31),\n\t\tmonday_1830: new Date(2026, 0, 5, 18, 30),\n\t\tsunday_0930: new Date(2026, 0, 4, 9, 30)\n\t};\n\n\tvar output = {};\n\tfor (var key in dates)\n\t\toutput[key] = job.check(dates[key]);\n\n\t$.json(output);\n}\n\nfunction runs($) {\n\t$.json(counter);\n}\n```"}],"knowledge":{"content_sha256":"1e42e869db5a82463754c80c867472d8194d27b6f0858093ca51ce16e18ded54","source_sha256":"69c78c33ca0b27d088349e5a5a7f180ec348f9c63cf78184c8c2d2b6b480be6f","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["CRON","GET","ROUTE","Total.js"],"language":"en"},"files":[{"path":"controllers/cron.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /cron/matches/', matches);\n\tROUTE('GET /cron/runs/', runs);\n};\n\nvar counter = { runs: 0 };\n\n// Every 15 minutes, from Monday to Friday, between 09:00 and 17:59\nvar job = CRON('0,15,30,45 9-17 * * 1-5', function(date) {\n\tcounter.runs++;\n});\n\nfunction matches($) {\n\n\tvar dates = {\n\t\tmonday_0930: new Date(2026, 0, 5, 9, 30),\n\t\tmonday_0931: new Date(2026, 0, 5, 9, 31),\n\t\tmonday_1830: new Date(2026, 0, 5, 18, 30),\n\t\tsunday_0930: new Date(2026, 0, 4, 9, 30)\n\t};\n\n\tvar output = {};\n\tfor (var key in dates)\n\t\toutput[key] = job.check(dates[key]);\n\n\t$.json(output);\n}\n\nfunction runs($) {\n\t$.json(counter);\n}"}],"test":"const result = await get('/cron/matches/');\nassert(result.json, 'expected JSON, got: ' + result.body);\nassert(result.json.monday_0930 === true, 'Monday 09:30 matches, got: ' + result.body);\nassert(result.json.monday_0931 === false, 'minute 31 is not in the list, got: ' + result.body);\nassert(result.json.monday_1830 === false, 'hour 18 is outside 9-17, got: ' + result.body);\nassert(result.json.sunday_0930 === false, 'Sunday is outside 1-5, got: ' + result.body);\n\nconst runs = await get('/cron/runs/');\nassert(runs.json && typeof runs.json.runs === 'number', 'the job counter exists, got: ' + runs.body);","test_kind":"http"}
{"id":"total5/workers-and-threads","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/workers-and-threads/","markdown_url":"https://forge.totaljs.es/en/total5/workers-and-threads.md","tags":["worker","thread","performance"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"9fc27f77999eb235286bfe8d6a3aba0aa8816174be2f2726324102457312d732","checked_at":"2026-10-20T12:19:08.212Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Run heavy work in a thread or a child process","description":"Use NEWTHREAD() to start a worker thread, send it messages and read the replies, without blocking the main process.","messages":[{"role":"user","content":"Run heavy work in a thread or a child process\n\nUse NEWTHREAD() to start a worker thread, send it messages and read the replies, without blocking the main process."},{"role":"assistant","content":"Node.js runs your code on one thread, so a long calculation blocks every request. `NEWTHREAD(name, data)` starts `workers/<name>.js` in a worker thread and returns a standard Node.js `Worker`: send it messages with `worker.postMessage(value)`, receive replies with `worker.on('message', fn)`, and stop it with `worker.kill()`. The second argument (`data`) is handed to the worker as its starting data. `NEWFORK(name)` has the same shape but starts a separate child process, which is isolated from the main process (a crash in it does not affect your app).\n\nInside the worker file, `NEWTHREAD()` without arguments returns the worker handle: `worker.data` holds the starting data, `worker.message = fn` receives messages and `worker.postMessage(value)` answers. Pass a name prefixed with `~` to use a file by path instead of `workers/<name>.js`.\n\n```js workers/sum.js\nrequire('total5');\n\nconst worker = NEWTHREAD();\nconst start = worker.data.start;\n\nworker.message = function(message) {\n\tlet total = start;\n\tfor (let i = 1; i <= message.to; i++)\n\t\ttotal += i;\n\tworker.postMessage({ total: total });\n};\n```\n\n```js controllers/sum.js\nexports.install = function() {\n\tROUTE('GET /sum/{to}/', sum);\n};\n\nvar thread = NEWTHREAD('sum', { start: 1000 });\n\nfunction sum($) {\n\tvar to = +$.params.to;\n\tvar timeout = setTimeout(() => $.invalid('timeout'), 4000);\n\tthread.once('message', function(response) {\n\t\tclearTimeout(timeout);\n\t\t$.json(response);\n\t});\n\tthread.postMessage({ to: to });\n}\n```"}],"knowledge":{"content_sha256":"6be8eed8f5c6fc8d1a9a88a066519dbcd1b8884933677a534fc44d7c4d95f0d1","source_sha256":"9fc27f77999eb235286bfe8d6a3aba0aa8816174be2f2726324102457312d732","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["GET","NEWFORK","NEWTHREAD","ROUTE"],"language":"en"},"files":[{"path":"workers/sum.js","language":"js","content":"require('total5');\n\nconst worker = NEWTHREAD();\nconst start = worker.data.start;\n\nworker.message = function(message) {\n\tlet total = start;\n\tfor (let i = 1; i <= message.to; i++)\n\t\ttotal += i;\n\tworker.postMessage({ total: total });\n};"},{"path":"controllers/sum.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /sum/{to}/', sum);\n};\n\nvar thread = NEWTHREAD('sum', { start: 1000 });\n\nfunction sum($) {\n\tvar to = +$.params.to;\n\tvar timeout = setTimeout(() => $.invalid('timeout'), 4000);\n\tthread.once('message', function(response) {\n\t\tclearTimeout(timeout);\n\t\t$.json(response);\n\t});\n\tthread.postMessage({ to: to });\n}"}],"test":"const result = await get('/sum/100/');\nassert(result.json && result.json.total === 1000 + 5050, 'the worker adds 1..100 to the start value, got: ' + result.body);","test_kind":"http"}
{"id":"total5/thread-pool","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/thread-pool/","markdown_url":"https://forge.totaljs.es/en/total5/thread-pool.md","tags":["worker","thread","pool","performance"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"1a23de78a45735d41f785690eac94ee299ca3920c96b9485328a7eaaba3b08c6","checked_at":"2026-10-20T12:18:53.819Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Share a few worker threads with NEWTHREADPOOL()","description":"Start a fixed number of workers, borrow one with pool.exec() when there is work, give it back with release(), and let the other jobs wait for a free worker.","messages":[{"role":"user","content":"Share a few worker threads with NEWTHREADPOOL()\n\nStart a fixed number of workers, borrow one with pool.exec() when there is work, give it back with release(), and let the other jobs wait for a free worker."},{"role":"assistant","content":"`NEWTHREAD()` starts one worker. When many requests need the same heavy job, one worker per request is too many and a single worker makes them wait for each other without any order. `NEWTHREADPOOL(name, count, [isfork])` starts `count` workers of `workers/<name>.js` (a thread; with `isfork` set to `true` a child process, like `NEWFORK()`) and hands them out one at a time.\n\n- `pool.exec(function(worker, release) {})` waits until a worker is free and calls your function with it. Send it the work with `worker.postMessage(value)`, read the answer with `worker.once('message', fn)` and **call `release()` when you are done**: the worker goes back to the pool and the next waiting job takes it.\n- Without a function, `pool.exec()` returns a promise that resolves with `worker` (and the worker has `worker.release()`), so it works with `await`.\n- The jobs are served in the order in which they asked. The pool does not queue the messages of a worker: one job at a time has it.\n- The pool is also kept in `F.workers[name]`. `pool.workers` is the list of the workers.\n\nThe worker file is the same as for `NEWTHREAD()`: `NEWTHREAD()` inside it returns the handle with `worker.message` and `worker.postMessage()`. Here every worker adds up the numbers and says which thread did it.\n\n```js workers/total.js\nrequire('total5');\n\nconst worker = NEWTHREAD();\n\nworker.message = function(message) {\n\tlet total = 0;\n\tfor (let i = 1; i <= message.to; i++)\n\t\ttotal += i;\n\tworker.postMessage({ to: message.to, total: total, thread: require('worker_threads').threadId });\n};\n```\n\n```js controllers/total.js\nexports.install = function() {\n\tROUTE('GET /total/{to}/', total);\n\tROUTE('GET /await/{to}/', total_await);\n\tROUTE('GET /workers/', workers);\n};\n\nconst pool = NEWTHREADPOOL('total', 2);\n\nfunction total($) {\n\tpool.exec(function(worker, release) {\n\t\tworker.once('message', function(response) {\n\t\t\trelease();\n\t\t\t$.json(response);\n\t\t});\n\t\tworker.postMessage({ to: +$.params.to });\n\t});\n}\n\nasync function total_await($) {\n\tconst worker = await pool.exec();\n\tworker.once('message', function(response) {\n\t\tworker.release();\n\t\t$.json(response);\n\t});\n\tworker.postMessage({ to: +$.params.to });\n}\n\nfunction workers($) {\n\t$.json({ workers: pool.workers.length, registered: F.workers.total === pool });\n}\n```"}],"knowledge":{"content_sha256":"6fbb579ec2a9b0a43ee3b6a86ea83738bde754d17a73392805d15250c0db98d6","source_sha256":"1a23de78a45735d41f785690eac94ee299ca3920c96b9485328a7eaaba3b08c6","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["GET","NEWFORK","NEWTHREAD","NEWTHREADPOOL","ROUTE"],"language":"en"},"files":[{"path":"workers/total.js","language":"js","content":"require('total5');\n\nconst worker = NEWTHREAD();\n\nworker.message = function(message) {\n\tlet total = 0;\n\tfor (let i = 1; i <= message.to; i++)\n\t\ttotal += i;\n\tworker.postMessage({ to: message.to, total: total, thread: require('worker_threads').threadId });\n};"},{"path":"controllers/total.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /total/{to}/', total);\n\tROUTE('GET /await/{to}/', total_await);\n\tROUTE('GET /workers/', workers);\n};\n\nconst pool = NEWTHREADPOOL('total', 2);\n\nfunction total($) {\n\tpool.exec(function(worker, release) {\n\t\tworker.once('message', function(response) {\n\t\t\trelease();\n\t\t\t$.json(response);\n\t\t});\n\t\tworker.postMessage({ to: +$.params.to });\n\t});\n}\n\nasync function total_await($) {\n\tconst worker = await pool.exec();\n\tworker.once('message', function(response) {\n\t\tworker.release();\n\t\t$.json(response);\n\t});\n\tworker.postMessage({ to: +$.params.to });\n}\n\nfunction workers($) {\n\t$.json({ workers: pool.workers.length, registered: F.workers.total === pool });\n}"}],"test":"const info = await get('/workers/');\nassert(info.json && info.json.workers === 2 && info.json.registered === true, 'two workers, registered in F.workers, got: ' + info.body);\n\n// six jobs at the same time on two workers: all of them are answered, and only two threads did the work\nconst jobs = await Promise.all([10, 20, 30, 40, 50, 60].map(n => get('/total/' + n + '/')));\nfor (const job of jobs)\n\tassert(job.json && job.json.total === job.json.to * (job.json.to + 1) / 2, 'each job gets its own total, got: ' + job.body);\nconst threads = new Set(jobs.map(n => n.json.thread));\nassert(threads.size === 2, 'the six jobs were done by two threads, got: ' + Array.from(threads).join(','));\n\nconst waited = await get('/await/100/');\nassert(waited.json && waited.json.total === 5050, 'pool.exec() without a function returns a promise, got: ' + waited.body);","test_kind":"http"}
{"id":"total5/request-and-download","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/request-and-download/","markdown_url":"https://forge.totaljs.es/en/total5/request-and-download.md","tags":["http","request","download"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"9eb9380ee4bcf54af32aca81b19c6fa28b3a976ea9570105755ca7f9f12ff0c0","checked_at":"2026-10-20T12:18:40.956Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Call other servers with REQUEST and save files with DOWNLOAD","description":"Use REQUEST() for low-level HTTP calls with full control over method, headers, query and body, and DOWNLOAD() to stream a remote file to disk.","messages":[{"role":"user","content":"Call other servers with REQUEST and save files with DOWNLOAD\n\nUse REQUEST() for low-level HTTP calls with full control over method, headers, query and body, and DOWNLOAD() to stream a remote file to disk."},{"role":"assistant","content":"`REQUEST(options)` is the low-level HTTP client that `RESTBuilder` is built on. Pass an object and `await` it (or give it a callback as the second argument). The useful options are `url`, `method` (default `GET`), `query` (an object, appended to the URL), `headers`, `cookies`, `body` (a string or a Buffer), `type` (`json`, `text`, `html`, `xml`, `urlencoded` or `raw`, which sets `content-type`), `timeout` in milliseconds and `noredirect`.\n\nThe result is an object with `status`, `headers`, `cookies` and `body` (a string; parse JSON yourself with `.parseJSON()`). A response with an error status such as 404 is **not** an error: check `status`. The promise only rejects when the request itself fails (connection refused, timeout, invalid host).\n\n`DOWNLOAD(url, filename)` streams the response into a file and resolves with `{ status, size, filename }`. It does not look at the status, so check it before using the file.\n\n```js controllers/request.js\nexports.install = function() {\n\tROUTE('GET /remote/', remote);\n\tROUTE('POST /remote/ @json', remote_post);\n\tROUTE('GET /remote/text/', remote_text);\n\tROUTE('GET /call/', call);\n\tROUTE('GET /download/', download);\n};\n\nfunction remote($) {\n\t$.json({ hello: 'world', name: $.query.name || null });\n}\n\nfunction remote_post($) {\n\t$.json({ received: $.body, token: $.headers['x-token'] || null });\n}\n\nfunction remote_text($) {\n\t$.plain('first line\\nsecond line\\n');\n}\n\nasync function call($) {\n\n\tvar get = await REQUEST({ url: $.hostname('/remote/'), query: { name: 'Louis' } });\n\tvar post = await REQUEST({ method: 'POST', url: $.hostname('/remote/'), type: 'json', body: JSON.stringify({ a: 1 }), headers: { 'x-token': 'abc' } });\n\tvar missing = await REQUEST({ url: $.hostname('/does-not-exist/') });\n\n\tvar failed = null;\n\ttry {\n\t\tawait REQUEST({ url: 'http://127.0.0.1:1/' });\n\t} catch (e) {\n\t\tfailed = true;\n\t}\n\n\t$.json({\n\t\tget: get.body.parseJSON(),\n\t\tpost: post.body.parseJSON(),\n\t\tstatus: [get.status, missing.status],\n\t\tfailed: failed\n\t});\n}\n\nasync function download($) {\n\tvar filename = PATH.tmp('forge-download.txt');\n\tvar response = await DOWNLOAD($.hostname('/remote/text/'), filename);\n\tvar content = require('fs').readFileSync(filename).toString('utf8');\n\t$.json({ status: response.status, size: response.size, content: content });\n}\n```"}],"knowledge":{"content_sha256":"53c1ccdf8b9d50fa08069f0f719cced7ec9528bd28bd251c9307fff966f9aaec","source_sha256":"9eb9380ee4bcf54af32aca81b19c6fa28b3a976ea9570105755ca7f9f12ff0c0","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["DOWNLOAD","GET","HTTP","JSON","PATH","POST","REQUEST","RESTB","ROUTE","URL"],"language":"en"},"files":[{"path":"controllers/request.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /remote/', remote);\n\tROUTE('POST /remote/ @json', remote_post);\n\tROUTE('GET /remote/text/', remote_text);\n\tROUTE('GET /call/', call);\n\tROUTE('GET /download/', download);\n};\n\nfunction remote($) {\n\t$.json({ hello: 'world', name: $.query.name || null });\n}\n\nfunction remote_post($) {\n\t$.json({ received: $.body, token: $.headers['x-token'] || null });\n}\n\nfunction remote_text($) {\n\t$.plain('first line\\nsecond line\\n');\n}\n\nasync function call($) {\n\n\tvar get = await REQUEST({ url: $.hostname('/remote/'), query: { name: 'Louis' } });\n\tvar post = await REQUEST({ method: 'POST', url: $.hostname('/remote/'), type: 'json', body: JSON.stringify({ a: 1 }), headers: { 'x-token': 'abc' } });\n\tvar missing = await REQUEST({ url: $.hostname('/does-not-exist/') });\n\n\tvar failed = null;\n\ttry {\n\t\tawait REQUEST({ url: 'http://127.0.0.1:1/' });\n\t} catch (e) {\n\t\tfailed = true;\n\t}\n\n\t$.json({\n\t\tget: get.body.parseJSON(),\n\t\tpost: post.body.parseJSON(),\n\t\tstatus: [get.status, missing.status],\n\t\tfailed: failed\n\t});\n}\n\nasync function download($) {\n\tvar filename = PATH.tmp('forge-download.txt');\n\tvar response = await DOWNLOAD($.hostname('/remote/text/'), filename);\n\tvar content = require('fs').readFileSync(filename).toString('utf8');\n\t$.json({ status: response.status, size: response.size, content: content });\n}"}],"test":"const result = await get('/call/');\nassert(result.json, 'expected JSON, got: ' + result.body);\nassert(result.json.get && result.json.get.hello === 'world' && result.json.get.name === 'Louis', 'query is appended to the URL, got: ' + result.body);\nassert(result.json.post && result.json.post.received && result.json.post.received.a === 1 && result.json.post.token === 'abc', 'body and headers are sent, got: ' + result.body);\nassert(result.json.status[0] === 200 && result.json.status[1] === 404, 'an error status does not reject, got: ' + result.body);\nassert(result.json.failed === true, 'a failed connection rejects, got: ' + result.body);\n\nconst file = await get('/download/');\nassert(file.json && file.json.status === 200 && file.json.size === 23 && file.json.content === 'first line\\nsecond line\\n', 'DOWNLOAD saves the body to disk, got: ' + file.body);","test_kind":"http"}
{"id":"total5/send-email","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/send-email/","markdown_url":"https://forge.totaljs.es/en/total5/send-email.md","tags":["mail","smtp","email"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"f7fe9f0f56eaa35d17399642831c1e961dd85f0ba02035342a08d5cb06cd3130","checked_at":"2026-10-20T12:18:48.212Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Send an email from a view","description":"Configure SMTP in the config file, render a view as the message body with MAIL(), and send plain HTML with HTMLMAIL().","messages":[{"role":"user","content":"Send an email from a view\n\nConfigure SMTP in the config file, render a view as the message body with MAIL(), and send plain HTML with HTMLMAIL()."},{"role":"assistant","content":"Total.js sends mail over SMTP. The server and the sender go in the `config` file: `mail_smtp` is the server, `mail_smtp_options` an object with the rest of the connection settings (`port`, `user`, `password`, `secure`...) and `mail_from` / `mail_from_name` the sender. `mail_reply`, `mail_cc` and `mail_bcc` add the same headers to every message.\n\n`MAIL(email, subject, view, model, [language], [callback])` renders `views/<view>.html` with the model (the same views as for pages, including `@(...)` localization) and sends the result as the HTML body. `email` can be one address or several separated by commas. `HTMLMAIL(email, subject, html, [language], [callback])` sends a string you built yourself. Both return the message; `callback(err)` runs when the server has accepted or rejected it, and `err` is `null` on success.\n\nMessages are queued and sent right after the current tick, so `MAIL()` never blocks the request. Wait for the callback only when the user has to know whether it worked.\n\n```text config\nname : Mail example\nmail_smtp : 127.0.0.1\nmail_smtp_options : { port: 2526 }\nmail_from : hello@example.com\nmail_from_name : Example\n```\n\nThe view is rendered **with the layout**, like any page, so `views/layout.html` must exist (the app crashes the request with an `ENOENT` error otherwise). A mail layout can be as small as the one below, or you can use your page layout.\n\n```html views/layout.html\n<!DOCTYPE html>\n<html>\n<head><meta charset=\"utf-8\" /></head>\n<body style=\"font-family:Arial\">@{body}</body>\n</html>\n```\n\n```html views/welcome.html\n<h1>Welcome, @{model.name}!</h1>\n<p>Your account is ready.</p>\n```\n\n```js controllers/mail.js\nexports.install = function() {\n\tROUTE('POST /welcome/ @json', welcome);\n\tROUTE('POST /note/ @json', note);\n};\n\nfunction welcome($) {\n\tMAIL($.body.email, 'Welcome aboard', 'welcome', { name: $.body.name }, function(err) {\n\t\t$.json({ sent: !err, error: err ? err.toString() : null });\n\t});\n}\n\nfunction note($) {\n\tHTMLMAIL($.body.email, 'A short note', '<p>Plain <b>HTML</b> message</p>', function(err) {\n\t\t$.json({ sent: !err, error: err ? err.toString() : null });\n\t});\n}\n```"}],"knowledge":{"content_sha256":"82d673e47df8e2c89407aa68da87b54706364712401f9ee8742dd3918b58e706","source_sha256":"f7fe9f0f56eaa35d17399642831c1e961dd85f0ba02035342a08d5cb06cd3130","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["DOCTYPE","ENOENT","HTML","HTMLMAIL","MAIL","POST","ROUTE","SMTP","Total.js"],"language":"en"},"files":[{"path":"config","language":"text","content":"name : Mail example\nmail_smtp : 127.0.0.1\nmail_smtp_options : { port: 2526 }\nmail_from : hello@example.com\nmail_from_name : Example"},{"path":"views/layout.html","language":"html","content":"<!DOCTYPE html>\n<html>\n<head><meta charset=\"utf-8\" /></head>\n<body style=\"font-family:Arial\">@{body}</body>\n</html>"},{"path":"views/welcome.html","language":"html","content":"<h1>Welcome, @{model.name}!</h1>\n<p>Your account is ready.</p>"},{"path":"controllers/mail.js","language":"js","content":"exports.install = function() {\n\tROUTE('POST /welcome/ @json', welcome);\n\tROUTE('POST /note/ @json', note);\n};\n\nfunction welcome($) {\n\tMAIL($.body.email, 'Welcome aboard', 'welcome', { name: $.body.name }, function(err) {\n\t\t$.json({ sent: !err, error: err ? err.toString() : null });\n\t});\n}\n\nfunction note($) {\n\tHTMLMAIL($.body.email, 'A short note', '<p>Plain <b>HTML</b> message</p>', function(err) {\n\t\t$.json({ sent: !err, error: err ? err.toString() : null });\n\t});\n}"}],"test":"// A tiny SMTP server that records what it receives, standing in for a real one\nconst net = process.getBuiltinModule('net');\nconst received = [];\n\n// Total.js sends the subject and the body as base64, as most mail clients expect\nfunction decode(raw) {\n\tconst subject = (raw.match(/^Subject: =\\?utf-8\\?B\\?(.+?)\\?=/mi) || [])[1];\n\tconst body = raw.split('Content-Transfer-Encoding: base64\\n\\n')[1].split('\\n--')[0].replace(/\\s/g, '');\n\treturn {\n\t\tsubject: subject ? Buffer.from(subject, 'base64').toString('utf8') : '',\n\t\tbody: Buffer.from(body, 'base64').toString('utf8'),\n\t\traw: raw\n\t};\n}\n\nconst server = net.createServer(function(socket) {\n\tlet data = false;\n\tlet buffer = '';\n\tlet current = '';\n\tsocket.write('220 localhost ready\\r\\n');\n\tsocket.on('data', function(chunk) {\n\t\tbuffer += chunk.toString('utf8');\n\t\tlet index;\n\t\twhile ((index = buffer.indexOf('\\r\\n')) !== -1) {\n\t\t\tconst line = buffer.substring(0, index);\n\t\t\tbuffer = buffer.substring(index + 2);\n\t\t\tif (data) {\n\t\t\t\tif (line === '.') {\n\t\t\t\t\tdata = false;\n\t\t\t\t\treceived.push(current);\n\t\t\t\t\tcurrent = '';\n\t\t\t\t\tsocket.write('250 queued\\r\\n');\n\t\t\t\t} else\n\t\t\t\t\tcurrent += line + '\\n';\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst command = line.substring(0, 4).toUpperCase();\n\t\t\tif (command === 'EHLO' || command === 'HELO')\n\t\t\t\tsocket.write('250 localhost\\r\\n');\n\t\t\telse if (command === 'DATA') {\n\t\t\t\tdata = true;\n\t\t\t\tsocket.write('354 go on\\r\\n');\n\t\t\t} else if (command === 'QUIT') {\n\t\t\t\tsocket.write('221 bye\\r\\n');\n\t\t\t\tsocket.end();\n\t\t\t} else\n\t\t\t\tsocket.write('250 ok\\r\\n');\n\t\t}\n\t});\n});\n\nawait new Promise(resolve => server.listen(2526, '127.0.0.1', resolve));\n\ntry {\n\n\tconst first = await post('/welcome/', { email: 'louis@example.com', name: 'Louis' });\n\tassert(first.json && first.json.sent === true, 'MAIL() reports success, got: ' + first.body);\n\n\tconst second = await post('/note/', { email: 'anna@example.com' });\n\tassert(second.json && second.json.sent === true, 'HTMLMAIL() reports success, got: ' + second.body);\n\n\tassert(received.length === 2, 'the server received two messages, got: ' + received.length);\n\n\tconst one = decode(received[0]);\n\tassert(one.subject === 'Welcome aboard', 'subject is set, got: ' + one.subject);\n\tassert(/louis@example.com/.test(one.raw), 'recipient is set');\n\tassert(/hello@example.com/.test(one.raw), 'sender comes from mail_from');\n\tassert(/<h1>Welcome, Louis!<\\/h1>/.test(one.body), 'the view is rendered with the model, got: ' + one.body);\n\n\tconst two = decode(received[1]);\n\tassert(/<b>HTML<\\/b>/.test(two.body), 'HTMLMAIL() sends the given HTML, got: ' + two.body);\n\n} finally {\n\tserver.close();\n}","test_kind":"http"}
{"id":"total5/error-pages","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/error-pages/","markdown_url":"https://forge.totaljs.es/en/total5/error-pages.md","tags":["errors","404","routing"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"a22d3a4075257ff5c1d1d7aab8e541822ca00445fcdf6a71eb4120053e4085c2","checked_at":"2026-10-20T12:18:02.658Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Customize the 404 and 401 pages","description":"Register ROUTE('#404') and ROUTE('#401') handlers to replace the built-in error pages, and trigger them yourself with $.fallback().","messages":[{"role":"user","content":"Customize the 404 and 401 pages\n\nRegister ROUTE('#404') and ROUTE('#401') handlers to replace the built-in error pages, and trigger them yourself with $.fallback()."},{"role":"assistant","content":"When Total.js cannot serve a request it shows a built-in error page. A route whose URL is `#` plus the status code replaces it: `ROUTE('#404', fn)` for \"not found\", `ROUTE('#401', fn)` for \"not authorized\", and the same for `#400`, `#431` and others. The handler receives `$` like any route, but **you set the status yourself** with `$.response.status`: Total.js does not do it for you when a custom handler exists. Without a custom handler the built-in page is used and AJAX requests (header `x-requested-with`) get a JSON error instead of HTML.\n\n`$.fallback(code)` sends the current request to the handler for `code`, which is how you answer 404 for a record that does not exist or 403 for a forbidden resource from inside your own code.\n\n```js controllers/errors.js\nexports.install = function() {\n\tROUTE('#404', not_found);\n\tROUTE('#401', unauthorized);\n\tROUTE('GET /items/{id}/', item);\n\tROUTE('+GET /private/', private_page);\n};\n\nvar items = { '1': 'First item' };\n\nfunction item($) {\n\tvar name = items[$.params.id];\n\tif (name)\n\t\t$.json({ name: name });\n\telse\n\t\t$.fallback(404);\n}\n\nfunction private_page($) {\n\t$.plain('secret');\n}\n\nfunction not_found($) {\n\t$.response.status = 404;\n\t$.html('<h1>Nothing here</h1><p>' + $.url.encode() + ' does not exist.</p>');\n}\n\nfunction unauthorized($) {\n\t$.response.status = 401;\n\t$.json({ error: 'Please sign in first' });\n}\n\nAUTH(function($) {\n\t$.invalid();\n});\n```"}],"knowledge":{"content_sha256":"379ed76a90c99ff5446f0f9679ef0c1fd1662cde467d0b7e43332f76bcc6db32","source_sha256":"a22d3a4075257ff5c1d1d7aab8e541822ca00445fcdf6a71eb4120053e4085c2","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["AJAX","AUTH","GET","HTML","JSON","ROUTE","Total.js","URL"],"language":"en"},"files":[{"path":"controllers/errors.js","language":"js","content":"exports.install = function() {\n\tROUTE('#404', not_found);\n\tROUTE('#401', unauthorized);\n\tROUTE('GET /items/{id}/', item);\n\tROUTE('+GET /private/', private_page);\n};\n\nvar items = { '1': 'First item' };\n\nfunction item($) {\n\tvar name = items[$.params.id];\n\tif (name)\n\t\t$.json({ name: name });\n\telse\n\t\t$.fallback(404);\n}\n\nfunction private_page($) {\n\t$.plain('secret');\n}\n\nfunction not_found($) {\n\t$.response.status = 404;\n\t$.html('<h1>Nothing here</h1><p>' + $.url.encode() + ' does not exist.</p>');\n}\n\nfunction unauthorized($) {\n\t$.response.status = 401;\n\t$.json({ error: 'Please sign in first' });\n}\n\nAUTH(function($) {\n\t$.invalid();\n});"}],"test":"const found = await get('/items/1/');\nassert(found.status === 200 && found.json && found.json.name === 'First item', 'existing item, got: ' + found.body);\n\nconst missing = await get('/items/99/');\nassert(missing.status === 404 && /Nothing here/.test(missing.body), '$.fallback(404) uses the custom page, got: ' + missing.status + ' ' + missing.body);\n\nconst unknown = await get('/no/such/page/');\nassert(unknown.status === 404 && /\\/no\\/such\\/page\\//.test(unknown.body), 'an unknown URL uses the custom page too, got: ' + unknown.status + ' ' + unknown.body);\n\nconst denied = await get('/private/');\nassert(denied.status === 401 && denied.json && denied.json.error === 'Please sign in first', 'the 401 handler runs, got: ' + denied.status + ' ' + denied.body);","test_kind":"http"}
{"id":"total5/logmail","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/logmail/","markdown_url":"https://forge.totaljs.es/en/total5/logmail.md","tags":["mail","smtp","logging"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"9af8ba9fc883b600e196e870a028ecf5776b29ed7b1017f80459bd2e6230c58b","checked_at":"2026-10-20T12:18:22.033Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Mail yourself a message with LOGMAIL()","description":"Send a plain text or JSON message to an address without a view, for logs and alerts, through the same SMTP settings as MAIL().","messages":[{"role":"user","content":"Mail yourself a message with LOGMAIL()\n\nSend a plain text or JSON message to an address without a view, for logs and alerts, through the same SMTP settings as MAIL()."},{"role":"assistant","content":"`LOGMAIL(email, [subject], body, [callback])` sends a message made for the developer, not for a customer: no view, no layout. The body is a text. The text goes inside a `<pre>`, in a small HTML page, so line breaks and spaces survive; it is put as it is, so do not send untrusted HTML. Without a `subject` the subject is the name of the application (`name` in the `config`).\n\n**Pass the body as a text.** For an object use `JSON.stringify(object, null, 2)`, as the second route below does.\n\nIt uses the same SMTP server, sender and queue as `MAIL()` (`mail_smtp`, `mail_smtp_options`, `mail_from` in the `config`). It is meant for things like \"the import failed\" with the data that you need to see.\n\n```text config\nname : Log mail example\nmail_smtp : 127.0.0.1\nmail_smtp_options : { port: 2527 }\nmail_from : hello@example.com\nmail_from_name : Example\n```\n\n```js controllers/log.js\nexports.install = function() {\n\tROUTE('POST /alert/ @json', alert);\n\tROUTE('POST /alert-object/ @json', alert_object);\n\tROUTE('POST /alert-raw/ @json', alert_raw);\n};\n\nfunction alert($) {\n\tLOGMAIL('ops@example.com', 'Import failed', 'Line 1\\nLine 2', function(err) {\n\t\t$.json({ sent: !err, error: err ? err.toString() : null });\n\t});\n}\n\nfunction alert_object($) {\n\t// without a subject: the subject is the name of the application; the object is written by us\n\tLOGMAIL('ops@example.com', JSON.stringify({ rows: 12, failed: ['a', 'b'] }, null, 2), function(err) {\n\t\t$.json({ sent: !err, error: err ? err.toString() : null });\n\t});\n}\n\nfunction alert_raw($) {\n\t// the object itself: it throws in total5 0.0.19\n\ttry {\n\t\tLOGMAIL('ops@example.com', { rows: 12 }, NOOP);\n\t\t$.json({ thrown: false });\n\t} catch (e) {\n\t\t$.json({ thrown: true, error: e.message });\n\t}\n}\n```"}],"knowledge":{"content_sha256":"28902e3036b56ddecd785939f8350520d582fbf2dea6e5755e97920246b2352e","source_sha256":"9af8ba9fc883b600e196e870a028ecf5776b29ed7b1017f80459bd2e6230c58b","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["HTML","JSON","LOGMAIL","MAIL","NOOP","POST","ROUTE","SMTP"],"language":"en"},"files":[{"path":"config","language":"text","content":"name : Log mail example\nmail_smtp : 127.0.0.1\nmail_smtp_options : { port: 2527 }\nmail_from : hello@example.com\nmail_from_name : Example"},{"path":"controllers/log.js","language":"js","content":"exports.install = function() {\n\tROUTE('POST /alert/ @json', alert);\n\tROUTE('POST /alert-object/ @json', alert_object);\n\tROUTE('POST /alert-raw/ @json', alert_raw);\n};\n\nfunction alert($) {\n\tLOGMAIL('ops@example.com', 'Import failed', 'Line 1\\nLine 2', function(err) {\n\t\t$.json({ sent: !err, error: err ? err.toString() : null });\n\t});\n}\n\nfunction alert_object($) {\n\t// without a subject: the subject is the name of the application; the object is written by us\n\tLOGMAIL('ops@example.com', JSON.stringify({ rows: 12, failed: ['a', 'b'] }, null, 2), function(err) {\n\t\t$.json({ sent: !err, error: err ? err.toString() : null });\n\t});\n}\n\nfunction alert_raw($) {\n\t// the object itself: it throws in total5 0.0.19\n\ttry {\n\t\tLOGMAIL('ops@example.com', { rows: 12 }, NOOP);\n\t\t$.json({ thrown: false });\n\t} catch (e) {\n\t\t$.json({ thrown: true, error: e.message });\n\t}\n}"}],"test":"// A tiny SMTP server that records what it receives, standing in for a real one\nconst net = process.getBuiltinModule('net');\nconst received = [];\n\nfunction decode(raw) {\n\tconst subject = (raw.match(/^Subject: =\\?utf-8\\?B\\?(.+?)\\?=/mi) || [])[1];\n\tconst body = raw.split('Content-Transfer-Encoding: base64\\n\\n')[1].split('\\n--')[0].replace(/\\s/g, '');\n\treturn { subject: subject ? Buffer.from(subject, 'base64').toString('utf8') : '', body: Buffer.from(body, 'base64').toString('utf8') };\n}\n\nconst server = net.createServer(function(socket) {\n\tlet data = false;\n\tlet buffer = '';\n\tlet current = '';\n\tsocket.write('220 localhost ready\\r\\n');\n\tsocket.on('data', function(chunk) {\n\t\tbuffer += chunk.toString('utf8');\n\t\tlet index;\n\t\twhile ((index = buffer.indexOf('\\r\\n')) !== -1) {\n\t\t\tconst line = buffer.substring(0, index);\n\t\t\tbuffer = buffer.substring(index + 2);\n\t\t\tif (data) {\n\t\t\t\tif (line === '.') {\n\t\t\t\t\tdata = false;\n\t\t\t\t\treceived.push(current);\n\t\t\t\t\tcurrent = '';\n\t\t\t\t\tsocket.write('250 queued\\r\\n');\n\t\t\t\t} else\n\t\t\t\t\tcurrent += line + '\\n';\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst command = line.substring(0, 4).toUpperCase();\n\t\t\tif (command === 'EHLO' || command === 'HELO')\n\t\t\t\tsocket.write('250 localhost\\r\\n');\n\t\t\telse if (command === 'DATA') {\n\t\t\t\tdata = true;\n\t\t\t\tsocket.write('354 go on\\r\\n');\n\t\t\t} else if (command === 'QUIT') {\n\t\t\t\tsocket.write('221 bye\\r\\n');\n\t\t\t\tsocket.end();\n\t\t\t} else\n\t\t\t\tsocket.write('250 ok\\r\\n');\n\t\t}\n\t});\n});\n\nawait new Promise(resolve => server.listen(2527, '127.0.0.1', resolve));\n\ntry {\n\n\tconst first = await post('/alert/', {});\n\tassert(first.json && first.json.sent === true, 'LOGMAIL() with a subject reports success, got: ' + first.body);\n\n\tconst second = await post('/alert-object/', {});\n\tassert(second.json && second.json.sent === true, 'LOGMAIL() without a subject reports success, got: ' + second.body);\n\n\tconst raw = await post('/alert-raw/', {});\n\tassert(raw.json && raw.json.thrown === true && /escape is not a function/.test(raw.json.error), 'an object as the body throws, got: ' + raw.body);\n\n\t// the callback says that the message left the queue; the server may need a moment to record the second one\n\tfor (let i = 0; i < 30 && received.length < 2; i++)\n\t\tawait new Promise(resolve => setTimeout(resolve, 100));\n\n\tassert(received.length === 2, 'the server received two messages, got: ' + received.length);\n\n\tconst one = decode(received[0]);\n\tassert(one.subject === 'Import failed', 'the subject is the one that was given, got: ' + one.subject);\n\tassert(/<pre[^>]*>Line 1\\r?\\nLine 2<\\/pre>/.test(one.body), 'the text is inside a pre, got: ' + one.body);\n\n\tconst two = decode(received[1]);\n\tassert(two.subject === 'Log mail example', 'without a subject it is the name of the application, got: ' + two.subject);\n\tassert(/\"rows\": 12/.test(two.body) && /<pre/.test(two.body), 'the JSON that we wrote is inside the pre, got: ' + two.body);\n\n} finally {\n\tserver.close();\n}","test_kind":"http"}
{"id":"total5/file-download","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/file-download/","markdown_url":"https://forge.totaljs.es/en/total5/file-download.md","tags":["files","download","stream"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"b37259d3e27dfb86813a52c196715a523f4a7190fade8a95e0eae304c792b450","checked_at":"2026-10-20T12:18:05.444Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Send files and streams to the client","description":"Serve a file from disk with $.file(), force a download name, stream generated content with $.stream() and answer with 404 when the file is missing.","messages":[{"role":"user","content":"Send files and streams to the client\n\nServe a file from disk with $.file(), force a download name, stream generated content with $.stream() and answer with 404 when the file is missing."},{"role":"assistant","content":"`$.file(path, [download])` sends a file from disk. The `content-type` comes from the extension. Pass `true` as the second argument to make the browser download the file under its own name, or a string to choose the name (`attachment; filename*=...` is set for you, UTF-8 safe). A path that does not exist ends with the 404 page.\n\n`$.stream(type, stream, [download])` sends any readable stream with the given content type: a generated file, a response from another server or a database export. It sets no length, so the response is chunked. `PATH.public()`, `PATH.private()` and `PATH.tmp()` build absolute paths into the project folders; files in `public/` are already served without any route, so use `$.file()` for the files that must stay private or need a check first.\n\n```js controllers/download.js\nconst Fs = require('fs');\n\nexports.install = function() {\n\tROUTE('GET /report/', report);\n\tROUTE('GET /report/save/', report_save);\n\tROUTE('GET /export/', export_csv);\n\tROUTE('GET /missing/', missing);\n};\n\nfunction filepath() {\n\tvar filename = PATH.tmp('report.txt');\n\tFs.writeFileSync(filename, 'Total.js report\\n');\n\treturn filename;\n}\n\nfunction report($) {\n\t$.file(filepath());\n}\n\nfunction report_save($) {\n\t$.file(filepath(), 'my report.txt');\n}\n\nfunction export_csv($) {\n\tvar stream = Fs.createReadStream(filepath());\n\t$.stream('text/csv', stream, 'export.csv');\n}\n\nfunction missing($) {\n\t$.file(PATH.tmp('does-not-exist.txt'));\n}\n```"}],"knowledge":{"content_sha256":"c21ebfd97c7af12b738ceba0374203c527f79fc70b1373c215c4a4462f8009d4","source_sha256":"b37259d3e27dfb86813a52c196715a523f4a7190fade8a95e0eae304c792b450","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["GET","PATH","ROUTE","Total.js","UTF"],"language":"en"},"files":[{"path":"controllers/download.js","language":"js","content":"const Fs = require('fs');\n\nexports.install = function() {\n\tROUTE('GET /report/', report);\n\tROUTE('GET /report/save/', report_save);\n\tROUTE('GET /export/', export_csv);\n\tROUTE('GET /missing/', missing);\n};\n\nfunction filepath() {\n\tvar filename = PATH.tmp('report.txt');\n\tFs.writeFileSync(filename, 'Total.js report\\n');\n\treturn filename;\n}\n\nfunction report($) {\n\t$.file(filepath());\n}\n\nfunction report_save($) {\n\t$.file(filepath(), 'my report.txt');\n}\n\nfunction export_csv($) {\n\tvar stream = Fs.createReadStream(filepath());\n\t$.stream('text/csv', stream, 'export.csv');\n}\n\nfunction missing($) {\n\t$.file(PATH.tmp('does-not-exist.txt'));\n}"}],"test":"const inline = await get('/report/');\nassert(inline.status === 200 && inline.body === 'Total.js report\\n', 'file content, got: ' + inline.status + ' ' + inline.body);\nassert(/text\\/plain/.test(inline.headers.get('content-type')), 'content type from the extension, got: ' + inline.headers.get('content-type'));\nassert(!inline.headers.get('content-disposition'), 'no download header by default');\n\nconst saved = await get('/report/save/');\nassert(/attachment; filename\\*=utf-8''my%20report\\.txt/.test(saved.headers.get('content-disposition')), 'download name is set, got: ' + saved.headers.get('content-disposition'));\n\nconst csv = await get('/export/');\nassert(csv.status === 200 && /text\\/csv/.test(csv.headers.get('content-type')) && csv.body === 'Total.js report\\n', 'stream is sent with its type, got: ' + csv.body);\nassert(/export\\.csv/.test(csv.headers.get('content-disposition')), 'stream download name, got: ' + csv.headers.get('content-disposition'));\n\nconst none = await get('/missing/');\nassert(none.status === 404, 'a missing file answers 404, got: ' + none.status);","test_kind":"http"}
{"id":"total5/proxy","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/proxy/","markdown_url":"https://forge.totaljs.es/en/total5/proxy.md","tags":["proxy","gateway","integrations"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"c3bbf318e36e67a57e4cae2d6a9419411fe1de5f84aadfed50e41f6aa69fba36","checked_at":"2026-10-20T12:18:34.315Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Forward requests to another server","description":"Use PROXY() to map a URL prefix to an upstream server, choose how the path is copied, and $.proxy() to forward a single route.","messages":[{"role":"user","content":"Forward requests to another server\n\nUse PROXY() to map a URL prefix to an upstream server, choose how the path is copied, and $.proxy() to forward a single route."},{"role":"assistant","content":"`PROXY(prefix, target)` forwards every request below a URL prefix to another server and streams the answer back, so one Total.js app can sit in front of several services. It runs before the routes, so a prefix handled by `PROXY()` never reaches `ROUTE()`. The request is passed on with its method, headers and body; the headers `x-forwarded-for`, `x-forwarded-url` and `x-forwarded-host` tell the upstream about the original request. `PROXY(prefix)` without a target removes the proxy.\n\nThe object it returns configures the proxy: `.copy(type)` chooses which part of the path is sent upstream (see below), `.timeout(ms)` limits the wait, `.check(fn)` makes the proxy apply only when `fn(ctrl)` returns true and `.before(uri, ctrl)` can change the outgoing request, for example to add a header.\n\n`.copy()` takes `none` (the default), `extend` or `replace`. With **`none`** the upstream only receives the path of the target plus the query string: `/none/a/b/?q=1` becomes `/base/?q=1` for the target `http://host/base/`. With **`extend`** the rest of the request path is appended to the target path: `/extend/a/b/?q=1` becomes `/base/a/b/?q=1`, which is what you want for a gateway. `replace` behaves like `extend` when the target has a path of its own; when the target is just a host it sends the rest of the path **without the query string**, so prefer `extend`.\n\nFor a single route use `$.proxy(url)`: it sends the current request (method, query string, body and headers) to `url` and returns the upstream answer with its status code.\n\n```js controllers/proxy.js\nconst Http = require('http');\n\n// A small upstream server that reports what it received\nHttp.createServer(function(req, res) {\n\tres.writeHead(200, { 'content-type': 'application/json' });\n\tres.end(JSON.stringify({ method: req.method, url: req.url, token: req.headers['x-token'] || null, forwarded: !!req.headers['x-forwarded-url'] }));\n}).listen(9411, '127.0.0.1');\n\n// One proxy per copy mode, to see the difference (the target has the path /base/)\nPROXY('/none/', 'http://127.0.0.1:9411/base/');\nPROXY('/extend/', 'http://127.0.0.1:9411/base/').copy('extend');\nPROXY('/plain/', 'http://127.0.0.1:9411/base/').copy('replace');\n\n// Everything below /service/ goes to the upstream, and a header is added on the way\nPROXY('/service/', 'http://127.0.0.1:9411/').copy('extend').before(function(uri, ctrl) {\n\turi.headers['x-token'] = 'secret';\n});\n\nexports.install = function() {\n\tROUTE('GET /single/', single);\n};\n\nfunction single($) {\n\t$.proxy('http://127.0.0.1:9411/from-route/');\n}\n```"}],"knowledge":{"content_sha256":"a5143999f45cb88cba286cb0dfca70e97c0f40c381830c2860aeb38043de9342","source_sha256":"c3bbf318e36e67a57e4cae2d6a9419411fe1de5f84aadfed50e41f6aa69fba36","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["GET","JSON","PROXY","ROUTE","Total.js","URL"],"language":"en"},"files":[{"path":"controllers/proxy.js","language":"js","content":"const Http = require('http');\n\n// A small upstream server that reports what it received\nHttp.createServer(function(req, res) {\n\tres.writeHead(200, { 'content-type': 'application/json' });\n\tres.end(JSON.stringify({ method: req.method, url: req.url, token: req.headers['x-token'] || null, forwarded: !!req.headers['x-forwarded-url'] }));\n}).listen(9411, '127.0.0.1');\n\n// One proxy per copy mode, to see the difference (the target has the path /base/)\nPROXY('/none/', 'http://127.0.0.1:9411/base/');\nPROXY('/extend/', 'http://127.0.0.1:9411/base/').copy('extend');\nPROXY('/plain/', 'http://127.0.0.1:9411/base/').copy('replace');\n\n// Everything below /service/ goes to the upstream, and a header is added on the way\nPROXY('/service/', 'http://127.0.0.1:9411/').copy('extend').before(function(uri, ctrl) {\n\turi.headers['x-token'] = 'secret';\n});\n\nexports.install = function() {\n\tROUTE('GET /single/', single);\n};\n\nfunction single($) {\n\t$.proxy('http://127.0.0.1:9411/from-route/');\n}"}],"test":"const modes = {};\nfor (const name of ['none', 'extend', 'plain'])\n\tmodes[name] = (await get('/' + name + '/a/b/?q=1')).json.url;\nassert(modes.none === '/base/?q=1', 'copy(\"none\") sends only the target path and the query, got: ' + modes.none);\nassert(modes.extend === '/base/a/b/?q=1' && modes.plain === '/base/a/b/?q=1', 'copy(\"extend\") appends the rest of the path to the target path, got: ' + JSON.stringify(modes));\n\nconst first = await get('/service/users/?page=2');\nassert(first.json && first.json.method === 'GET', 'the request reaches the upstream, got: ' + first.body);\nassert(first.json.url === '/users/?page=2', 'copy(\"extend\") drops the prefix and keeps the query, got: ' + first.json.url);\nassert(first.json.token === 'secret', 'before() adds a header, got: ' + first.body);\nassert(first.json.forwarded === true, 'x-forwarded-url is sent, got: ' + first.body);\n\nconst posted = await post('/service/items/', { a: 1 });\nassert(posted.json && posted.json.method === 'POST' && posted.json.url === '/items/', 'the method is forwarded, got: ' + posted.body);\n\nconst single = await get('/single/?x=1');\nassert(single.json && single.json.url === '/from-route/?x=1', '$.proxy() forwards one route with its query string, got: ' + single.body);","test_kind":"http"}
{"id":"total5/proxy-tunnel","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/proxy-tunnel/","markdown_url":"https://forge.totaljs.es/en/total5/proxy-tunnel.md","tags":["proxy","tunnel","websocket","integrations"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"acadb6b9406bd486407331e27d8b937cec52dbadc6768ad55f5648e67557bd82","checked_at":"2026-10-20T12:18:33.584Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Publish a local server through a tunnel with PROXYSERVER() and PROXYCLIENT()","description":"Let a Total.js server with a public address forward requests to a program that only runs on a private machine, with a WebSocket tunnel that the private side opens.","messages":[{"role":"user","content":"Publish a local server through a tunnel with PROXYSERVER() and PROXYCLIENT()\n\nLet a Total.js server with a public address forward requests to a program that only runs on a private machine, with a WebSocket tunnel that the private side opens."},{"role":"assistant","content":"`PROXY()` forwards a request to a server that the proxy can reach. A tunnel is for the opposite case: the program is on a laptop or behind a firewall, so nobody can connect to it, but it can connect out. `PROXYSERVER()` and `PROXYCLIENT()` make that with one WebSocket that the private side opens:\n\n- `PROXYSERVER([endpoint], [socketendpoint], [logger])` runs on the public server. Every request below `endpoint` (default `/`) is sent through the tunnel; `socketendpoint` (default `/`) is the WebSocket address where the client connects. Use **different** addresses when the same application serves anything else: a proxy on `/` takes every request, also the ones of your own routes (it runs before them). `logger` is a function (default: prints with the date); pass `null` for none.\n- `PROXYCLIENT(serverurl, port, [logger])` runs on the private side (in this example the address of the server is built with `CONF.$port`, the port of the application). `serverurl` is the address of the WebSocket of the server (`ws://` or `wss://`; `http` is changed to `ws`) and `port` is `3000` or `ip:3000`, the local program that receives the requests. It returns the WebSocket client.\n- **One client at a time:** when a second client connects, the server closes it with the code `4001`. Without a client the answer of the server is `Not yet available.`.\n- The local program receives the **whole path of the original request**, including the `endpoint` (`/tunnel/hello`), the headers of the visitor and an `x-proxy: Total.js` header, with `host` set to the local address.\n- The WebSocket route has no authentication of its own: do not publish it without something in front of it that decides who can connect (the address is the secret, a `check` of your own, a firewall).\n\nHere one application is both sides: a small local program on port 9511 (any program would do), the server on `/tunnel/` and the client that connects to the same application.\n\n```js definitions/local.js\n// The program that is \"private\": an ordinary HTTP server that only listens on the loopback address\nrequire('http').createServer(function(req, res) {\n\tres.writeHead(200, { 'content-type': 'application/json' });\n\tres.end(JSON.stringify({ path: req.url, via: req.headers['x-proxy'] || null, host: req.headers.host }));\n}).listen(9511, '127.0.0.1');\n```\n\n```js controllers/tunnel.js\nexports.install = function() {\n\tROUTE('GET /status/', status);\n\tROUTE('POST /connect/', connect);\n};\n\n// The public side: /tunnel/* goes through the tunnel, the WebSocket of the client is /tunnel-socket/. No logger.\nPROXYSERVER('/tunnel/', '/tunnel-socket/', null);\n\nlet client = null;\n\nfunction connect($) {\n\tclient = PROXYCLIENT('ws://127.0.0.1:' + CONF.$port + '/tunnel-socket/', '9511', null);\n\t$.json({ connecting: true });\n}\n\nfunction status($) {\n\t$.json({ connected: !!client });\n}\n```"}],"knowledge":{"content_sha256":"6c7bccccd48386b830d20ab4b33001d93abc21834308e84c8fd99c0210bab471","source_sha256":"acadb6b9406bd486407331e27d8b937cec52dbadc6768ad55f5648e67557bd82","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["CONF","GET","HTTP","JSON","POST","PROXY","PROXYCLIENT","PROXYSERVER","ROUTE","Total.js"],"language":"en"},"files":[{"path":"definitions/local.js","language":"js","content":"// The program that is \"private\": an ordinary HTTP server that only listens on the loopback address\nrequire('http').createServer(function(req, res) {\n\tres.writeHead(200, { 'content-type': 'application/json' });\n\tres.end(JSON.stringify({ path: req.url, via: req.headers['x-proxy'] || null, host: req.headers.host }));\n}).listen(9511, '127.0.0.1');"},{"path":"controllers/tunnel.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /status/', status);\n\tROUTE('POST /connect/', connect);\n};\n\n// The public side: /tunnel/* goes through the tunnel, the WebSocket of the client is /tunnel-socket/. No logger.\nPROXYSERVER('/tunnel/', '/tunnel-socket/', null);\n\nlet client = null;\n\nfunction connect($) {\n\tclient = PROXYCLIENT('ws://127.0.0.1:' + CONF.$port + '/tunnel-socket/', '9511', null);\n\t$.json({ connecting: true });\n}\n\nfunction status($) {\n\t$.json({ connected: !!client });\n}"}],"test":"// Before a client connects the server has nothing to forward to\nconst before = await get('/tunnel/hello');\nassert(/Not yet available/.test(before.body), 'without a client: Not yet available, got: ' + before.body);\n\nawait post('/connect/', {});\n\n// The request goes through the tunnel to the local program, with its whole path\nlet answer = null;\nfor (let i = 0; i < 30; i++) {\n\tawait new Promise(resolve => setTimeout(resolve, 200));\n\tconst res = await get('/tunnel/hello?x=1');\n\tif (res.json && res.json.path) {\n\t\tanswer = res.json;\n\t\tbreak;\n\t}\n}\n\nassert(answer, 'the request reached the local program through the tunnel');\nassert(answer.path === '/tunnel/hello?x=1', 'the local program receives the whole path, got: ' + answer.path);\nassert(answer.via === 'Total.js', 'the client adds the x-proxy header, got: ' + answer.via);\nassert(answer.host === '127.0.0.1:9511', 'the host is the local address, got: ' + answer.host);\n\n// The routes of the application that are not below the endpoint still work\nconst own = await get('/status/');\nassert(own.json && own.json.connected === true, 'the other routes are not proxied, got: ' + own.body);","test_kind":"http"}
{"id":"total5/csrf-protection","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/csrf-protection/","markdown_url":"https://forge.totaljs.es/en/total5/csrf-protection.md","tags":["security","csrf","middleware"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"de40a87355ae05a59c23f9cb2a6b8b4bc9e8e99e597a8c51a40e344ee42d2e4e","checked_at":"2026-10-20T12:17:58.619Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Protect forms and requests with CSRF tokens","description":"Create a token with $.csrf(), send it with the request and verify it in a middleware so cross-site requests are rejected.","messages":[{"role":"user","content":"Protect forms and requests with CSRF tokens\n\nCreate a token with $.csrf(), send it with the request and verify it in a middleware so cross-site requests are rejected."},{"role":"assistant","content":"A CSRF attack makes a logged-in user's browser send a request they never intended. The defence is a token that the page receives from your server and that has to come back with every state-changing request. Total.js creates and verifies the token for you, but only when `secret_csrf` is set in the `config` file (without it the check always passes).\n\n`$.csrf()` returns a token (and `@{csrf}` prints it in a view). It is an encrypted value tied to the **IP address** and the **user agent** of the client and it expires after the time in `$csrfexpiration` (30 minutes by default). `DEF.onCSRFcheck($)` verifies the token that came back, read from the `x-csrf-token` header or the `csrf` query string parameter, and returns `true` or `false`. For ordinary routes you call it yourself, most conveniently from a middleware that you add with `#csrf` to every route that changes data. WebSocket routes with the `@csrf` flag are checked automatically.\n\n```text config\nsecret_csrf : change-this-secret-in-production\n```\n\n```js definitions/csrf.js\nMIDDLEWARE('csrf', function($, next) {\n\tif (DEF.onCSRFcheck($))\n\t\tnext();\n\telse\n\t\t$.invalid(403);\n});\n```\n\n```js controllers/csrf.js\nexports.install = function() {\n\tROUTE('GET /form/', form);\n\tROUTE('POST /transfer/ @json #csrf', transfer);\n};\n\nfunction form($) {\n\t$.json({ csrf: $.csrf() });\n}\n\nfunction transfer($) {\n\t$.json({ ok: true, amount: $.body.amount });\n}\n```"}],"knowledge":{"content_sha256":"e10434ff98f2737ef66f4645652b5c226dd2bc4b75b4c4a3bc5d348b2d366762","source_sha256":"de40a87355ae05a59c23f9cb2a6b8b4bc9e8e99e597a8c51a40e344ee42d2e4e","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["CSRF","DEF","GET","MIDDLEWARE","POST","ROUTE","Total.js"],"language":"en"},"files":[{"path":"config","language":"text","content":"secret_csrf : change-this-secret-in-production"},{"path":"definitions/csrf.js","language":"js","content":"MIDDLEWARE('csrf', function($, next) {\n\tif (DEF.onCSRFcheck($))\n\t\tnext();\n\telse\n\t\t$.invalid(403);\n});"},{"path":"controllers/csrf.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /form/', form);\n\tROUTE('POST /transfer/ @json #csrf', transfer);\n};\n\nfunction form($) {\n\t$.json({ csrf: $.csrf() });\n}\n\nfunction transfer($) {\n\t$.json({ ok: true, amount: $.body.amount });\n}"}],"test":"const missing = await post('/transfer/', { amount: 10 });\nassert(missing.status === 403, 'a request without a token is rejected, got: ' + missing.status);\n\nconst forged = await post('/transfer/', { amount: 10 }, { headers: { 'x-csrf-token': 'not-a-real-token-at-all' } });\nassert(forged.status === 403, 'an invalid token is rejected, got: ' + forged.status);\n\nconst page = await get('/form/');\nassert(page.json && page.json.csrf && page.json.csrf.length > 10, 'the page receives a token, got: ' + page.body);\n\nconst header = await post('/transfer/', { amount: 10 }, { headers: { 'x-csrf-token': page.json.csrf } });\nassert(header.status === 200 && header.json && header.json.ok === true && header.json.amount === 10, 'the token in the header is accepted, got: ' + header.status + ' ' + header.body);\n\nconst query = await post('/transfer/?csrf=' + encodeURIComponent(page.json.csrf), { amount: 5 });\nassert(query.status === 200, 'the token in the query string is accepted, got: ' + query.status);","test_kind":"http"}
{"id":"total5/schemas","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/schemas/","markdown_url":"https://forge.totaljs.es/en/total5/schemas.md","tags":["schema","actions","validation"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"6c6f010c8a28c97e17eda4b746246588e6dba88fc81b8d24788a5d087a134ead","checked_at":"2026-10-20T12:18:47.485Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Group actions in a schema and reuse input definitions","description":"Use NEWSCHEMA() to declare related actions under one name, and share an input definition between actions with a file in jsonschemas/.","messages":[{"role":"user","content":"Group actions in a schema and reuse input definitions\n\nUse NEWSCHEMA() to declare related actions under one name, and share an input definition between actions with a file in jsonschemas/."},{"role":"assistant","content":"`NEWSCHEMA(name, fn)` groups the actions of one entity. The function receives the schema object and `schema.action(name, meta)` registers an action called `Name/action`, with the same options as `NEWACTION()` (`input`, `output`, `params`, `user`, `permissions`, `action`...). Everywhere else the action is used by its full name, exactly as if it had been declared with `NEWACTION('Users/insert', ...)`: `ROUTE('POST /users/ --> *Users/insert')` or `ACTION('Users/insert', payload)`.\n\nA fragment of an input definition that several actions share, such as an address, goes in its own file in `jsonschemas/`. A `.txt` file holds the same `name:Type` list as `input`; the name of the file is the name of the schema. Reference it from an `input` with `@Name`, and its fields (and which of them are required) are added to the action's own fields.\n\n```text jsonschemas/Address.txt\n*street:String, *city:String, zip:String\n```\n\n```js schemas/users.js\nNEWSCHEMA('Users', function(schema) {\n\n\tschema.action('insert', {\n\t\tname: 'Create a user',\n\t\tinput: '*name:String, @Address',\n\t\toutput: 'id:UID, name:String, city:String',\n\t\taction: function($, model) {\n\t\t\t$.callback({ id: UID(), name: model.name, city: model.city });\n\t\t}\n\t});\n\n\tschema.action('count', {\n\t\tname: 'Count users',\n\t\taction: function($) {\n\t\t\t$.callback({ count: 2 });\n\t\t}\n\t});\n\n});\n```\n\n```js controllers/users.js\nexports.install = function() {\n\tROUTE('POST /users/ --> *Users/insert');\n\tROUTE('GET /users/count/ --> *Users/count');\n};\n```"}],"knowledge":{"content_sha256":"951ce36fc3ea0cd16be8f91ca9a8f5e5b115d12205262a6c67c6e3f7ed178117","source_sha256":"6c6f010c8a28c97e17eda4b746246588e6dba88fc81b8d24788a5d087a134ead","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["ACTION","GET","NEWACTION","NEWSCHEMA","POST","ROUTE","UID"],"language":"en"},"files":[{"path":"jsonschemas/Address.txt","language":"text","content":"*street:String, *city:String, zip:String"},{"path":"schemas/users.js","language":"js","content":"NEWSCHEMA('Users', function(schema) {\n\n\tschema.action('insert', {\n\t\tname: 'Create a user',\n\t\tinput: '*name:String, @Address',\n\t\toutput: 'id:UID, name:String, city:String',\n\t\taction: function($, model) {\n\t\t\t$.callback({ id: UID(), name: model.name, city: model.city });\n\t\t}\n\t});\n\n\tschema.action('count', {\n\t\tname: 'Count users',\n\t\taction: function($) {\n\t\t\t$.callback({ count: 2 });\n\t\t}\n\t});\n\n});"},{"path":"controllers/users.js","language":"js","content":"exports.install = function() {\n\tROUTE('POST /users/ --> *Users/insert');\n\tROUTE('GET /users/count/ --> *Users/count');\n};"}],"test":"const created = await post('/users/', { name: 'Louis', street: 'Main street 1', city: 'Bratislava' });\nassert(created.status === 200 && created.json && created.json.name === 'Louis' && created.json.city === 'Bratislava', 'the schema action runs with the shared fields, got: ' + created.status + ' ' + created.body);\n\nconst invalid = await post('/users/', { name: 'Louis', street: 'Main street 1' });\nassert(invalid.status === 400, 'the required field of the shared definition is enforced, got: ' + invalid.status + ' ' + invalid.body);\n\nconst count = await get('/users/count/');\nassert(count.json && count.json.count === 2, 'a second action of the same schema, got: ' + count.body);","test_kind":"http"}
{"id":"total5/transforms","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/transforms/","markdown_url":"https://forge.totaljs.es/en/total5/transforms.md","tags":["transform","pipeline"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"6ffcda68b1773e36838b9e35aadb7ed05f1229cb0d502e17a8440950b27c165d","checked_at":"2026-10-20T12:18:56.622Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Build a processing pipeline with transforms","description":"Register several steps under one name with NEWTRANSFORM() and run a value through them in order with TRANSFORM().","messages":[{"role":"user","content":"Build a processing pipeline with transforms\n\nRegister several steps under one name with NEWTRANSFORM() and run a value through them in order with TRANSFORM()."},{"role":"assistant","content":"A transform is a named chain of steps. `NEWTRANSFORM(name, fn, [id])` adds a step to the chain `name`; steps run in the order they were registered, and each one receives the options object `$` and the current value. Change the value with `$.value = ...` and call `$.next()` to continue with the next step. `$.invalid(message)` adds an error. `TRANSFORM(name, value)` starts the chain and returns a promise with the final value; the promise rejects when a step has reported an error. With a callback instead, `TRANSFORM(name, value, function(err, value) {})`. A name without any step returns the value as it is.\n\nUse it for work that has to be extended from several places, such as cleaning a comment before it is saved: a spam filter, a trimmer and a link checker can each live in their own file and be added or removed without touching the code that calls `TRANSFORM()`. The optional `id` lets you remove one step later with `NEWTRANSFORM(name, null, id)`.\n\n```js definitions/transforms.js\nNEWTRANSFORM('comment', function($, value) {\n\t$.value = value.trim();\n\t$.next();\n}, 'trim');\n\nNEWTRANSFORM('comment', function($, value) {\n\tif (/spam/i.test(value))\n\t\t$.invalid('Spam is not allowed');\n\t$.next();\n}, 'spam');\n\nNEWTRANSFORM('comment', function($, value) {\n\t$.value = value.replace(/\\s+/g, ' ');\n\t$.next();\n}, 'spaces');\n```\n\n```js controllers/comments.js\nexports.install = function() {\n\tROUTE('POST /comment/ @json', comment);\n\tROUTE('DELETE /comment/spaces/', remove_step);\n};\n\nasync function comment($) {\n\ttry {\n\t\tvar text = await TRANSFORM('comment', $.body.text);\n\t\t$.json({ text: text });\n\t} catch (err) {\n\t\t$.json({ error: true });\n\t}\n}\n\nfunction remove_step($) {\n\tNEWTRANSFORM('comment', null, 'spaces');\n\t$.success();\n}\n```"}],"knowledge":{"content_sha256":"c9dfc7d277360f14463537cdbe813208627c535f75555adae2c6f31bf79557f3","source_sha256":"6ffcda68b1773e36838b9e35aadb7ed05f1229cb0d502e17a8440950b27c165d","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["DELETE","NEWTRANSFORM","POST","ROUTE","TRANSFORM"],"language":"en"},"files":[{"path":"definitions/transforms.js","language":"js","content":"NEWTRANSFORM('comment', function($, value) {\n\t$.value = value.trim();\n\t$.next();\n}, 'trim');\n\nNEWTRANSFORM('comment', function($, value) {\n\tif (/spam/i.test(value))\n\t\t$.invalid('Spam is not allowed');\n\t$.next();\n}, 'spam');\n\nNEWTRANSFORM('comment', function($, value) {\n\t$.value = value.replace(/\\s+/g, ' ');\n\t$.next();\n}, 'spaces');"},{"path":"controllers/comments.js","language":"js","content":"exports.install = function() {\n\tROUTE('POST /comment/ @json', comment);\n\tROUTE('DELETE /comment/spaces/', remove_step);\n};\n\nasync function comment($) {\n\ttry {\n\t\tvar text = await TRANSFORM('comment', $.body.text);\n\t\t$.json({ text: text });\n\t} catch (err) {\n\t\t$.json({ error: true });\n\t}\n}\n\nfunction remove_step($) {\n\tNEWTRANSFORM('comment', null, 'spaces');\n\t$.success();\n}"}],"test":"const clean = await post('/comment/', { text: '   Hello    brave   new world  ' });\nassert(clean.json && clean.json.text === 'Hello brave new world', 'every step is applied in order, got: ' + clean.body);\n\nconst spam = await post('/comment/', { text: 'Buy SPAM now' });\nassert(spam.json && spam.json.error === true, 'an error reported by a step rejects the promise, got: ' + spam.body);\n\nawait send('DELETE', '/comment/spaces/');\nconst after = await post('/comment/', { text: ' a   b ' });\nassert(after.json && after.json.text === 'a   b', 'a step can be removed by its id, got: ' + after.body);","test_kind":"http"}
{"id":"total5/tms","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/tms/","markdown_url":"https://forge.totaljs.es/en/total5/tms.md","tags":["tms","publish","subscribe","websocket","messaging"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"02fd6154bc5db6f385926f326e0f03ee366b67a8c876eceddd4dc076afdf7bad","checked_at":"2026-10-20T12:18:55.165Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Exchange messages with the Total Messaging Service","description":"Enable the TMS WebSocket endpoint and let external clients receive published messages, send messages to your app and call its functions.","messages":[{"role":"user","content":"Exchange messages with the Total Messaging Service\n\nEnable the TMS WebSocket endpoint and let external clients receive published messages, send messages to your app and call its functions."},{"role":"assistant","content":"TMS (Total Messaging Service) is a small message bus on top of a WebSocket. Turn it on in the `config` file with `$tms : true`; the endpoint is `/$tms/` and `secret_tms` is the token that every client must send in the `x-token` header (a wrong token closes the connection with code 4001, and an address that fails more than 5 times is blocked for a while). When a client connects it receives a `meta` message that lists what the app publishes, what it accepts and what it can do.\n\nThree things can be exposed, each one described by a schema in the same `name:Type` format as an action `input`:\n\n- `NEWPUBLISH(name, schema)` declares a message the app **sends**; `PUBLISH(name, value)` sends it to every client that asked for it by sending `{ type: 'subscribers', subscribers: [name] }`.\n- `NEWSUBSCRIBE(name, schema, callback)` declares a message the app **receives**: a client sends `{ type: 'subscribe', id: name, data: {...} }` and `callback(message, client)` runs only when the data is valid. The validated data is in `message.response` (`message.error` is always `null` there) and `client` is the sender.\n- `NEWCALL(name, schema, callback)` declares a function the client **calls**: the client sends `{ type: 'call', id: name, data: {...}, callbackid: '1' }`, and the app answers with the same message carrying `success: true` and the result in `data`, or `error: true` when the data is invalid. `callback(data, done, client)` produces the result with `done(null, result)`.\n\nPassing `null` as the schema of `NEWPUBLISH` or `NEWSUBSCRIBE` removes the declaration. The official client of Total.js (`TMSCLIENT`) speaks this protocol for you from another Total.js app; any WebSocket client works.\n\n`UNSUBSCRIBE(name, [callback])` removes the listeners of a message that the app receives: only `callback` when you pass it, all of them when you do not. It returns `true` when it removed something and `false` when there was nothing to remove.\n\n```text config\n$tms : true\nsecret_tms : my-tms-token\n```\n\n```js definitions/tms.js\nNEWPUBLISH('news', 'title:String');\n\nNEWSUBSCRIBE('orders', 'id:String, qty:Number', function(message, client) {\n\tMAIN.orders.push(message.response);\n});\n\nNEWCALL('sum', 'a:Number, b:Number', function(data, done) {\n\tdone(null, { total: data.a + data.b });\n});\n\nMAIN.orders = [];\n```\n\n```js controllers/tms.js\nexports.install = function() {\n\tROUTE('POST /news/ @json', news);\n\tROUTE('GET /orders/', orders);\n\tROUTE('GET /unsubscribe/', unsubscribe);\n};\n\nfunction news($) {\n\tPUBLISH('news', { title: $.body.title });\n\t$.success();\n}\n\nfunction orders($) {\n\t$.json(MAIN.orders);\n}\n\nfunction unsubscribe($) {\n\tconst first = UNSUBSCRIBE('orders');\n\tconst second = UNSUBSCRIBE('orders');\n\t$.json({ first: first, second: second });\n}\n```"}],"knowledge":{"content_sha256":"821100b4707c6d136070bfa640d0a05c702215e66bf7abe7101bcf0f64bf3ca0","source_sha256":"02fd6154bc5db6f385926f326e0f03ee366b67a8c876eceddd4dc076afdf7bad","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["GET","MAIN","NEWCALL","NEWPUBLISH","NEWSUBSCRIBE","POST","PUBLISH","ROUTE","TMS","TMSCLIENT","Total.js","UNSUBSCRIBE"],"language":"en"},"files":[{"path":"config","language":"text","content":"$tms : true\nsecret_tms : my-tms-token"},{"path":"definitions/tms.js","language":"js","content":"NEWPUBLISH('news', 'title:String');\n\nNEWSUBSCRIBE('orders', 'id:String, qty:Number', function(message, client) {\n\tMAIN.orders.push(message.response);\n});\n\nNEWCALL('sum', 'a:Number, b:Number', function(data, done) {\n\tdone(null, { total: data.a + data.b });\n});\n\nMAIN.orders = [];"},{"path":"controllers/tms.js","language":"js","content":"exports.install = function() {\n\tROUTE('POST /news/ @json', news);\n\tROUTE('GET /orders/', orders);\n\tROUTE('GET /unsubscribe/', unsubscribe);\n};\n\nfunction news($) {\n\tPUBLISH('news', { title: $.body.title });\n\t$.success();\n}\n\nfunction orders($) {\n\t$.json(MAIN.orders);\n}\n\nfunction unsubscribe($) {\n\tconst first = UNSUBSCRIBE('orders');\n\tconst second = UNSUBSCRIBE('orders');\n\t$.json({ first: first, second: second });\n}"}],"test":"const client = await socket('/$tms/', { headers: { 'x-token': 'my-tms-token' } });\n\nconst meta = await client.next();\nassert(meta.type === 'meta' && meta.publish.some(m => m.id === 'news') && meta.call.some(m => m.id === 'sum'), 'the client receives the meta message, got: ' + JSON.stringify(meta));\n\nclient.send({ type: 'subscribers', subscribers: ['news'] });\nawait new Promise(resolve => setTimeout(resolve, 200));\nawait post('/news/', { title: 'Hello TMS' });\nconst published = await client.next();\nassert(published.type === 'publish' && published.id === 'news' && published.data.title === 'Hello TMS', 'a published message arrives, got: ' + JSON.stringify(published));\n\nclient.send({ type: 'call', id: 'sum', data: { a: 2, b: 3 }, callbackid: '1' });\nconst call = await client.next();\nassert(call.type === 'call' && call.success === true && call.data.total === 5 && call.callbackid === '1', 'the call returns its result, got: ' + JSON.stringify(call));\n\nclient.send({ type: 'subscribe', id: 'orders', data: { id: 'A1', qty: 3 } });\nawait new Promise(resolve => setTimeout(resolve, 200));\nconst orders = await get('/orders/');\nassert(orders.json && orders.json.length === 1 && orders.json[0].id === 'A1' && orders.json[0].qty === 3, 'the app receives the message, got: ' + orders.body);\n\nconst removed = (await get('/unsubscribe/')).json;\nassert(removed.first === true && removed.second === false, 'UNSUBSCRIBE removes the listeners and says whether it removed anything, got: ' + JSON.stringify(removed));\nclient.send({ type: 'subscribe', id: 'orders', data: { id: 'B2', qty: 1 } });\nawait new Promise(resolve => setTimeout(resolve, 200));\nconst after = await get('/orders/');\nassert(after.json.length === 1, 'the app no longer receives the message after UNSUBSCRIBE, got: ' + after.body);\n\nclient.close();","test_kind":"http"}
{"id":"total5/actions-crud","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/actions-crud/","markdown_url":"https://forge.totaljs.es/en/total5/actions-crud.md","tags":["actions","crud","rest","nosql"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"618315379f37edd4d953f8f4a0c78de3af28884039f9da960524b84b11b3b403","checked_at":"2026-10-20T12:17:41.846Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Build a complete REST resource with actions","description":"Declare query, read, insert, update, patch and remove actions over the NoSQL database, with validation, a 404 for missing records and routes that map each HTTP method.","messages":[{"role":"user","content":"Build a complete REST resource with actions\n\nDeclare query, read, insert, update, patch and remove actions over the NoSQL database, with validation, a 404 for missing records and routes that map each HTTP method."},{"role":"assistant","content":"A REST resource is six operations: list (`query`), `read`, `insert`, `update` (replace), `patch` (change a few fields) and `remove`. In Total.js each one is an action with its own validation, so the route table stays a plain map from URL to action. `params` validates the id of the URL, `query` the filters of the list, `input` the body. Inside an action `$.params`, `$.query` and the validated body (second argument of `action`) are already clean values.\n\nReport a missing record with `$.invalid(404)`: it answers with the status code and a JSON error, the same way for every action. `$.success()` answers `{ \"success\": true }` and `$.callback(value)` answers with a value, which is also filtered by the `output` of the action. `UID()` creates the id of a new record.\n\n**`patch` needs `partial: true`.** Without it a field that was not sent is still filled with the default of its type (`''` for a `String`), and `update()` would overwrite the stored value with that empty text. With `partial: true` only the fields present in the request reach the action.\n\n```js actions/products.js\nNEWACTION('Products/query', {\n\tname: 'List products',\n\tquery: 'page:Number, search:String',\n\toutput: 'items:[id:UID, name:String, price:Number], page:Number',\n\taction: function($) {\n\t\tvar page = $.query.page || 1;\n\t\tvar builder = NOSQL('products').find().sort('name').take(10).skip((page - 1) * 10);\n\t\tif ($.query.search)\n\t\t\tbuilder.search('name', $.query.search);\n\t\tbuilder.callback((err, items) => $.callback({ items: items, page: page }));\n\t}\n});\n\nNEWACTION('Products/read', {\n\tname: 'Read a product',\n\tparams: '*id:UID',\n\toutput: 'id:UID, name:String, price:Number',\n\taction: function($) {\n\t\tNOSQL('products').read().where('id', $.params.id).callback(function(err, item) {\n\t\t\tif (item)\n\t\t\t\t$.callback(item);\n\t\t\telse\n\t\t\t\t$.invalid(404);\n\t\t});\n\t}\n});\n\nNEWACTION('Products/insert', {\n\tname: 'Create a product',\n\tinput: '*name:String, *price:Number',\n\taction: function($, model) {\n\t\tmodel.id = UID();\n\t\tNOSQL('products').insert(model).callback(() => $.callback({ id: model.id }));\n\t}\n});\n\nNEWACTION('Products/update', {\n\tname: 'Replace a product',\n\tparams: '*id:UID',\n\tinput: '*name:String, *price:Number',\n\taction: function($, model) {\n\t\tNOSQL('products').update(model).where('id', $.params.id).callback(function(err, count) {\n\t\t\tif (count)\n\t\t\t\t$.success();\n\t\t\telse\n\t\t\t\t$.invalid(404);\n\t\t});\n\t}\n});\n\nNEWACTION('Products/patch', {\n\tname: 'Change some fields of a product',\n\tpartial: true,\n\tparams: '*id:UID',\n\tinput: 'name:String, price:Number',\n\taction: function($, model) {\n\t\tNOSQL('products').update(model).where('id', $.params.id).callback(function(err, count) {\n\t\t\tif (count)\n\t\t\t\t$.success();\n\t\t\telse\n\t\t\t\t$.invalid(404);\n\t\t});\n\t}\n});\n\nNEWACTION('Products/remove', {\n\tname: 'Remove a product',\n\tparams: '*id:UID',\n\taction: function($) {\n\t\tNOSQL('products').remove().where('id', $.params.id).callback(function(err, count) {\n\t\t\tif (count)\n\t\t\t\t$.success();\n\t\t\telse\n\t\t\t\t$.invalid(404);\n\t\t});\n\t}\n});\n```\n\n```js controllers/products.js\nexports.install = function() {\n\tROUTE('GET    /products/            --> *Products/query');\n\tROUTE('GET    /products/{id}/       --> *Products/read');\n\tROUTE('POST   /products/            --> *Products/insert');\n\tROUTE('PUT    /products/{id}/       --> *Products/update');\n\tROUTE('PATCH  /products/{id}/       --> *Products/patch');\n\tROUTE('DELETE /products/{id}/       --> *Products/remove');\n};\n```"}],"knowledge":{"content_sha256":"54d1adaf485ed805011ae1c7cc61741866509242ce858ba3819af61ed0065ab5","source_sha256":"618315379f37edd4d953f8f4a0c78de3af28884039f9da960524b84b11b3b403","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["DELETE","GET","JSON","NEWACTION","NOSQL","PATCH","POST","PUT","REST","ROUTE","Total.js","UID","URL"],"language":"en"},"files":[{"path":"actions/products.js","language":"js","content":"NEWACTION('Products/query', {\n\tname: 'List products',\n\tquery: 'page:Number, search:String',\n\toutput: 'items:[id:UID, name:String, price:Number], page:Number',\n\taction: function($) {\n\t\tvar page = $.query.page || 1;\n\t\tvar builder = NOSQL('products').find().sort('name').take(10).skip((page - 1) * 10);\n\t\tif ($.query.search)\n\t\t\tbuilder.search('name', $.query.search);\n\t\tbuilder.callback((err, items) => $.callback({ items: items, page: page }));\n\t}\n});\n\nNEWACTION('Products/read', {\n\tname: 'Read a product',\n\tparams: '*id:UID',\n\toutput: 'id:UID, name:String, price:Number',\n\taction: function($) {\n\t\tNOSQL('products').read().where('id', $.params.id).callback(function(err, item) {\n\t\t\tif (item)\n\t\t\t\t$.callback(item);\n\t\t\telse\n\t\t\t\t$.invalid(404);\n\t\t});\n\t}\n});\n\nNEWACTION('Products/insert', {\n\tname: 'Create a product',\n\tinput: '*name:String, *price:Number',\n\taction: function($, model) {\n\t\tmodel.id = UID();\n\t\tNOSQL('products').insert(model).callback(() => $.callback({ id: model.id }));\n\t}\n});\n\nNEWACTION('Products/update', {\n\tname: 'Replace a product',\n\tparams: '*id:UID',\n\tinput: '*name:String, *price:Number',\n\taction: function($, model) {\n\t\tNOSQL('products').update(model).where('id', $.params.id).callback(function(err, count) {\n\t\t\tif (count)\n\t\t\t\t$.success();\n\t\t\telse\n\t\t\t\t$.invalid(404);\n\t\t});\n\t}\n});\n\nNEWACTION('Products/patch', {\n\tname: 'Change some fields of a product',\n\tpartial: true,\n\tparams: '*id:UID',\n\tinput: 'name:String, price:Number',\n\taction: function($, model) {\n\t\tNOSQL('products').update(model).where('id', $.params.id).callback(function(err, count) {\n\t\t\tif (count)\n\t\t\t\t$.success();\n\t\t\telse\n\t\t\t\t$.invalid(404);\n\t\t});\n\t}\n});\n\nNEWACTION('Products/remove', {\n\tname: 'Remove a product',\n\tparams: '*id:UID',\n\taction: function($) {\n\t\tNOSQL('products').remove().where('id', $.params.id).callback(function(err, count) {\n\t\t\tif (count)\n\t\t\t\t$.success();\n\t\t\telse\n\t\t\t\t$.invalid(404);\n\t\t});\n\t}\n});"},{"path":"controllers/products.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET    /products/            --> *Products/query');\n\tROUTE('GET    /products/{id}/       --> *Products/read');\n\tROUTE('POST   /products/            --> *Products/insert');\n\tROUTE('PUT    /products/{id}/       --> *Products/update');\n\tROUTE('PATCH  /products/{id}/       --> *Products/patch');\n\tROUTE('DELETE /products/{id}/       --> *Products/remove');\n};"}],"test":"const created = await post('/products/', { name: 'Keyboard', price: 49 });\nassert(created.status === 200 && created.json && created.json.id, 'insert returns the new id, got: ' + created.status + ' ' + created.body);\nconst id = created.json.id;\n\nawait post('/products/', { name: 'Mouse', price: 19 });\n\nconst invalid = await post('/products/', { name: 'No price' });\nassert(invalid.status === 400, 'a missing required field is rejected, got: ' + invalid.status);\n\nconst one = await get('/products/' + id + '/');\nassert(one.json && one.json.name === 'Keyboard' && one.json.price === 49, 'read returns the record, got: ' + one.body);\n\nconst list = await get('/products/');\nassert(list.json && list.json.items.length === 2 && list.json.items[0].name === 'Keyboard' && list.json.page === 1, 'query lists the records sorted by name, got: ' + list.body);\n\nconst found = await get('/products/?search=mouse');\nassert(found.json && found.json.items.length === 1 && found.json.items[0].name === 'Mouse', 'query filters by text, got: ' + found.body);\n\nconst patched = await send('PATCH', '/products/' + id + '/', { price: 59 });\nassert(patched.json && patched.json.success === true, 'patch succeeds, got: ' + patched.body);\nconst afterpatch = await get('/products/' + id + '/');\nassert(afterpatch.json.price === 59 && afterpatch.json.name === 'Keyboard', 'patch changes only the sent fields, got: ' + afterpatch.body);\n\nconst replaced = await send('PUT', '/products/' + id + '/', { name: 'Keyboard Pro', price: 99 });\nassert(replaced.json && replaced.json.success === true, 'update succeeds, got: ' + replaced.body);\nassert((await get('/products/' + id + '/')).json.name === 'Keyboard Pro', 'update changes the record');\n\nconst removed = await send('DELETE', '/products/' + id + '/');\nassert(removed.json && removed.json.success === true, 'remove succeeds, got: ' + removed.body);\n\nconst gone = await get('/products/' + id + '/');\nassert(gone.status === 404, 'a removed record answers 404, got: ' + gone.status);\n\nconst missing = await send('DELETE', '/products/' + id + '/');\nassert(missing.status === 404, 'removing it again answers 404, got: ' + missing.status);","test_kind":"http"}
{"id":"total5/websocket-flags","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/websocket-flags/","markdown_url":"https://forge.totaljs.es/en/total5/websocket-flags.md","tags":["websocket","realtime","security"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"e22b603d1d58fb4be7109dd0a616f91096a32eb28d178f209412f8dabf94e6c3","checked_at":"2026-10-20T12:19:07.501Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Configure a WebSocket route with flags, auth and parameters","description":"Choose the message type with @text or @binary, require a signed-in user with the + prefix, check a CSRF token with @csrf and read URL parameters in SOCKET routes.","messages":[{"role":"user","content":"Configure a WebSocket route with flags, auth and parameters\n\nChoose the message type with @text or @binary, require a signed-in user with the + prefix, check a CSRF token with @csrf and read URL parameters in SOCKET routes."},{"role":"assistant","content":"A `SOCKET` route takes the same building blocks as an HTTP route. By default messages are JSON objects. `@text` makes the incoming messages plain strings and `@binary` makes them `Buffer`s. In a text route `send(message)` still encodes what it sends as JSON, so a string arrives with quotes; pass `true` as the second argument, `send(message, true)`, to send the string as it is. A `+` before `SOCKET` accepts only users that `AUTH()` approves (the connection is refused otherwise), and `{name}` segments are available in `self.params`. `@csrf` checks a CSRF token before the connection is accepted: send it as the `csrf` query string parameter (`/secure/?csrf=...`), and see the CSRF recipe for `secret_csrf` and `$.csrf()`. A middleware is attached with `#name` as on any route.\n\n```text config\nsecret_csrf : change-this-secret-in-production\n```\n\n```js definitions/auth.js\nAUTH(function($) {\n\tif ($.query.token === 'letmein')\n\t\t$.success({ id: '1', name: 'Louis' });\n\telse\n\t\t$.invalid();\n});\n```\n\n```js controllers/sockets.js\nexports.install = function() {\n\tROUTE('SOCKET /text/ @text', text);\n\tROUTE('SOCKET /bin/ @binary', binary);\n\tROUTE('+SOCKET /private/', private_socket);\n\tROUTE('SOCKET /room/{name}/', room);\n\tROUTE('SOCKET /secure/ @csrf', secure);\n\tROUTE('GET /where/', where);\n\tROUTE('GET /csrf/', csrf);\n};\n\nfunction text() {\n\tthis.on('message', function(client, message) {\n\t\tclient.send(typeof(message) + ':' + message.toUpperCase(), true);\n\t});\n}\n\nfunction binary() {\n\tthis.on('message', function(client, message) {\n\t\tclient.send(Buffer.from(Buffer.isBuffer(message) ? 'buffer:' + message.length : 'other'));\n\t});\n}\n\nfunction private_socket() {\n\tthis.on('open', function(client) {\n\t\tclient.send({ hello: client.user.name });\n\t});\n}\n\nfunction room() {\n\tvar self = this;\n\tself.on('open', function(client) {\n\t\tclient.send({ room: self.params.name });\n\t});\n}\n\nfunction secure() {\n\tthis.on('open', function(client) {\n\t\tclient.send({ welcome: true });\n\t});\n}\n\nfunction where($) {\n\t$.json({ url: $.hostname('/bin/') });\n}\n\nfunction csrf($) {\n\t$.json({ csrf: $.csrf() });\n}\n```"}],"knowledge":{"content_sha256":"153249a5572bf8c2fd9e77659ca65a7b282a94a9600af87f3e59004c290776ab","source_sha256":"e22b603d1d58fb4be7109dd0a616f91096a32eb28d178f209412f8dabf94e6c3","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["AUTH","CSRF","GET","HTTP","JSON","ROUTE","SOCKET"],"language":"en"},"files":[{"path":"config","language":"text","content":"secret_csrf : change-this-secret-in-production"},{"path":"definitions/auth.js","language":"js","content":"AUTH(function($) {\n\tif ($.query.token === 'letmein')\n\t\t$.success({ id: '1', name: 'Louis' });\n\telse\n\t\t$.invalid();\n});"},{"path":"controllers/sockets.js","language":"js","content":"exports.install = function() {\n\tROUTE('SOCKET /text/ @text', text);\n\tROUTE('SOCKET /bin/ @binary', binary);\n\tROUTE('+SOCKET /private/', private_socket);\n\tROUTE('SOCKET /room/{name}/', room);\n\tROUTE('SOCKET /secure/ @csrf', secure);\n\tROUTE('GET /where/', where);\n\tROUTE('GET /csrf/', csrf);\n};\n\nfunction text() {\n\tthis.on('message', function(client, message) {\n\t\tclient.send(typeof(message) + ':' + message.toUpperCase(), true);\n\t});\n}\n\nfunction binary() {\n\tthis.on('message', function(client, message) {\n\t\tclient.send(Buffer.from(Buffer.isBuffer(message) ? 'buffer:' + message.length : 'other'));\n\t});\n}\n\nfunction private_socket() {\n\tthis.on('open', function(client) {\n\t\tclient.send({ hello: client.user.name });\n\t});\n}\n\nfunction room() {\n\tvar self = this;\n\tself.on('open', function(client) {\n\t\tclient.send({ room: self.params.name });\n\t});\n}\n\nfunction secure() {\n\tthis.on('open', function(client) {\n\t\tclient.send({ welcome: true });\n\t});\n}\n\nfunction where($) {\n\t$.json({ url: $.hostname('/bin/') });\n}\n\nfunction csrf($) {\n\t$.json({ csrf: $.csrf() });\n}"}],"test":"// The helper sends JSON, so plain text and binary messages use a raw WebSocket\nconst base = (await get('/where/')).json.url.replace(/^http/, 'ws').replace('/bin/', '');\nfunction raw(path, message) {\n\treturn new Promise(function(resolve, reject) {\n\t\tconst ws = new WebSocket(base + path);\n\t\tws.binaryType = 'arraybuffer';\n\t\tws.onopen = () => ws.send(message);\n\t\tws.onmessage = e => { resolve(typeof(e.data) === 'string' ? e.data : Buffer.from(e.data).toString('utf8')); ws.close(); };\n\t\tws.onerror = () => reject(new Error('socket failed: ' + path));\n\t\tsetTimeout(() => reject(new Error('socket timeout: ' + path)), 3000);\n\t});\n}\n\nconst upper = await raw('/text/', 'hello');\nassert(upper === 'string:HELLO', 'with @text the message is a string, got: ' + upper);\n\nconst r = await socket('/room/kitchen/');\nconst joined = await r.next();\nassert(joined && joined.room === 'kitchen', 'the parameter is in self.params, got: ' + JSON.stringify(joined));\nr.close();\n\nlet refused = false;\ntry {\n\tawait socket('/private/');\n} catch (e) {\n\trefused = true;\n}\nassert(refused, 'an unauthenticated connection to a + route is refused');\n\nconst p = await socket('/private/?token=letmein');\nconst hello = await p.next();\nassert(hello && hello.hello === 'Louis', 'the authenticated user is available on the client, got: ' + JSON.stringify(hello));\np.close();\n\nlet blocked = false;\ntry {\n\tawait socket('/secure/');\n} catch (e) {\n\tblocked = true;\n}\nassert(blocked, 'a connection without a CSRF token is refused');\n\nconst token = (await get('/csrf/')).json.csrf;\nconst s = await socket('/secure/?csrf=' + encodeURIComponent(token));\nconst welcome = await s.next();\nassert(welcome && welcome.welcome === true, 'a valid CSRF token is accepted, got: ' + JSON.stringify(welcome));\ns.close();\n\nconst received = await raw('/bin/', new Uint8Array([1, 2, 3, 4]));\nassert(received === 'buffer:4', 'with @binary the message is a Buffer, got: ' + received);","test_kind":"http"}
{"id":"total5/minification","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/minification/","markdown_url":"https://forge.totaljs.es/en/total5/minification.md","tags":["performance","css","javascript","html","minification"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"f08e8f4da87875fe72adba59e4ce7af413ca8d7fefc29050ed6e6ed324db4f7d","checked_at":"2026-10-20T12:18:25.658Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Minify and merge CSS, JavaScript and HTML","description":"Static files and HTML responses are minified automatically; use MERGE() to serve several files as one and the $minify settings to switch it off.","messages":[{"role":"user","content":"Minify and merge CSS, JavaScript and HTML\n\nStatic files and HTML responses are minified automatically; use MERGE() to serve several files as one and the $minify settings to switch it off."},{"role":"assistant","content":"Total.js minifies what it serves without any build step. A `.css` or `.js` file from `public/` is sent with comments and whitespace removed (the code is not renamed or restructured, only compacted), and a string sent with `$.html()` or a rendered view has its HTML comments removed and repeated spaces collapsed. A file whose name contains `.min.` (such as `library.min.js`) is considered minified already and is sent as it is.\n\nThree settings in the `config` file control it, all `true` by default: `$minifyjs`, `$minifycss` and `$minifyhtml`. Set one to `false` to serve that type untouched, which is handy when you debug in the browser.\n\n`MERGE(url, file1, file2, ...)` registers a virtual file that is the concatenation of the given files, minified. The browser makes one request instead of many. File paths are URLs of the `public/` folder.\n\n```css public/css/app.css\n/* the page style */\nbody {\n\tmargin: 0;\n\tcolor: #000000;\n}\n```\n\n```js public/js/app.js\n// greeting\nfunction hello(name) {\n\tvar message = 'Hello ' + name;\n\treturn message;\n}\n```\n\n```js public/js/library.min.js\nfunction   untouched ( ) { return  1 }\n```\n\n```js public/js/two.js\nfunction two() { return 2; }\n```\n\n```js controllers/minify.js\nexports.install = function() {\n\tROUTE('GET /page/', page);\n\tMERGE('/all.js', '/js/app.js', '/js/two.js');\n};\n\nfunction page($) {\n\t$.html('<!DOCTYPE html>\\n<html>\\n  <body>\\n    <!-- a note -->\\n    <p>  Hello   world  </p>\\n  </body>\\n</html>');\n}\n```"}],"knowledge":{"content_sha256":"194c1898038bdd990369cbcb263bcd01f76642eea7059467e5d1544e067099d9","source_sha256":"f08e8f4da87875fe72adba59e4ce7af413ca8d7fefc29050ed6e6ed324db4f7d","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["DOCTYPE","GET","HTML","MERGE","ROUTE","Total.js","URL"],"language":"en"},"files":[{"path":"public/css/app.css","language":"css","content":"/* the page style */\nbody {\n\tmargin: 0;\n\tcolor: #000000;\n}"},{"path":"public/js/app.js","language":"js","content":"// greeting\nfunction hello(name) {\n\tvar message = 'Hello ' + name;\n\treturn message;\n}"},{"path":"public/js/library.min.js","language":"js","content":"function   untouched ( ) { return  1 }"},{"path":"public/js/two.js","language":"js","content":"function two() { return 2; }"},{"path":"controllers/minify.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /page/', page);\n\tMERGE('/all.js', '/js/app.js', '/js/two.js');\n};\n\nfunction page($) {\n\t$.html('<!DOCTYPE html>\\n<html>\\n  <body>\\n    <!-- a note -->\\n    <p>  Hello   world  </p>\\n  </body>\\n</html>');\n}"}],"test":"const css = await get('/css/app.css');\nassert(css.body === 'body{margin:0;color:#000}', 'CSS is minified, got: ' + css.body);\n\nconst js = await get('/js/app.js');\nassert(js.body === \"function hello(name){var message='Hello '+name;return message}\", 'JavaScript is minified, got: ' + js.body);\n\nconst min = await get('/js/library.min.js');\nassert(min.body.indexOf('function   untouched ( )') === 0, 'a .min. file is sent as it is, got: ' + min.body);\n\nconst page = await get('/page/');\nassert(page.body.indexOf('a note') === -1 && page.body.indexOf('Hello world') !== -1, 'HTML comments are removed and spaces collapsed, got: ' + page.body);\n\nconst merged = await get('/all.js');\nassert(merged.body === \"function hello(name){var message='Hello '+name;return message}function two(){return 2}\", 'MERGE joins and minifies the files, got: ' + merged.body);","test_kind":"http"}
{"id":"total5/images","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/images/","markdown_url":"https://forge.totaljs.es/en/total5/images.md","tags":["images","thumbnails","graphicsmagick"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"41cb238f05d0b0bb4f239ead1596a01fe2224bf875e3acacbb6a25ecf0d964bb","checked_at":"2026-10-20T12:18:12.560Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Resize and convert images","description":"Process images with Image.load(), chain resize, crop and format operations, save the result, measure its size and serve thumbnails with $.image().","messages":[{"role":"user","content":"Resize and convert images\n\nProcess images with Image.load(), chain resize, crop and format operations, save the result, measure its size and serve thumbnails with $.image()."},{"role":"assistant","content":"Total.js processes images by calling **GraphicsMagick** (`gm`) or ImageMagick, which must be installed on the server (`brew install graphicsmagick`, `apt install graphicsmagick`). The `$imageconverter` setting chooses the program: `gm` (default), `im` or `magick`.\n\n`Image.load(filename)` (or a `Buffer` or a stream) returns a chain that records the operations and runs them when you call `.save(filename, callback)`. The useful operations are `resize(width, height)` (keeps the proportions inside the box), `thumbnail(width, height)` (a fast, small preview), `miniature(width, height)` (fits the image in the box and fills the rest with the background), `crop(width, height, x, y)`, `rotate(degrees)`, `grayscale()`, `blur(radius)`, `quality(percentage)` and `output('jpg')` to convert the format. Uploaded files do it directly: `file.image()` in a handler for `@upload` routes returns the same chain.\n\nReading the size does not need GraphicsMagick: `Image.measure('png', buffer)` returns `{ width, height }` from the first bytes of PNG, JPG, GIF and WebP data.\n\n`$.image(options)` is for a route that creates a picture on demand: `ext` is the format, `load(next)` hands over the source (a stream or a path) with `next(...)`, `image(img)` describes the operations and `cache` is a name under which the result is kept in `tmp/` for the next requests.\n\n```js controllers/images.js\nconst Zlib = require('zlib');\nconst Fs = require('fs');\n\nexports.install = function() {\n\tROUTE('GET /resize/', resize);\n\tROUTE('GET /thumb/', thumb);\n};\n\n// Builds a solid red PNG so that the example needs no file\nfunction makepng(width, height) {\n\n\tfunction chunk(type, data) {\n\t\tvar length = Buffer.alloc(4);\n\t\tlength.writeUInt32BE(data.length);\n\t\tvar body = Buffer.concat([Buffer.from(type), data]);\n\t\tvar crc = Buffer.alloc(4);\n\t\tcrc.writeUInt32BE(Zlib.crc32(body));\n\t\treturn Buffer.concat([length, body, crc]);\n\t}\n\n\tvar header = Buffer.alloc(13);\n\theader.writeUInt32BE(width, 0);\n\theader.writeUInt32BE(height, 4);\n\theader[8] = 8; // bit depth\n\theader[9] = 2; // RGB\n\n\tvar row = Buffer.alloc(1 + width * 3);\n\tfor (var x = 0; x < width; x++)\n\t\trow[1 + x * 3] = 255;\n\n\tvar raw = Buffer.concat(Array(height).fill(row));\n\n\treturn Buffer.concat([\n\t\tBuffer.from([137, 80, 78, 71, 13, 10, 26, 10]),\n\t\tchunk('IHDR', header),\n\t\tchunk('IDAT', Zlib.deflateSync(raw)),\n\t\tchunk('IEND', Buffer.alloc(0))\n\t]);\n}\n\nfunction source() {\n\tvar filename = PATH.tmp('source.png');\n\tFs.writeFileSync(filename, makepng(200, 100));\n\treturn filename;\n}\n\nfunction resize($) {\n\n\tvar target = PATH.tmp('small.jpg');\n\n\tImage.load(source()).resize(50, 50).output('jpg').save(target, function(err) {\n\n\t\tif (err) {\n\t\t\t$.invalid(err);\n\t\t\treturn;\n\t\t}\n\n\t\tvar size = Image.measure('jpg', Fs.readFileSync(target));\n\t\t$.json({ width: size.width, height: size.height, original: Image.measure('png', Fs.readFileSync(source())) });\n\t});\n}\n\nfunction thumb($) {\n\t$.image({\n\t\text: 'png',\n\t\tcache: 'thumb-example',\n\t\tload: function(next) {\n\t\t\tnext(Fs.createReadStream(source()));\n\t\t},\n\t\timage: function(img) {\n\t\t\timg.resize(40, 40);\n\t\t}\n\t});\n}\n```"}],"knowledge":{"content_sha256":"59df6959402e9050bed76d06e7696a95cec76f8421f71a17fc7bff28bef220a9","source_sha256":"41cb238f05d0b0bb4f239ead1596a01fe2224bf875e3acacbb6a25ecf0d964bb","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["GET","GIF","IDAT","IEND","IHDR","JPG","PATH","PNG","RGB","ROUTE","Total.js"],"language":"en"},"files":[{"path":"controllers/images.js","language":"js","content":"const Zlib = require('zlib');\nconst Fs = require('fs');\n\nexports.install = function() {\n\tROUTE('GET /resize/', resize);\n\tROUTE('GET /thumb/', thumb);\n};\n\n// Builds a solid red PNG so that the example needs no file\nfunction makepng(width, height) {\n\n\tfunction chunk(type, data) {\n\t\tvar length = Buffer.alloc(4);\n\t\tlength.writeUInt32BE(data.length);\n\t\tvar body = Buffer.concat([Buffer.from(type), data]);\n\t\tvar crc = Buffer.alloc(4);\n\t\tcrc.writeUInt32BE(Zlib.crc32(body));\n\t\treturn Buffer.concat([length, body, crc]);\n\t}\n\n\tvar header = Buffer.alloc(13);\n\theader.writeUInt32BE(width, 0);\n\theader.writeUInt32BE(height, 4);\n\theader[8] = 8; // bit depth\n\theader[9] = 2; // RGB\n\n\tvar row = Buffer.alloc(1 + width * 3);\n\tfor (var x = 0; x < width; x++)\n\t\trow[1 + x * 3] = 255;\n\n\tvar raw = Buffer.concat(Array(height).fill(row));\n\n\treturn Buffer.concat([\n\t\tBuffer.from([137, 80, 78, 71, 13, 10, 26, 10]),\n\t\tchunk('IHDR', header),\n\t\tchunk('IDAT', Zlib.deflateSync(raw)),\n\t\tchunk('IEND', Buffer.alloc(0))\n\t]);\n}\n\nfunction source() {\n\tvar filename = PATH.tmp('source.png');\n\tFs.writeFileSync(filename, makepng(200, 100));\n\treturn filename;\n}\n\nfunction resize($) {\n\n\tvar target = PATH.tmp('small.jpg');\n\n\tImage.load(source()).resize(50, 50).output('jpg').save(target, function(err) {\n\n\t\tif (err) {\n\t\t\t$.invalid(err);\n\t\t\treturn;\n\t\t}\n\n\t\tvar size = Image.measure('jpg', Fs.readFileSync(target));\n\t\t$.json({ width: size.width, height: size.height, original: Image.measure('png', Fs.readFileSync(source())) });\n\t});\n}\n\nfunction thumb($) {\n\t$.image({\n\t\text: 'png',\n\t\tcache: 'thumb-example',\n\t\tload: function(next) {\n\t\t\tnext(Fs.createReadStream(source()));\n\t\t},\n\t\timage: function(img) {\n\t\t\timg.resize(40, 40);\n\t\t}\n\t});\n}"}],"test":"const result = await get('/resize/');\nassert(result.json, 'expected JSON, got: ' + result.status + ' ' + result.body);\nassert(result.json.original.width === 200 && result.json.original.height === 100, 'measure() reads the PNG size, got: ' + result.body);\nassert(result.json.width === 50 && result.json.height === 25, 'resize() keeps the proportions inside 50x50, got: ' + result.body);\n\nconst picture = await get('/thumb/');\nassert(picture.status === 200 && /image\\/png/.test(picture.headers.get('content-type')), '$.image() answers with an image, got: ' + picture.status + ' ' + picture.headers.get('content-type'));\nassert(picture.body.substring(1, 4) === 'PNG', '$.image() sends the converted image, got: ' + JSON.stringify(picture.body.substring(0, 8)));","test_kind":"http"}
{"id":"total5/numeric-schema-boundaries","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/numeric-schema-boundaries/","markdown_url":"https://forge.totaljs.es/en/total5/numeric-schema-boundaries.md","tags":["validation","schema","numbers","boundaries"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"f7f4f3bc2d159c94708d510b8216af80f563c9120a6fd011273c9a17cab47171","checked_at":"2026-10-20T12:18:29.497Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Verify numeric schema boundaries instead of assuming integer coercion","description":"Check Int, SmallInt, TinyInt and Number2 with native compact schemas, including fractional values, zero and out-of-range inputs.","messages":[{"role":"user","content":"Verify numeric schema boundaries instead of assuming integer coercion\n\nCheck Int, SmallInt, TinyInt and Number2 with native compact schemas, including fractional values, zero and out-of-range inputs."},{"role":"assistant","content":"In `total5@0.0.19`, the compact numeric types all enter the native number validator. **`Int` does not guarantee an integer**: `7.9` is preserved. `Number2` also preserves this fraction; do not interpret its name as rounding to two decimal places. Required numeric zero is accepted.\n\n`TinyInt` accepts values from 0 through 255. `SmallInt` accepts -32767 through 32767; notably, -32768 is rejected in this version. These subtype checks enforce bounds but do not require integral values: `TinyInt` accepts `1.5`. Apply an explicit `Number.isInteger()` check for identifiers, counters or quantities that must be integers. These observations describe this version and the tested values, not SQL column semantics.\n\n```js controllers/numbers.js\nvar schema = NEWSCHEMA('NumericBoundaries', '*integer:Int,*small:SmallInt,*tiny:TinyInt,*decimal:Number2');\n\nexports.install = function() {\n\tROUTE('POST /numbers/ @json', numbers);\n};\n\nfunction numbers($) {\n\tvar result = schema.transform($.body);\n\tif (result.error) {\n\t\t$.response.status = 400;\n\t\t$.json({ error: 'invalid numeric input' });\n\t\treturn;\n\t}\n\t$.json(result.response);\n}\n```"}],"knowledge":{"content_sha256":"164490677c2f9a2f3852886b573a46742243ade192a31b09fb8cad9656e73591","source_sha256":"f7f4f3bc2d159c94708d510b8216af80f563c9120a6fd011273c9a17cab47171","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["NEWSCHEMA","Number.isInteger","POST","ROUTE","SQL"],"language":"en"},"files":[{"path":"controllers/numbers.js","language":"js","content":"var schema = NEWSCHEMA('NumericBoundaries', '*integer:Int,*small:SmallInt,*tiny:TinyInt,*decimal:Number2');\n\nexports.install = function() {\n\tROUTE('POST /numbers/ @json', numbers);\n};\n\nfunction numbers($) {\n\tvar result = schema.transform($.body);\n\tif (result.error) {\n\t\t$.response.status = 400;\n\t\t$.json({ error: 'invalid numeric input' });\n\t\treturn;\n\t}\n\t$.json(result.response);\n}"}],"test":"const base = { integer: '7.9', small: -32767, tiny: 255, decimal: '1.2345' };\nconst converted = await post('/numbers/', base);\nassert(converted.status === 200, 'numeric strings accepted: ' + converted.body);\nassert(converted.json.integer === 7.9 && converted.json.decimal === 1.2345, 'Int preserves fractions and Number2 does not round');\nconst zero = await post('/numbers/', { integer: 0, small: 0, tiny: 0, decimal: 0 });\nassert(zero.status === 200 && zero.json.tiny === 0, 'required zero accepted');\nconst fraction = await post('/numbers/', { ...base, tiny: 1.5 });\nassert(fraction.status === 200 && fraction.json.tiny === 1.5, 'TinyInt checks bounds, not integrality');\nfor (const patch of [{ tiny: -1 }, { tiny: 256 }, { small: -32768 }, { small: 32768 }, { integer: 'not a number' }]) {\n\tconst invalid = await post('/numbers/', { ...base, ...patch });\n\tassert(invalid.status === 400, 'reject ' + JSON.stringify(patch) + ': ' + invalid.body);\n}","test_kind":"http"}
{"id":"total5/utilities","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/utilities/","markdown_url":"https://forge.totaljs.es/en/total5/utilities.md","tags":["utilities","helpers","string","array","date"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"0c4ecc67d96e26c574ea6278a211e858605813a7baabab91de166f021ed3b23d","checked_at":"2026-10-20T12:18:58.805Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Use the built-in string, number, date and array helpers","description":"Total.js extends String, Number, Date and Array with helpers for slugs, hashes, formatting, parsing, grouping and searching, and offers U.extend() and U.clone() for objects.","messages":[{"role":"user","content":"Use the built-in string, number, date and array helpers\n\nTotal.js extends String, Number, Date and Array with helpers for slugs, hashes, formatting, parsing, grouping and searching, and offers U.extend() and U.clone() for objects."},{"role":"assistant","content":"Total.js adds a set of methods to the JavaScript built-ins. They are available everywhere in the app, so they are worth knowing before you write a helper of your own. The ones below are exercised by the example.\n\n**String:** `slug([max])` makes a URL-safe text (lowercase, no accents or symbols); `format(a, b)` replaces `{0}`, `{1}`; `encode()` / `decode()` escape and restore HTML; `padLeft(length, char)` / `padRight()`; `parseInt2()` and `parseFloat2()` read a number out of a text and return `0` when there is none; `isEmail()`, `isURL()` and `isJSON()` validate; `parseJSON()` parses JSON and returns `null` on error; `md5()`, `sha1()` and `sha256()` return hex hashes; `capitalize()` upper-cases the first letter of every word.\n\n**Number:** `format(decimals, thousands, decimal)`, for example `(1234567.891).format(2)` gives `1 234 567.89`.\n\n**Date:** `format('yyyy-MM-dd HH:mm')`, `add('1 day')` and `diff(date, 'days')`.\n\n**Array:** `take(n)` and `skip(n)`, `first()` and `last()`, `trim()` (trims the texts and removes the empty ones), `findItem(field, value)` and `findIndex(field, value)`, `remove(value)` (removes every match and returns the array), `group(field)` (returns `[{ name, items }]`) and `quicksort(field)`.\n\n**Objects:** `U.extend(target, source)` merges `source` into `target` and returns it; `U.clone(object)` makes a deep copy.\n\n```js controllers/utilities.js\nexports.install = function() {\n\tROUTE('GET /utils/', utils);\n};\n\nfunction utils($) {\n\n\tvar items = [{ type: 'a', n: 1 }, { type: 'b', n: 2 }, { type: 'a', n: 3 }];\n\tvar original = { a: { b: 1 } };\n\tvar copy = U.clone(original);\n\tcopy.a.b = 2;\n\n\t$.json({\n\t\tslug: 'Hello Wörld & Co!'.slug(),\n\t\tslugmax: 'Hello brave new world'.slug(11),\n\t\tformat: '{0} has {1} items'.format('Ann', 3),\n\t\tencode: '<a href=\"x\">&</a>'.encode(),\n\t\tdecode: '&lt;b&gt;'.decode(),\n\t\tpad: ['7'.padLeft(3, '0'), '7'.padRight(3, '0')],\n\t\tnumbers: ['abc12x'.parseInt2(), '3,5kg'.parseFloat2(), 'none'.parseInt2()],\n\t\tis: ['a@b.cz'.isEmail(), 'nope'.isEmail(), 'https://totaljs.com'.isURL(), '{\"a\":1}'.isJSON(), 'abc'.isJSON()],\n\t\tjson: ['{\"a\":1}'.parseJSON(), 'not json'.parseJSON()],\n\t\tmd5: 'abc'.md5(),\n\t\tsha1: 'abc'.sha1(),\n\t\tcapitalize: 'hello big world'.capitalize(),\n\t\tnumber: [(1234567.891).format(2), (1234567.891).format(2, ' ', ',')],\n\t\tdate: [new Date(2026, 0, 5, 9, 7).format('yyyy-MM-dd HH:mm'), new Date(2026, 0, 31).add('1 day').format('yyyy-MM-dd'), new Date(2026, 0, 5).diff(new Date(2026, 0, 1), 'days')],\n\t\ttake: [[1, 2, 3, 4, 5].take(2), [1, 2, 3, 4, 5].skip(3), [1, 2, 3].first(), [1, 2, 3].last()],\n\t\ttrim: ['a', '', ' b '].trim(),\n\t\tfind: [[{ id: 1 }, { id: 2 }].findItem('id', 2), [{ id: 1 }, { id: 2 }].findIndex('id', 2)],\n\t\tremove: [1, 2, 3, 2].remove(2),\n\t\tgroup: items.group('type'),\n\t\tsort: [{ n: 'b' }, { n: 'a' }].quicksort('n'),\n\t\textend: U.extend({ a: 1 }, { b: 2 }),\n\t\tclone: [original.a.b, copy.a.b]\n\t});\n}\n```"}],"knowledge":{"content_sha256":"d746997c3cbc0c0a1fa153c1794c7f9bb910cc42ba745b6782ca68e08e6f57ea","source_sha256":"0c4ecc67d96e26c574ea6278a211e858605813a7baabab91de166f021ed3b23d","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["GET","HTML","JSON","ROUTE","Total.js","URL"],"language":"en"},"files":[{"path":"controllers/utilities.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /utils/', utils);\n};\n\nfunction utils($) {\n\n\tvar items = [{ type: 'a', n: 1 }, { type: 'b', n: 2 }, { type: 'a', n: 3 }];\n\tvar original = { a: { b: 1 } };\n\tvar copy = U.clone(original);\n\tcopy.a.b = 2;\n\n\t$.json({\n\t\tslug: 'Hello Wörld & Co!'.slug(),\n\t\tslugmax: 'Hello brave new world'.slug(11),\n\t\tformat: '{0} has {1} items'.format('Ann', 3),\n\t\tencode: '<a href=\"x\">&</a>'.encode(),\n\t\tdecode: '&lt;b&gt;'.decode(),\n\t\tpad: ['7'.padLeft(3, '0'), '7'.padRight(3, '0')],\n\t\tnumbers: ['abc12x'.parseInt2(), '3,5kg'.parseFloat2(), 'none'.parseInt2()],\n\t\tis: ['a@b.cz'.isEmail(), 'nope'.isEmail(), 'https://totaljs.com'.isURL(), '{\"a\":1}'.isJSON(), 'abc'.isJSON()],\n\t\tjson: ['{\"a\":1}'.parseJSON(), 'not json'.parseJSON()],\n\t\tmd5: 'abc'.md5(),\n\t\tsha1: 'abc'.sha1(),\n\t\tcapitalize: 'hello big world'.capitalize(),\n\t\tnumber: [(1234567.891).format(2), (1234567.891).format(2, ' ', ',')],\n\t\tdate: [new Date(2026, 0, 5, 9, 7).format('yyyy-MM-dd HH:mm'), new Date(2026, 0, 31).add('1 day').format('yyyy-MM-dd'), new Date(2026, 0, 5).diff(new Date(2026, 0, 1), 'days')],\n\t\ttake: [[1, 2, 3, 4, 5].take(2), [1, 2, 3, 4, 5].skip(3), [1, 2, 3].first(), [1, 2, 3].last()],\n\t\ttrim: ['a', '', ' b '].trim(),\n\t\tfind: [[{ id: 1 }, { id: 2 }].findItem('id', 2), [{ id: 1 }, { id: 2 }].findIndex('id', 2)],\n\t\tremove: [1, 2, 3, 2].remove(2),\n\t\tgroup: items.group('type'),\n\t\tsort: [{ n: 'b' }, { n: 'a' }].quicksort('n'),\n\t\textend: U.extend({ a: 1 }, { b: 2 }),\n\t\tclone: [original.a.b, copy.a.b]\n\t});\n}"}],"test":"const r = (await get('/utils/')).json;\nassert(r, 'expected JSON');\nassert(r.slug === 'hello-world-co' && r.slugmax === 'hello-brave', 'slug, got: ' + r.slug + ' / ' + r.slugmax);\nassert(r.format === 'Ann has 3 items', 'format, got: ' + r.format);\nassert(r.encode === '&lt;a href=&quot;x&quot;&gt;&amp;&lt;/a&gt;' && r.decode === '<b>', 'encode and decode, got: ' + r.encode + ' / ' + r.decode);\nassert(r.pad[0] === '007' && r.pad[1] === '700', 'padLeft and padRight, got: ' + r.pad);\nassert(r.numbers[0] === 12 && r.numbers[1] === 3.5 && r.numbers[2] === 0, 'parseInt2 and parseFloat2, got: ' + r.numbers);\nassert(JSON.stringify(r.is) === '[true,false,true,true,false]', 'validators, got: ' + r.is);\nassert(r.json[0].a === 1 && r.json[1] === null, 'parseJSON, got: ' + JSON.stringify(r.json));\nassert(r.md5 === '900150983cd24fb0d6963f7d28e17f72' && r.sha1 === 'a9993e364706816aba3e25717850c26c9cd0d89d', 'hashes');\nassert(r.capitalize === 'Hello Big World', 'capitalize, got: ' + r.capitalize);\nassert(r.number[0] === '1 234 567.89' && r.number[1] === '1 234 567,89', 'number format, got: ' + r.number);\nassert(r.date[0] === '2026-01-05 09:07' && r.date[1] === '2026-02-01' && r.date[2] === 4, 'dates, got: ' + r.date);\nassert(JSON.stringify(r.take) === '[[1,2],[4,5],1,3]', 'take, skip, first, last, got: ' + JSON.stringify(r.take));\nassert(JSON.stringify(r.trim) === '[\"a\",\"b\"]', 'trim, got: ' + JSON.stringify(r.trim));\nassert(r.find[0].id === 2 && r.find[1] === 1, 'findItem and findIndex, got: ' + JSON.stringify(r.find));\nassert(JSON.stringify(r.remove) === '[1,3]', 'remove, got: ' + JSON.stringify(r.remove));\nassert(r.group.length === 2 && r.group[0].name === 'a' && r.group[0].items.length === 2, 'group, got: ' + JSON.stringify(r.group));\nassert(r.sort[0].n === 'a', 'quicksort, got: ' + JSON.stringify(r.sort));\nassert(r.extend.a === 1 && r.extend.b === 2, 'extend, got: ' + JSON.stringify(r.extend));\nassert(r.clone[0] === 1 && r.clone[1] === 2, 'clone is a deep copy, got: ' + JSON.stringify(r.clone));","test_kind":"http"}
{"id":"total5/action-audit","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/action-audit/","markdown_url":"https://forge.totaljs.es/en/total5/action-audit.md","tags":["actions","audit","security"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"654c991eaffefc7d84ce352a5f0625680b0dc82071adb3a7ac8b90f6436d34ec","checked_at":"2026-10-20T12:17:35.485Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Record action audit events with a native audit sink","description":"Observe the audit option and explicit $.audit() calls without contacting a logging service or writing sensitive input to an application log.","messages":[{"role":"user","content":"Record action audit events with a native audit sink\n\nObserve the audit option and explicit $.audit() calls without contacting a logging service or writing sensitive input to an application log."},{"role":"assistant","content":"An action can set `audit` to a text template. On successful completion, the framework sends an audit record to `DEF.onAudit(name, data, context)`. The automatic record uses the sink name `actions` and includes the action id. An action can also call `$.audit(message, type)` explicitly; this emits its own record and can carry a type such as `info`.\n\nThe example replaces the audit sink with an in-memory collector to inspect its contract. It records only the name, action id, message and type. The framework also prepares contextual data and serialized request data: a real sink needs an explicit policy for secrets, retention, access and failures. An audit event is not evidence that a business transaction committed; the action should call its success callback only after the intended write succeeds.\n\nIn this version the automatic audit event occurs after the successful action callback path. It does not automatically log validation failures or failed actions.\n\n```js definitions/audit.js\nMAIN.auditEvents = [];\nDEF.onAudit = function(name, data) {\n\tMAIN.auditEvents.push({ sink: name || null, action: data.action, message: data.message || '', type: data.type || null });\n};\n\nNEWACTION('AuditExample|save', {\n\tinput: '*name:String',\n\taudit: 'Saved the example',\n\taction: function($) {\n\t\t$.callback({ ok: true });\n\t}\n});\n\nNEWACTION('AuditExample|explicit', {\n\taction: function($) {\n\t\t$.audit('Explicit example event', 'info');\n\t\t$.success();\n\t}\n});\n\nNEWACTION('AuditExample|fail', {\n\taudit: 'This must not become a success event',\n\taction: function($) {\n\t\t$.invalid(409);\n\t}\n});\n```\n\n```js controllers/audit.js\nexports.install = function() {\n\tROUTE('POST /save/ --> *AuditExample|save');\n\tROUTE('POST /explicit/ --> *AuditExample|explicit');\n\tROUTE('POST /fail/ --> *AuditExample|fail');\n\tROUTE('GET /events/', function($) { $.json(MAIN.auditEvents); });\n};\n```"}],"knowledge":{"content_sha256":"b949a031d439b733931dd095add9923cac6304897ab4528084ca59651f89a176","source_sha256":"654c991eaffefc7d84ce352a5f0625680b0dc82071adb3a7ac8b90f6436d34ec","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["DEF","GET","MAIN","NEWACTION","POST","ROUTE"],"language":"en"},"files":[{"path":"definitions/audit.js","language":"js","content":"MAIN.auditEvents = [];\nDEF.onAudit = function(name, data) {\n\tMAIN.auditEvents.push({ sink: name || null, action: data.action, message: data.message || '', type: data.type || null });\n};\n\nNEWACTION('AuditExample|save', {\n\tinput: '*name:String',\n\taudit: 'Saved the example',\n\taction: function($) {\n\t\t$.callback({ ok: true });\n\t}\n});\n\nNEWACTION('AuditExample|explicit', {\n\taction: function($) {\n\t\t$.audit('Explicit example event', 'info');\n\t\t$.success();\n\t}\n});\n\nNEWACTION('AuditExample|fail', {\n\taudit: 'This must not become a success event',\n\taction: function($) {\n\t\t$.invalid(409);\n\t}\n});"},{"path":"controllers/audit.js","language":"js","content":"exports.install = function() {\n\tROUTE('POST /save/ --> *AuditExample|save');\n\tROUTE('POST /explicit/ --> *AuditExample|explicit');\n\tROUTE('POST /fail/ --> *AuditExample|fail');\n\tROUTE('GET /events/', function($) { $.json(MAIN.auditEvents); });\n};"}],"test":"const valid = await post('/save/', { name: 'Example' });\nassert(valid.status === 200, 'successful action');\nlet events = (await get('/events/')).json;\nassert(events.length === 1 && events[0].sink === 'actions' && events[0].action === 'AuditExample|save' && events[0].message === 'Saved the example', 'automatic audit event: ' + JSON.stringify(events));\nawait post('/save/', {});\nassert((await get('/events/')).json.length === 1, 'invalid input emits no success audit event');\nconst failed = await post('/fail/', {});\nassert(failed.status === 409 && (await get('/events/')).json.length === 1, 'failed action emits no automatic success audit event');\nawait post('/explicit/', {});\nevents = (await get('/events/')).json;\nassert(events.length === 2 && events[1].message === 'Explicit example event' && events[1].type === 'info' && events[1].action === 'AuditExample|explicit', 'explicit audit context: ' + JSON.stringify(events));","test_kind":"http"}
{"id":"total5/release-mode-and-debugging","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/release-mode-and-debugging/","markdown_url":"https://forge.totaljs.es/en/total5/release-mode-and-debugging.md","tags":["deployment","debug","release","environment"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"6be468d0f03f1f8fc61a648f9622414a6de9ce287e6d2ffdf83812a91a9e9856","checked_at":"2026-10-20T12:18:39.506Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Run in debug and release mode","description":"The options of Total.run() choose between debug and release mode; read the mode from DEBUG and limit routes to one mode with @debug and @release.","messages":[{"role":"user","content":"Run in debug and release mode\n\nThe options of Total.run() choose between debug and release mode; read the mode from DEBUG and limit routes to one mode with @debug and @release."},{"role":"assistant","content":"Total.js runs in one of two modes, **debug** for development and **release** for the server, and the global boolean `DEBUG` tells your code which one is active. In debug mode the app runs under a file watcher that restarts it when you change a file, errors show details and source maps are generated. Release mode has no watcher and error pages without the technical details.\n\nThe mode is chosen by how the app is started, in the start script (`index.js`) that calls `Total.run(options)`:\n\n- the default is debug, with the watcher;\n- `options.release = true` starts release mode; the start script that Total.js generates sets it from the command line, so `node index.js --release` is the way to start production;\n- `options.watcher = false` runs a single process without the watcher, and that is always release mode (it is what the examples of this cookbook use).\n\nBefore `Total.run()` has decided, `DEBUG` has a default taken from `NODE_ENV`: it is `false` when `NODE_ENV=production` and `true` otherwise. Code that runs at the moment a module is loaded sees that default, so do not rely on `NODE_ENV` for the final mode, only on `DEBUG` after the app has started.\n\nThe routes can follow the mode. The flag `@debug` makes a route exist only in debug mode and `@release` only in release mode; in the other mode the URL answers 404. Use `@debug` for tools you need while developing (a database viewer, a seed route) so they can never reach production by accident.\n\n```js controllers/mode.js\nexports.install = function() {\n\tROUTE('GET /mode/', mode);\n\tROUTE('GET /tools/ @debug', tools);\n\tROUTE('GET /status/ @release', status);\n};\n\nfunction mode($) {\n\t$.json({ debug: DEBUG });\n}\n\nfunction tools($) {\n\t$.json({ tools: true });\n}\n\nfunction status($) {\n\t$.json({ status: 'ok' });\n}\n```"}],"knowledge":{"content_sha256":"6b9c467dade4b69e103b12fa2687c66dae398141f5b622497f90a81bba6e01b9","source_sha256":"6be468d0f03f1f8fc61a648f9622414a6de9ce287e6d2ffdf83812a91a9e9856","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["DEBUG","GET","NODE_ENV","ROUTE","Total.js","Total.run","URL"],"language":"en"},"files":[{"path":"controllers/mode.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /mode/', mode);\n\tROUTE('GET /tools/ @debug', tools);\n\tROUTE('GET /status/ @release', status);\n};\n\nfunction mode($) {\n\t$.json({ debug: DEBUG });\n}\n\nfunction tools($) {\n\t$.json({ tools: true });\n}\n\nfunction status($) {\n\t$.json({ status: 'ok' });\n}"}],"test":"// This app runs with { watcher: false }, which is release mode\nconst mode = (await get('/mode/')).json;\nassert(mode && mode.debug === false, 'watcher: false means release mode, got: ' + JSON.stringify(mode));\n\nassert((await get('/status/')).status === 200, 'a @release route exists in release mode');\nassert((await get('/tools/')).status === 404, 'a @debug route answers 404 in release mode');\n\n// The default of DEBUG before Total.run() depends on NODE_ENV\nconst fs = process.getBuiltinModule('fs');\nconst os = process.getBuiltinModule('os');\nconst path = process.getBuiltinModule('path');\nconst script = path.join(os.tmpdir(), 'forge-debug-default-' + process.pid + '.js');\nfs.writeFileSync(script, \"require(\" + JSON.stringify(path.join(process.cwd(), 'node_modules', 'total5')) + \"); console.log('DEBUG=' + DEBUG); process.exit();\");\n\nconst spawn = process.getBuiltinModule('child_process').spawnSync;\nconst production = spawn(process.execPath, [script], { env: Object.assign({}, process.env, { NODE_ENV: 'production' }), encoding: 'utf8' });\nconst development = spawn(process.execPath, [script], { env: Object.assign({}, process.env, { NODE_ENV: 'development' }), encoding: 'utf8' });\nfs.unlinkSync(script);\n\nassert(/DEBUG=false/.test(production.stdout), 'NODE_ENV=production makes the default false, got: ' + production.stdout + production.stderr);\nassert(/DEBUG=true/.test(development.stdout), 'any other NODE_ENV makes the default true, got: ' + development.stdout + development.stderr);","test_kind":"http"}
{"id":"total5/tests","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/tests/","markdown_url":"https://forge.totaljs.es/en/total5/tests.md","tags":["testing","ci"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"1c2dfe62f60da8a991d433a5e6a0d02538db2afd136f88496c489766a78daabe","checked_at":"2026-10-20T12:18:53.095Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Test the application with the built-in test runner","description":"Write a test.js that starts the app, registers tests with Test.push(), checks results with Test.print() and exits with a non-zero code when one fails.","messages":[{"role":"user","content":"Test the application with the built-in test runner\n\nWrite a test.js that starts the app, registers tests with Test.push(), checks results with Test.print() and exits with a non-zero code when one fails."},{"role":"assistant","content":"Total.js has a small test runner, `total5/test`. A test file is a normal Total.js script: it loads the framework and the runner, starts the application and, when it is `ready`, registers its tests. `Test.push(name, fn)` adds a test; `fn(next)` does its work and calls `next()` to continue with the following one. `Test.print(message, error)` reports one check: pass `null` as `error` for success (`[OK]` is printed) or a message for failure (`[FAIL]` is printed and the process exits with code 1). `Test.run()` runs the registered tests in order and exits with code 0 at the end.\n\nBecause failure ends the process with a non-zero code, the file can be called by `npm test` and by any CI service. Wait a moment (about 500 ms) after `ready` before the first request: a request sent right away can still get a 404. Use `RESTBuilder` (or `REQUEST`) against the local server to test routes, and call actions directly with `ACTION()`.\n\n```js controllers/sum.js\nexports.install = function() {\n\tROUTE('GET /sum/{a}/{b}/', sum);\n\tROUTE('GET /dir/', dir);\n};\n\nfunction sum($) {\n\t$.json({ total: +$.params.a + +$.params.b });\n}\n\nfunction dir($) {\n\t$.json({ dir: PATH.root() });\n}\n```\n\n```js test.js\nrequire('total5');\nrequire('total5/test');\n\n// Another port than the application uses, so both can run\nTotal.run({ port: 8765, release: false, watcher: false });\n\nON('ready', function() {\n\n\t// A request sent in the same tick as \"ready\" can still get a 404, so give the routes a moment\n\tsetTimeout(runtests, 500);\n});\n\nfunction runtests() {\n\n\tTest.push('The sum route', function(next) {\n\t\tRESTBuilder.GET('http://127.0.0.1:8765/sum/2/3/').exec(function(err, response) {\n\t\t\tTest.print('2 + 3 = 5', response && response.total === 5 ? null : 'Expected 5, got ' + JSON.stringify(response));\n\t\t\tnext();\n\t\t});\n\t});\n\n\tTest.push('Unknown route', function(next) {\n\t\tRESTBuilder.GET('http://127.0.0.1:8765/missing/').exec(function(err, response, output) {\n\t\t\tTest.print('/missing/ answers 404', output.status === 404 ? null : 'Expected 404, got ' + output.status);\n\t\t\tnext();\n\t\t});\n\t});\n\n\tTest.run();\n}\n```"}],"knowledge":{"content_sha256":"86c51b7edfa17059a77abaa717c02fc9a5330e98f06d50fca0798bcb7ec815df","source_sha256":"1c2dfe62f60da8a991d433a5e6a0d02538db2afd136f88496c489766a78daabe","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["ACTION","FAIL","GET","JSON","PATH","REQUEST","RESTB","ROUTE","Total.js","Total.run"],"language":"en"},"files":[{"path":"controllers/sum.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /sum/{a}/{b}/', sum);\n\tROUTE('GET /dir/', dir);\n};\n\nfunction sum($) {\n\t$.json({ total: +$.params.a + +$.params.b });\n}\n\nfunction dir($) {\n\t$.json({ dir: PATH.root() });\n}"},{"path":"test.js","language":"js","content":"require('total5');\nrequire('total5/test');\n\n// Another port than the application uses, so both can run\nTotal.run({ port: 8765, release: false, watcher: false });\n\nON('ready', function() {\n\n\t// A request sent in the same tick as \"ready\" can still get a 404, so give the routes a moment\n\tsetTimeout(runtests, 500);\n});\n\nfunction runtests() {\n\n\tTest.push('The sum route', function(next) {\n\t\tRESTBuilder.GET('http://127.0.0.1:8765/sum/2/3/').exec(function(err, response) {\n\t\t\tTest.print('2 + 3 = 5', response && response.total === 5 ? null : 'Expected 5, got ' + JSON.stringify(response));\n\t\t\tnext();\n\t\t});\n\t});\n\n\tTest.push('Unknown route', function(next) {\n\t\tRESTBuilder.GET('http://127.0.0.1:8765/missing/').exec(function(err, response, output) {\n\t\t\tTest.print('/missing/ answers 404', output.status === 404 ? null : 'Expected 404, got ' + output.status);\n\t\t\tnext();\n\t\t});\n\t});\n\n\tTest.run();\n}"}],"test":"const dir = (await get('/dir/')).json.dir;\nconst spawn = process.getBuiltinModule('child_process').spawnSync;\nconst fs = process.getBuiltinModule('fs');\nconst path = process.getBuiltinModule('path');\n\nconst passing = spawn(process.execPath, ['test.js'], { cwd: dir, encoding: 'utf8', timeout: 30000 });\nassert(passing.status === 0, 'the test file exits with 0 when all checks pass, got: ' + passing.status + '\\n' + passing.stdout + passing.stderr);\nassert(/\\[OK\\] 2 \\+ 3 = 5/.test(passing.stdout) && /\\[OK\\] \\/missing\\/ answers 404/.test(passing.stdout), 'every check is reported, got: ' + passing.stdout);\n\n// The same file with a wrong expectation fails with a non-zero code\nconst file = path.join(dir, 'test.js');\nfs.writeFileSync(file, fs.readFileSync(file, 'utf8').replace('response.total === 5', 'response.total === 6'));\nconst failing = spawn(process.execPath, ['test.js'], { cwd: dir, encoding: 'utf8', timeout: 30000 });\nassert(failing.status === 1 && /\\[FAIL\\] 2 \\+ 3 = 5/.test(failing.stdout), 'a failed check exits with 1, got: ' + failing.status + '\\n' + failing.stdout);","test_kind":"http"}
{"id":"total5/cluster","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/cluster/","markdown_url":"https://forge.totaljs.es/en/total5/cluster.md","tags":["cluster","performance","deployment"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"802a126f80f89f0ae042b7c165968bff0a5364447a306543ec0fac081729b63d","checked_at":"2026-10-20T12:17:54.963Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Use all CPU cores with the cluster mode","description":"Start several worker processes behind one port with the cluster option of Total.run(), and know what is shared between them and what is not.","messages":[{"role":"user","content":"Use all CPU cores with the cluster mode\n\nStart several worker processes behind one port with the cluster option of Total.run(), and know what is shared between them and what is not."},{"role":"assistant","content":"A Node.js process uses one CPU core. `Total.run({ cluster: N })` starts a **master** that forks `N` worker processes, each running your whole application, and the operating system shares the incoming connections between them (`'auto'` instead of a number lets the master add threads as the load grows). Run it in release mode (`release: true`, which the generated start script sets with `--release`); the debug watcher is not meant for a cluster. In the start script it is one option:\n\n```js\nTotal.run({ cluster: 4, release: true }); // or cluster: 'auto'\n```\n\nEvery worker is a separate process with its own memory, so anything kept in a variable, in `CACHE` or in `MEMORIZE` exists once **per worker** and is not shared: two requests of the same user can land on different workers. Keep shared state in a database, in a file storage or in a message service, and keep sessions in a store all workers can read. Scheduled work such as `CRON()` also runs in every worker, so guard it if it must run once.\n\n**Check that all the workers are up.** After a deploy, request a URL like `/pid/` below repeatedly and count the distinct ids: you should see as many as the workers you asked for.\n\nEach worker has its own process id. The example starts two workers and includes that id in the response.\n\n```js controllers/pid.js\nexports.install = function() {\n\tROUTE('GET /pid/', pid);\n\tROUTE('GET /dir/', dir);\n};\n\nfunction pid($) {\n\t$.json({ pid: process.pid });\n}\n\nfunction dir($) {\n\t$.json({ dir: PATH.root() });\n}\n```\n\n```js cluster.js\nrequire('total5');\nTotal.run({ cluster: 2, port: 8766, release: true });\n```"}],"knowledge":{"content_sha256":"99d3248ff978a66dc94b11b8e9618643225d23dc06e9d6f4278b01bbb9f41e09","source_sha256":"802a126f80f89f0ae042b7c165968bff0a5364447a306543ec0fac081729b63d","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["CACHE","CPU","CRON","GET","MEMORIZE","PATH","ROUTE","Total.run","URL"],"language":"en"},"files":[{"path":"controllers/pid.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /pid/', pid);\n\tROUTE('GET /dir/', dir);\n};\n\nfunction pid($) {\n\t$.json({ pid: process.pid });\n}\n\nfunction dir($) {\n\t$.json({ dir: PATH.root() });\n}"},{"path":"cluster.js","language":"js","content":"require('total5');\nTotal.run({ cluster: 2, port: 8766, release: true });"}],"test":"const dir = (await get('/dir/')).json.dir;\nconst http = process.getBuiltinModule('http');\nconst spawn = process.getBuiltinModule('child_process').spawn;\n\n// A new connection for every request (no keep-alive), so that the system can balance them\nfunction pid() {\n\treturn new Promise(function(resolve, reject) {\n\t\thttp.get('http://127.0.0.1:8766/pid/', { agent: false }, function(res) {\n\t\t\tlet body = '';\n\t\t\tres.on('data', chunk => body += chunk);\n\t\t\tres.on('end', () => resolve(res.statusCode === 200 ? JSON.parse(body).pid : 0));\n\t\t}).on('error', reject);\n\t});\n}\n\n// Starts the cluster and returns the process ids that answered, or what it got before giving up\nasync function attempt() {\n\n\tconst child = spawn(process.execPath, ['cluster.js'], { cwd: dir, stdio: 'ignore', detached: true });\n\tconst pids = new Set();\n\n\ttry {\n\t\t// The master starts the workers one after another, so ask until the second one shows up\n\t\tfor (let i = 0; i < 80 && pids.size < 2; i++) {\n\t\t\ttry {\n\t\t\t\tconst id = await pid();\n\t\t\t\tid && pids.add(id);\n\t\t\t} catch (e) {}\n\t\t\tpids.size < 2 && await new Promise(resolve => setTimeout(resolve, 500));\n\t\t}\n\t\treturn { master: child.pid, pids: pids };\n\t} finally {\n\t\t// The master and its workers form one process group: end them all together\n\t\ttry {\n\t\t\tprocess.kill(-child.pid, 'SIGKILL');\n\t\t} catch (e) {}\n\t\tawait new Promise(resolve => setTimeout(resolve, 1000));\n\t}\n}\n\n// A worker sometimes fails to come up (see the text above), so a start is tried a few times\nlet result;\nfor (let i = 0; i < 6; i++) {\n\tresult = await attempt();\n\tif (result.pids.size >= 2)\n\t\tbreak;\n}\n\nassert(result.pids.size >= 2, 'requests are served by more than one worker process, got: ' + [...result.pids].join(', '));\nassert(!result.pids.has(result.master), 'the master does not serve requests, only the workers do');","test_kind":"http"}
{"id":"total5/import-remote-module","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/import-remote-module/","markdown_url":"https://forge.totaljs.es/en/total5/import-remote-module.md","tags":["modules","download","dependencies"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"43626cb3e02c938e8726bb827a265831fe4d431a4e4d254cce4434a356c8bb18","checked_at":"2026-10-20T12:18:14.789Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Load a JavaScript module from a URL with IMPORT()","description":"Download a file with JavaScript from an address, keep it in the tmp folder, require it and use what it exports; the second call does not download it again.","messages":[{"role":"user","content":"Load a JavaScript module from a URL with IMPORT()\n\nDownload a file with JavaScript from an address, keep it in the tmp folder, require it and use what it exports; the second call does not download it again."},{"role":"assistant","content":"`IMPORT(url, [callback])` downloads a JavaScript file, saves it in the `tmp` folder of the application and returns what the file exports (`module.exports`), as `require()` would. With a callback it is `callback(err, module, response)`; without it, `IMPORT()` returns a promise for the module. The address is remembered: calling `IMPORT()` again with the same address does **not** download the file again, it requires the one in `tmp` (and the file stays there between restarts, but the memory of \"already downloaded\" does not: after a restart it is downloaded again).\n\n**This runs code that comes from the network with all the rights of your application.** Import only from an address that you control and that you reach with `https`, never one that a user sent you or that anyone else can change. The file is a CommonJS module. **The status of the answer is not checked:** an address that answers `404` (or any error page) is saved as if it were the module, and `require()` then throws a `SyntaxError` inside the download, so in `total5` 0.0.19 the callback is **never called** and the promise never settles. Always give `IMPORT()` a limit of time of your own, as the last route does, and check the address before you depend on it.\n\nHere the file comes from a route of the same application, so that the example needs no network. The route counts the downloads.\n\n```js controllers/import.js\nexports.install = function() {\n\tROUTE('GET /lib/tax/', lib);\n\tROUTE('POST /use/', use);\n\tROUTE('POST /use-twice/', use_twice);\n\tROUTE('POST /missing/', missing);\n\tROUTE('GET /downloads/', downloads);\n};\n\nlet count = 0;\n\nfunction lib($) {\n\tcount++;\n\t$.plain('module.exports = { vat: function(price) { return Math.round(price * 121) / 100; } };');\n}\n\nfunction downloads($) {\n\t$.json({ downloads: count });\n}\n\nfunction base() {\n\treturn 'http://127.0.0.1:' + CONF.$port;\n}\n\nasync function use($) {\n\tconst tax = await IMPORT(base() + '/lib/tax/');\n\t$.json({ price: tax.vat(100) });\n}\n\nfunction use_twice($) {\n\tIMPORT(base() + '/lib/tax/', function(err, tax) {\n\t\t$.json({ err: err ? err.toString() : null, price: tax ? tax.vat(200) : null });\n\t});\n}\n\nasync function missing($) {\n\t// the address does not exist: the answer is the 404 page of the application\n\tconst winner = await Promise.race([\n\t\tIMPORT(base() + '/lib/missing/').then(() => 'answered', () => 'answered'),\n\t\tnew Promise(resolve => setTimeout(() => resolve('no answer'), 1500))\n\t]);\n\t$.json({ result: winner });\n}\n```"}],"knowledge":{"content_sha256":"d9ee8c3000dc6dc3cbcd81c5a4523bfb6863dca95d50aafed96c8f1755a91c0a","source_sha256":"43626cb3e02c938e8726bb827a265831fe4d431a4e4d254cce4434a356c8bb18","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["CONF","GET","IMPORT","POST","ROUTE"],"language":"en"},"files":[{"path":"controllers/import.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /lib/tax/', lib);\n\tROUTE('POST /use/', use);\n\tROUTE('POST /use-twice/', use_twice);\n\tROUTE('POST /missing/', missing);\n\tROUTE('GET /downloads/', downloads);\n};\n\nlet count = 0;\n\nfunction lib($) {\n\tcount++;\n\t$.plain('module.exports = { vat: function(price) { return Math.round(price * 121) / 100; } };');\n}\n\nfunction downloads($) {\n\t$.json({ downloads: count });\n}\n\nfunction base() {\n\treturn 'http://127.0.0.1:' + CONF.$port;\n}\n\nasync function use($) {\n\tconst tax = await IMPORT(base() + '/lib/tax/');\n\t$.json({ price: tax.vat(100) });\n}\n\nfunction use_twice($) {\n\tIMPORT(base() + '/lib/tax/', function(err, tax) {\n\t\t$.json({ err: err ? err.toString() : null, price: tax ? tax.vat(200) : null });\n\t});\n}\n\nasync function missing($) {\n\t// the address does not exist: the answer is the 404 page of the application\n\tconst winner = await Promise.race([\n\t\tIMPORT(base() + '/lib/missing/').then(() => 'answered', () => 'answered'),\n\t\tnew Promise(resolve => setTimeout(() => resolve('no answer'), 1500))\n\t]);\n\t$.json({ result: winner });\n}"}],"test":"const first = await post('/use/', {});\nassert(first.json && first.json.price === 121, 'the module that was downloaded is used, got: ' + first.body);\n\nconst second = await post('/use-twice/', {});\nassert(second.json && second.json.price === 242, 'a second import with a callback gives the same module, got: ' + second.body);\n\nconst count = await get('/downloads/');\nassert(count.json && count.json.downloads === 1, 'the file was downloaded once, got: ' + count.body);\n\nconst missing = await post('/missing/', {});\nassert(missing.json && missing.json.result === 'no answer', 'an address that answers 404 never settles, got: ' + missing.body);","test_kind":"http"}
{"id":"total5/flowstream","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/flowstream/","markdown_url":"https://forge.totaljs.es/en/total5/flowstream.md","tags":["flow","flowstream","components","automation"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"281df1149e0a94f9fe0ef81717fa659d1f2f251c01b48d5f4940da0ec19c0010","checked_at":"2026-10-20T12:18:10.295Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Build a flow of components with Flow.load() and run it with exec()","description":"Describe a FlowStream as components and the connections between them, load it in the application, send a message through it and read the answer, and change the configuration of a component while it runs.","messages":[{"role":"user","content":"Build a flow of components with Flow.load() and run it with exec()\n\nDescribe a FlowStream as components and the connections between them, load it in the application, send a message through it and read the answer, and change the configuration of a component while it runs."},{"role":"assistant","content":"A **FlowStream** is a small program made of **components** (boxes with inputs and outputs) and **connections** between them. Total.js runs it inside your application: a message enters one component, the component sends results to its outputs, and the connections carry them to the next components. The same schema is what the Flow designer of Total.js draws; here it is written by hand.\n\n`Flow.load(schema, [callback])` starts a FlowStream and gives the callback `(err, instance)`. The schema is an object:\n\n- `id` and `name`: the id is the key in `Flow.instances` (`Flow.instances.demo`); without an id a random one is made.\n- `components`: an object with the **source code** of each component, as text. The text is a script, `<script total>...</script>` (the form that the designer saves).\n- `design`: the instances of the components. Each one has an `id`, the `component` that it uses, its `config` and `connections`, an object whose keys are **output ids** and whose values are lists of `{ id, index }`: the instance and the **input id** that receive what is sent to that output.\n- `variables`: values that components can read.\n\nA component is a module: `exports.id`, `exports.name`, `exports.inputs` and `exports.outputs` (lists of `{ id, name }`), `exports.config` (the defaults) and `exports.make = function(instance, config)`. In `make()` the component sets `instance.message = function($) {}`, which receives each message: `$.data` is the data, `$.send(outputid, data)` sends it to an output and `$.destroy()` ends a message that does not go any further.\n\nTo use the flow from your code, `instance.exec({ id, data, callback })` sends `data` to the instance `id` and calls `callback(err, message)` when the message ends; the last message of the chain is `message` (so `message.data` is what the last component received). `instance.reconfigure(id, config)` changes the configuration of a running instance and `Flow.remove(id)` stops the FlowStream and forgets it. `FlowStream` is the same object as `Flow`: two names for the manager.\n\n**A component is code that runs with all the rights of the application** (in the same thread unless the schema says `worker`). Never load a schema or a component that a visitor can write.\n\n```js definitions/flow.js\nconst upper = `<script total>\nexports.id = 'upper';\nexports.name = 'Upper case';\nexports.inputs = [{ id: 'input', name: 'Input' }];\nexports.outputs = [{ id: 'output', name: 'Output' }];\nexports.config = { prefix: '' };\nexports.make = function(instance, config) {\n\tinstance.message = function($) {\n\t\t$.send('output', config.prefix + String($.data).toUpperCase());\n\t};\n};\n</script>`;\n\nconst collect = `<script total>\nexports.id = 'collect';\nexports.name = 'Collect';\nexports.inputs = [{ id: 'input', name: 'Input' }];\nexports.make = function(instance) {\n\tinstance.message = function($) {\n\t\tMAIN.collected.push($.data);\n\t\t$.destroy();\n\t};\n};\n</script>`;\n\nMAIN.collected = [];\n\nMAIN.flow = null;\n\nFlow.load({\n\tid: 'demo',\n\tname: 'Demo',\n\tcomponents: { upper: upper, collect: collect },\n\tdesign: {\n\t\ta: { id: 'a', component: 'upper', config: { prefix: '> ' }, connections: { output: [{ id: 'b', index: 'input' }] } },\n\t\tb: { id: 'b', component: 'collect', config: {}, connections: {} }\n\t},\n\tvariables: {}\n}, function(err, instance) {\n\tMAIN.flow = instance;\n});\n```\n\n```js controllers/flow.js\nexports.install = function() {\n\tROUTE('GET /shout/', shout);\n\tROUTE('POST /prefix/ @json', prefix);\n\tROUTE('GET /collected/', collected);\n\tROUTE('GET /info/', info);\n\tROUTE('POST /stop/', stop);\n};\n\nfunction shout($) {\n\tif (!MAIN.flow)\n\t\treturn $.invalid('The flow is not running');\n\tMAIN.flow.exec({ id: 'a', data: $.query.text, callback: (err, message) => $.json({ result: message ? message.data : null }) });\n}\n\nfunction prefix($) {\n\tMAIN.flow.reconfigure('a', { prefix: $.body.prefix });\n\t$.json({ ok: true });\n}\n\nfunction collected($) {\n\t$.json(MAIN.collected);\n}\n\nfunction info($) {\n\t$.json({ flows: Object.keys(Flow.instances), instances: Object.keys(MAIN.flow.flow.meta.flow), same: Flow === FlowStream });\n}\n\nfunction stop($) {\n\tFlow.remove('demo');\n\tMAIN.flow = null;\n\t$.json({ flows: Object.keys(Flow.instances) });\n}\n```"}],"knowledge":{"content_sha256":"8bb7bd513b4f12b9d58eafbd976efdc76a26cc9b10ead6bb184f67b0e7af38c6","source_sha256":"281df1149e0a94f9fe0ef81717fa659d1f2f251c01b48d5f4940da0ec19c0010","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["GET","MAIN","POST","ROUTE","Total.js"],"language":"en"},"files":[{"path":"definitions/flow.js","language":"js","content":"const upper = `<script total>\nexports.id = 'upper';\nexports.name = 'Upper case';\nexports.inputs = [{ id: 'input', name: 'Input' }];\nexports.outputs = [{ id: 'output', name: 'Output' }];\nexports.config = { prefix: '' };\nexports.make = function(instance, config) {\n\tinstance.message = function($) {\n\t\t$.send('output', config.prefix + String($.data).toUpperCase());\n\t};\n};\n</script>`;\n\nconst collect = `<script total>\nexports.id = 'collect';\nexports.name = 'Collect';\nexports.inputs = [{ id: 'input', name: 'Input' }];\nexports.make = function(instance) {\n\tinstance.message = function($) {\n\t\tMAIN.collected.push($.data);\n\t\t$.destroy();\n\t};\n};\n</script>`;\n\nMAIN.collected = [];\n\nMAIN.flow = null;\n\nFlow.load({\n\tid: 'demo',\n\tname: 'Demo',\n\tcomponents: { upper: upper, collect: collect },\n\tdesign: {\n\t\ta: { id: 'a', component: 'upper', config: { prefix: '> ' }, connections: { output: [{ id: 'b', index: 'input' }] } },\n\t\tb: { id: 'b', component: 'collect', config: {}, connections: {} }\n\t},\n\tvariables: {}\n}, function(err, instance) {\n\tMAIN.flow = instance;\n});"},{"path":"controllers/flow.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /shout/', shout);\n\tROUTE('POST /prefix/ @json', prefix);\n\tROUTE('GET /collected/', collected);\n\tROUTE('GET /info/', info);\n\tROUTE('POST /stop/', stop);\n};\n\nfunction shout($) {\n\tif (!MAIN.flow)\n\t\treturn $.invalid('The flow is not running');\n\tMAIN.flow.exec({ id: 'a', data: $.query.text, callback: (err, message) => $.json({ result: message ? message.data : null }) });\n}\n\nfunction prefix($) {\n\tMAIN.flow.reconfigure('a', { prefix: $.body.prefix });\n\t$.json({ ok: true });\n}\n\nfunction collected($) {\n\t$.json(MAIN.collected);\n}\n\nfunction info($) {\n\t$.json({ flows: Object.keys(Flow.instances), instances: Object.keys(MAIN.flow.flow.meta.flow), same: Flow === FlowStream });\n}\n\nfunction stop($) {\n\tFlow.remove('demo');\n\tMAIN.flow = null;\n\t$.json({ flows: Object.keys(Flow.instances) });\n}"}],"test":"// the flow is loaded when the application is ready: wait for it\nlet info = null;\nfor (let i = 0; i < 20; i++) {\n\tconst res = await get('/info/');\n\tif (res.json && res.json.flows) {\n\t\tinfo = res.json;\n\t\tbreak;\n\t}\n\tawait new Promise(resolve => setTimeout(resolve, 200));\n}\n\nassert(info && info.flows.join() === 'demo' && info.instances.join() === 'a,b', 'the flow demo has the instances a and b, got: ' + JSON.stringify(info));\nassert(info.same === true, 'Flow and FlowStream are the same object');\n\nconst first = await get('/shout/?text=hello');\nassert(first.json && first.json.result === '> HELLO', 'the message went through a and arrived to b, got: ' + first.body);\n\nconst list = await get('/collected/');\nassert(list.json && list.json[0] === '> HELLO', 'the last component collected it, got: ' + list.body);\n\nawait post('/prefix/', { prefix: '# ' });\nawait new Promise(resolve => setTimeout(resolve, 200));\nconst second = await get('/shout/?text=again');\nassert(second.json && second.json.result === '# AGAIN', 'reconfigure changes the running component, got: ' + second.body);\n\nconst stopped = await post('/stop/', {});\nassert(stopped.json && stopped.json.flows.length === 0, 'Flow.remove() forgets the flow, got: ' + stopped.body);","test_kind":"http"}
{"id":"total5/flowstream-component-test","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/flowstream-component-test/","markdown_url":"https://forge.totaljs.es/en/total5/flowstream-component-test.md","tags":["flow","flowstream","components","tests"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"fcd3712769860e95275565174e6baa47004600e25500870dab132f368e324c5c","checked_at":"2026-10-20T12:18:09.362Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Test a Flow component alone with Flow.test()","description":"Load one component in a flow of its own, send it a message from the code and read what it sends to its outputs, without building the whole flow.","messages":[{"role":"user","content":"Test a Flow component alone with Flow.test()\n\nLoad one component in a flow of its own, send it a message from the code and read what it sends to its outputs, without building the whole flow."},{"role":"assistant","content":"`Flow.test(name, source, [config], [init])` builds a flow with **one** component (the `source` is its code, like in the recipe of FlowStream, and `config` replaces its configuration) and a dummy component that collects its outputs. `init(test)` is called when the flow is ready, with the object `test`:\n\n- `test.send(inputid, data)` (also `test.input`) sends a message to an input of the component and returns it.\n- `test.message = function($, data)`: called with every message that the component sends to **any of its outputs**; `$` is the message and `data` its data.\n- `test.reconfigure(config)` changes the configuration; `test.configure = function(config)` is called after it. `test.variables(object)` sets the variables of the flow.\n- `test.assert(ok, message)`, `test.ok(message)` and `test.fail(message)` print `[OK]` or `[NO]` with the date, and a failure throws. The functions are plain properties: replace `test.ok` and `test.fail` to send the result somewhere else, as the example does.\n- The test flow always has the id `test`: only one `Flow.test()` at a time.\n\nIt is meant for the tests of a component that is written for a Flow, run with `node index.js` in a small application of its own.\n\n```js definitions/component-test.js\nMAIN.results = [];\n\nconst source = `<script total>\nexports.id = 'tax';\nexports.name = 'Tax';\nexports.inputs = [{ id: 'input', name: 'Input' }];\nexports.outputs = [{ id: 'output', name: 'Output' }];\nexports.config = { rate: 21 };\nexports.make = function(instance, config) {\n\tinstance.message = function($) {\n\t\t$.send('output', { net: $.data, total: Math.round($.data * (100 + config.rate)) / 100 });\n\t};\n};\n</script>`;\n\nON('ready', function() {\n\tFlow.test('tax', source, { rate: 10 }, function(test) {\n\n\t\ttest.ok = message => MAIN.results.push('ok: ' + message);\n\t\ttest.fail = message => MAIN.results.push('no: ' + message);\n\n\t\ttest.message = function($, data) {\n\t\t\ttest.assert(data.net === 100, 'the net amount comes back');\n\t\t\ttest.assert(data.total === 110, 'the configuration of the test (10 %) is used, got ' + data.total);\n\t\t\tMAIN.results.push('message: ' + JSON.stringify(data));\n\t\t};\n\n\t\ttest.send('input', 100);\n\t});\n});\n```\n\n```js controllers/results.js\nexports.install = function() {\n\tROUTE('GET /results/', results);\n};\n\nfunction results($) {\n\t$.json(MAIN.results);\n}\n```"}],"knowledge":{"content_sha256":"c9944def331db1296e725f6200d7bcf3bf60c1265eed77a225d7c71d3864d64e","source_sha256":"fcd3712769860e95275565174e6baa47004600e25500870dab132f368e324c5c","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["GET","JSON","MAIN","ROUTE"],"language":"en"},"files":[{"path":"definitions/component-test.js","language":"js","content":"MAIN.results = [];\n\nconst source = `<script total>\nexports.id = 'tax';\nexports.name = 'Tax';\nexports.inputs = [{ id: 'input', name: 'Input' }];\nexports.outputs = [{ id: 'output', name: 'Output' }];\nexports.config = { rate: 21 };\nexports.make = function(instance, config) {\n\tinstance.message = function($) {\n\t\t$.send('output', { net: $.data, total: Math.round($.data * (100 + config.rate)) / 100 });\n\t};\n};\n</script>`;\n\nON('ready', function() {\n\tFlow.test('tax', source, { rate: 10 }, function(test) {\n\n\t\ttest.ok = message => MAIN.results.push('ok: ' + message);\n\t\ttest.fail = message => MAIN.results.push('no: ' + message);\n\n\t\ttest.message = function($, data) {\n\t\t\ttest.assert(data.net === 100, 'the net amount comes back');\n\t\t\ttest.assert(data.total === 110, 'the configuration of the test (10 %) is used, got ' + data.total);\n\t\t\tMAIN.results.push('message: ' + JSON.stringify(data));\n\t\t};\n\n\t\ttest.send('input', 100);\n\t});\n});"},{"path":"controllers/results.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /results/', results);\n};\n\nfunction results($) {\n\t$.json(MAIN.results);\n}"}],"test":"let list = [];\nfor (let i = 0; i < 20 && list.length < 3; i++) {\n\tawait new Promise(resolve => setTimeout(resolve, 200));\n\tlist = (await get('/results/')).json || [];\n}\n\nassert(list.length === 3, 'two assertions and the message were recorded, got: ' + JSON.stringify(list));\nassert(list.includes('ok: the net amount comes back'), 'the first assertion passed, got: ' + JSON.stringify(list));\nassert(list.includes('ok: the configuration of the test (10 %) is used, got 110'), 'the second assertion passed, got: ' + JSON.stringify(list));\nassert(list.some(n => n.startsWith('message: ') && n.includes('\"total\":110')), 'the output reached test.message, got: ' + JSON.stringify(list));","test_kind":"http"}
{"id":"total5/pypeline","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/pypeline/","markdown_url":"https://forge.totaljs.es/en/total5/pypeline.md","tags":["python","pypeline","integrations","ai"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"4cc84046449fce3fdd024829bb5ccbcb132fcca8e6d7346ef0c225a37dc71a88","checked_at":"2026-10-20T12:18:35.037Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Call a Python program that keeps running with PYPELINE()","description":"Start a Python script from the application, send it JSON messages over a socket and receive its answers, with the script restarted if it dies.","messages":[{"role":"user","content":"Call a Python program that keeps running with PYPELINE()\n\nStart a Python script from the application, send it JSON messages over a socket and receive its answers, with the script restarted if it dies."},{"role":"assistant","content":"`PYPELINE(name, [options])` starts `pypelines/<name>.py` with `python3` and keeps a connection to it, so the application can use Python libraries (data, models, image processing) without starting a new process for every request. It returns an object with `send(data)` to send a message, `on('data', fn)` to receive the answers and `close()` to stop the script. A name that starts with `~` is the path of a file instead (`~/opt/job.py`), and a text that is code (it has line breaks or looks like Python) is run as an inline program with `python3 -c`.\n\n- **The protocol.** The script is started as `python3 -u script.py <socket>`: the **last argument is the path of a Unix socket** that the application listens on, and the script connects to it. Every message, in both directions, is a frame: 4 bytes with the length (big endian) followed by the body, which is JSON by default.\n- **Options:** `type` (`'json'` default, `'text'`, or anything else for raw `Buffer`s), `autorestart` (`false` to not restart the script when it ends; by default it is started again), `threads` and `socket: false` (no socket, only start the program).\n- Messages sent before the script has connected wait and are delivered when it does.\n- Events: `open` (the script connected), `data` (a message), `restart` and `close`.\n- **The path of the socket has a limit.** The socket is a file in the `tmp` folder of the application and the system limits the whole path (about 104 characters on macOS and 108 on Linux). In a project that is deep in the disk the server fails with `listen EINVAL`, the script never connects and the messages wait forever. The path is the property `pypeline.socket`: set it right after `PYPELINE()` (the file is checked and the server started afterwards) to a short one, as the example does.\n- The Python program is a **normal program that runs with the rights of the application**: it is only as safe as the code of the file, and Python 3 has to be installed (`python3` in the `PATH`).\n\n```python pypelines/shout.py\nimport json\nimport socket\nimport struct\nimport sys\n\n# The last argument is the socket that the application listens on\nsock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)\nsock.connect(sys.argv[-1])\n\ndef read(count):\n    data = b''\n    while len(data) < count:\n        chunk = sock.recv(count - len(data))\n        if not chunk:\n            raise SystemExit(0)\n        data += chunk\n    return data\n\nwhile True:\n    size = struct.unpack('>I', read(4))[0]\n    message = json.loads(read(size).decode('utf-8'))\n    answer = json.dumps({ 'id': message['id'], 'upper': message['text'].upper(), 'words': len(message['text'].split()) }).encode('utf-8')\n    sock.sendall(struct.pack('>I', len(answer)) + answer)\n```\n\n```js controllers/py.js\nexports.install = function() {\n\tROUTE('POST /shout/ @json', shout);\n};\n\nconst pypeline = PYPELINE('shout');\n\n// A short path for the socket: the folder of a project can be too deep for a Unix socket\npypeline.socket = require('os').tmpdir() + '/forge-shout-' + process.pid + '.socket';\nconst pending = {};\nlet counter = 0;\n\n// Every answer says which message it is for\npypeline.on('data', function(answer) {\n\tconst callback = pending[answer.id];\n\tif (callback) {\n\t\tdelete pending[answer.id];\n\t\tcallback(answer);\n\t}\n});\n\nfunction shout($) {\n\tconst id = ++counter;\n\tconst timer = setTimeout(() => $.invalid('The script did not answer'), 5000);\n\tpending[id] = function(answer) {\n\t\tclearTimeout(timer);\n\t\t$.json(answer);\n\t};\n\tpypeline.send({ id: id, text: $.body.text });\n}\n```"}],"knowledge":{"content_sha256":"c346fc22c80a08961b4ed4e5da21cc650cb0f3fac63fddec6f03504e6f5bcaf7","source_sha256":"4cc84046449fce3fdd024829bb5ccbcb132fcca8e6d7346ef0c225a37dc71a88","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["AF_UNIX","EINVAL","JSON","PATH","POST","PYPELINE","ROUTE","SOCK_STREAM"],"language":"en"},"files":[{"path":"pypelines/shout.py","language":"python","content":"import json\nimport socket\nimport struct\nimport sys\n\n# The last argument is the socket that the application listens on\nsock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)\nsock.connect(sys.argv[-1])\n\ndef read(count):\n    data = b''\n    while len(data) < count:\n        chunk = sock.recv(count - len(data))\n        if not chunk:\n            raise SystemExit(0)\n        data += chunk\n    return data\n\nwhile True:\n    size = struct.unpack('>I', read(4))[0]\n    message = json.loads(read(size).decode('utf-8'))\n    answer = json.dumps({ 'id': message['id'], 'upper': message['text'].upper(), 'words': len(message['text'].split()) }).encode('utf-8')\n    sock.sendall(struct.pack('>I', len(answer)) + answer)"},{"path":"controllers/py.js","language":"js","content":"exports.install = function() {\n\tROUTE('POST /shout/ @json', shout);\n};\n\nconst pypeline = PYPELINE('shout');\n\n// A short path for the socket: the folder of a project can be too deep for a Unix socket\npypeline.socket = require('os').tmpdir() + '/forge-shout-' + process.pid + '.socket';\nconst pending = {};\nlet counter = 0;\n\n// Every answer says which message it is for\npypeline.on('data', function(answer) {\n\tconst callback = pending[answer.id];\n\tif (callback) {\n\t\tdelete pending[answer.id];\n\t\tcallback(answer);\n\t}\n});\n\nfunction shout($) {\n\tconst id = ++counter;\n\tconst timer = setTimeout(() => $.invalid('The script did not answer'), 5000);\n\tpending[id] = function(answer) {\n\t\tclearTimeout(timer);\n\t\t$.json(answer);\n\t};\n\tpypeline.send({ id: id, text: $.body.text });\n}"}],"test":"const first = await post('/shout/', { text: 'hello from node' });\nassert(first.json && first.json.upper === 'HELLO FROM NODE' && first.json.words === 3, 'the Python script answered, got: ' + first.body);\n\n// the same script, many messages at the same time: each answer goes to its own request\nconst many = await Promise.all(['a', 'b c', 'd e f'].map(text => post('/shout/', { text: text })));\nassert(many.map(n => n.json.upper).join('|') === 'A|B C|D E F', 'each request gets its own answer, got: ' + many.map(n => n.body).join(' '));\nassert(many.map(n => n.json.words).join() === '1,2,3', 'the words were counted by the script');","test_kind":"http"}
{"id":"total5/schema-enums-and-formats","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/schema-enums-and-formats/","markdown_url":"https://forge.totaljs.es/en/total5/schema-enums-and-formats.md","tags":["total5","reference"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"4056ed0946344e7dae0301d2ba6bd306a361038c97bfa7e7e0e62aad16d329b1","checked_at":"2026-10-20T12:18:46.760Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Enums and string-format validation boundaries","description":"Run positive and negative compact-schema checks for enums, Color, Base64, Zip, Icon, JSON and IP formats.","messages":[{"role":"user","content":"Enums and string-format validation boundaries\n\nRun positive and negative compact-schema checks for enums, Color, Base64, Zip, Icon, JSON and IP formats."},{"role":"assistant","content":"Compact enums use `status:{draft|published}` (semicolon also separates choices). The string is trimmed before exact, case-sensitive membership checking. A required enum rejects an omitted/empty value. Nested objects use `{name:Type}`; they are not enum declarations.\n\nIn `total5@0.0.19`, `Color` accepts hexadecimal `#RGB`, `#RRGGBB` and `#RRGGBBAA`; it does not accept CSS color names. `Icon` validates a class-name pattern, not the presence of the icon asset. `Base64` and `Zip` use the framework's validators: format acceptance is not content verification. In the compact-schema parser of this version, **`JSON` and `IP` are not recognized subtypes and fall back to plain strings**. Malformed JSON and invalid IP text are accepted. The explicit JSON Schema below sets native `subtype` values to enable these checks; it does not parse a JSON string into an object. Do not use `SafeString` in place of bound SQL parameters or output escaping.\n\n```js controllers/formats.js\nvar schema = NEWSCHEMA('FormatBoundaries', '*status:{draft|published},*color:Color,*blob:Base64,*zip:Zip,*icon:Icon,*document:JSON,*address:IP');\nvar explicit = Total.newjsonschema('ExplicitFormats', { $id: 'ExplicitFormats', type: 'object', required: ['document','address'], properties: { document: { type: 'string', subtype: 'json' }, address: { type: 'string', subtype: 'ip' } } });\nexports.install = function() {\n\tROUTE('POST /explicit/ @json', function($) {\n\t\tvar result = explicit.transform($.body);\n\t\tif (result.error) { $.response.status = 400; $.json({ error: 'invalid format' }); }\n\t\telse $.json(result.response);\n\t});\n\tROUTE('POST /formats/ @json', formats);\n};\nfunction formats($) {\n\tvar result = schema.transform($.body);\n\tif (result.error) {\n\t\t$.response.status = 400;\n\t\t$.json({ error: 'invalid format' });\n\t} else\n\t\t$.json(result.response);\n}\n```"}],"knowledge":{"content_sha256":"7170c1adf5f76b4f2a6754b93b35b731172e178c0fabc248d6e3043eadc8a83c","source_sha256":"4056ed0946344e7dae0301d2ba6bd306a361038c97bfa7e7e0e62aad16d329b1","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["CSS","JSON","NEWSCHEMA","POST","RGB","ROUTE","RRGGBB","RRGGBBAA","SQL","Total.newjsonschema"],"language":"en"},"files":[{"path":"controllers/formats.js","language":"js","content":"var schema = NEWSCHEMA('FormatBoundaries', '*status:{draft|published},*color:Color,*blob:Base64,*zip:Zip,*icon:Icon,*document:JSON,*address:IP');\nvar explicit = Total.newjsonschema('ExplicitFormats', { $id: 'ExplicitFormats', type: 'object', required: ['document','address'], properties: { document: { type: 'string', subtype: 'json' }, address: { type: 'string', subtype: 'ip' } } });\nexports.install = function() {\n\tROUTE('POST /explicit/ @json', function($) {\n\t\tvar result = explicit.transform($.body);\n\t\tif (result.error) { $.response.status = 400; $.json({ error: 'invalid format' }); }\n\t\telse $.json(result.response);\n\t});\n\tROUTE('POST /formats/ @json', formats);\n};\nfunction formats($) {\n\tvar result = schema.transform($.body);\n\tif (result.error) {\n\t\t$.response.status = 400;\n\t\t$.json({ error: 'invalid format' });\n\t} else\n\t\t$.json(result.response);\n}"}],"test":"const base = { status: 'draft', color: '#aBc', blob: 'SGVsbG8=', zip: '81101', icon: 'ti ti-home', document: '{\"ok\":true}', address: '127.0.0.1' };\nconst ok = await post('/formats/', { ...base, status: ' published ' });\nassert(ok.status === 200 && ok.json.status === 'published' && typeof ok.json.document === 'string', 'trimmed enum and JSON string: ' + ok.body);\nfor (const color of ['#ABC', '#aabbcc', '#AABBCCDD'])\n\tassert((await post('/formats/', { ...base, color })).status === 200, 'valid color ' + color);\nassert((await post('/formats/', { ...base, address: '::1' })).status === 200, 'IPv6');\nfor (const [field, value] of [['status','Draft'],['status','deleted'],['status',''],['color','red'],['color','#12'],['blob','***'],['zip','!!!'],['icon','home']])\n\tassert((await post('/formats/', { ...base, [field]: value })).status === 400, 'reject ' + field + '=' + value);\nconst unchecked = await post('/formats/', { ...base, document: '{broken', address: '999.1.1.1' });\nassert(unchecked.status === 200 && unchecked.json.document === '{broken', 'compact JSON/IP labels fall back to strings');\nassert((await post('/explicit/', base)).status === 200, 'explicit native string subtypes accept valid JSON/IP');\nfor (const patch of [{ document: '{broken' }, { address: '999.1.1.1' }])\n\tassert((await post('/explicit/', { ...base, ...patch })).status === 400, 'explicit subtype rejects malformed input');\nconst missing = { ...base };\ndelete missing.status;\nassert((await post('/formats/', missing)).status === 400, 'missing required enum');","test_kind":"http"}
{"id":"total5/remote-service-boundaries","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/remote-service-boundaries/","markdown_url":"https://forge.totaljs.es/en/total5/remote-service-boundaries.md","tags":["total5","reference"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"74f58669c008eb239fb97fc6182de38c120e642631723ea0160ea370617b654f","checked_at":"2026-10-20T12:18:40.224Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"TotalAPI and Componentator service boundaries","description":"Verify the disabled TotalAPI branch and Componentator local-cache reuse without making external service calls.","messages":[{"role":"user","content":"TotalAPI and Componentator service boundaries\n\nVerify the disabled TotalAPI branch and Componentator local-cache reuse without making external service calls."},{"role":"assistant","content":"`TotalAPI(operation, data)` delegates to `API('TAPI', operation, data)`. The built-in driver sends requests to the regional endpoint selected by `CONF.$tapiurl`, using a configured token such as `CONF.secret_totalapi`. `.promise()` and `.callback()` use the same API builder as other drivers. With `$tapi:false`, operations other than the driver's special `check` operation fail locally with `totalapi_inactive`.\n\n`COMPONENTATOR(name, components, removeprev=true, attrs='')` determines a public bundle filename, stores its URL in `REPO[name]` and fetches it from Componentator only when the file is absent. With `removeprev:true`, older matching files may be deleted. The `componentator` event receives the name/component metadata before the lookup. This function returns before its async filesystem/download work has finished; the repo URL alone is not proof of download success.\n\nFor locally vendored frontend assets, serving known files avoids dependence on remote bundle availability. For live TotalAPI/Componentator integration, separately verify your exact service, credentials, network behavior and returned content. These services are optional and are not dependencies needed to start a native Total.js application.\n\n```text config\n$tapi : false\n```\n\n```js controllers/remote.js\nexports.install = function() {\n\tROUTE('GET /disabled-api/', async function($) {\n\t\ttry { await TotalAPI('forge-fixture-no-service', {}).promise(); $.json({ unexpected: true }); }\n\t\tcatch (e) { $.json({ error: String(e) }); }\n\t});\n\tROUTE('GET /cached-componentator/', function($) {\n\t\tvar components = 'forge-local-fixture';\n\t\tvar relative = 'ui-' + components.makeid() + '.min.js';\n\t\tF.Fs.writeFileSync(PATH.public(relative), '/* local fixture */');\n\t\tCOMPONENTATOR('fixture', components, false);\n\t\t$.json({ url: REPO.fixture, expected: '/ui-' + components.makeid() + '.min.js' });\n\t});\n};\n```"}],"knowledge":{"content_sha256":"5524e132d24137a2f048bb67797b5f5cbee93beb1b855059c50d44c081d0e2d4","source_sha256":"74f58669c008eb239fb97fc6182de38c120e642631723ea0160ea370617b654f","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["API","COMPONENTATOR","CONF","GET","PATH","REPO","ROUTE","TAPI","Total.js","URL"],"language":"en"},"files":[{"path":"config","language":"text","content":"$tapi : false"},{"path":"controllers/remote.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /disabled-api/', async function($) {\n\t\ttry { await TotalAPI('forge-fixture-no-service', {}).promise(); $.json({ unexpected: true }); }\n\t\tcatch (e) { $.json({ error: String(e) }); }\n\t});\n\tROUTE('GET /cached-componentator/', function($) {\n\t\tvar components = 'forge-local-fixture';\n\t\tvar relative = 'ui-' + components.makeid() + '.min.js';\n\t\tF.Fs.writeFileSync(PATH.public(relative), '/* local fixture */');\n\t\tCOMPONENTATOR('fixture', components, false);\n\t\t$.json({ url: REPO.fixture, expected: '/ui-' + components.makeid() + '.min.js' });\n\t});\n};"}],"test":"const disabled = await get('/disabled-api/');\nassert(disabled.json.error.includes('totalapi_inactive'), 'disabled TotalAPI rejects before remote request');\nconst cached = await get('/cached-componentator/');\nassert(cached.json.url === cached.json.expected, 'Componentator stores deterministic public URL');","test_kind":"http"}
{"id":"total5/query-builder-contract","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/query-builder-contract/","markdown_url":"https://forge.totaljs.es/en/total5/query-builder-contract.md","tags":["total5","reference"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"43f8339531846f95ce280dfd43fde0317fe7b7ebfed78dbc55a6b1380eb0e0a8","checked_at":"2026-10-20T12:18:35.761Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"QueryBuilder operations, filters and driver contract","description":"Inspect native DB and NEWDB options, queued results, upsert, errors and filter shapes without confusing a builder with a database driver.","messages":[{"role":"user","content":"QueryBuilder operations, filters and driver contract\n\nInspect native DB and NEWDB options, queued results, upsert, errors and filter shapes without confusing a builder with a database driver."},{"role":"assistant","content":"This page covers the native `total5@0.0.19` QueryBuilder (`querybuilder.js`). `DB()` returns a queue controller. Each operation schedules a driver call; append filters and callbacks synchronously before it executes. `NEWDB(name, driver)` registers a connection. `NEWDB(name)` unregisters it. There is no implicit database engine or transaction in this queue.\n\nThe example driver returns the received options. To execute a database operation, implement that operation in the driver. For actual SQL execution and rollback see [query-builder-sqlite](/en/total5/query-builder-sqlite/).\n\n| Operations | Driver receives |\n|---|---|\n| `find/all(table)`, `read/one(table)`, `list(table)` | `exec`, `table`; `read` sets `first:true`, `take:1`; `list` defaults to `take:100`, `skip:0` |\n| `check(table)`, `count(table)` | `check` sets `first:1`, `take:1`; the driver defines the result value |\n| `insert/ins(table, data)` | `payload` |\n| `update/modify/mod/upd(table, data, upsert)` | `payload`, `upsert`; a zero result triggers a second `insert` when upsert is enabled |\n| `remove/rem(table)`, `truncate/clear(table)`, `drop(table)` | The corresponding `exec`; destructive operations need driver support |\n| `scalar(table, type, key, key2)` | `scalar:{type,key,key2}`; omitted `key` becomes `*` |\n| `command(table, name)` | `command:name` |\n| `query(sql, params)` or `query(connection, sql, params)` | Raw text in `query`, values in `params`, table is empty; SQL binding is the driver's responsibility |\n\n| Query methods | Options / behavior |\n|---|---|\n| `where(name, [comparer], value)`, `equal(object)`, `id(value)`, `userid(value)` | Equality shorthand; `==` becomes `=`, `!=` becomes `<>`; array IDs use `in` |\n| `in/notin(name, values, [idField])`, `between(name,a,b)`, `or(fn)` | Filter records; `or` groups the records added inside `fn(builder)` |\n| `array(name,[comparer],value)` | Default comparer `&&`; interpretation belongs to driver |\n| `month/day/year/hour/minute(name,[comparer],value)` | Date-part filters; default comparer `=` |\n| `search(name,value,[where])`, `contains(name)`, `empty(name)` | Filter records; matching semantics belong to driver |\n| `query(fragment)`, `join(name,table,type,a,b)`, `permit(name,type,values,userid,required)` | Driver-specific filters; raw fragments must come from trusted application code |\n| `fields(csv)`, `returning(csv)` | Arrays of trimmed field names, not SQL escaping or authorization |\n| `sort(name, descending)`, `gridsort(csv,[localized])` | Sort strings; `true` or `desc` select descending |\n| `take/limit(n)`, `skip(n)`, `page(page,limit)`, `paginate(page,limit,max)` | Offsets/limits; `paginate` caps the limit, `page` does not |\n| `first()`, `primarykey(name)`, `schema(name)`, `language(value,prefix,skip)`, `debug()` | Metadata for driver; `first` also unwraps an array result |\n| `gridfilter(name,query,type,[key])`, `autoquery(query,schema,sort,max)` | Convert grid query parameters into options. `autoquery` restricts requested fields and sort keys to its declared schema |\n| `gridfields(fields,allowed)` | Use `fields()` with your own allowlist: it is the tested way to choose the columns |\n| `callback/pipe(fn)`, `promise()`, `done($,fn,param)` | Completion; promises reject on driver error |\n| `data(fn)`, `fail(fn)`, `error/err(message,reverse)` | Success/failure hooks; `error()` rejects empty results, `reverse:true` rejects truthy results (including an empty array) |\n| `set(name)`, `nobind()` | Name the queued result, or do not replace queue output |\n| `insert(fn,param)` on an update builder | Mutate the payload before the fallback insert of an upsert |\n| `audit($,message,type)` | Calls the supplied context's `variables()` and `audit()` after a successful driver response |\n\nController methods: `callback/pipe`, `promise`, `done`, `debug`, `language`, `load(connection, options)` and `exec(options, callback)`. `load` copies a defined subset of serialized options; `exec` dispatches supported operation names. `next/free` are lifecycle internals. A queue stops after a driver error; it does not undo earlier writes.\n\n```js definitions/contract.js\nNEWDB('contract', function(options, callback) {\n\tif (options.table === 'failure')\n\t\treturn callback(new Error('driver failed'));\n\tif (options.table === 'empty')\n\t\treturn callback(null, []);\n\tif (options.table === 'upsert') {\n\t\tMAIN.upsert.push(JSON.parse(JSON.stringify(options)));\n\t\treturn callback(null, options.exec === 'update' ? 0 : options.payload);\n\t}\n\tif (options.exec === 'list')\n\t\treturn callback(null, { items: [], count: 12 });\n\tcallback(null, options.first ? [options] : options);\n});\nMAIN.upsert = [];\n```\n\n```js controllers/contract.js\nexports.install = function() {\n\tROUTE('GET /contract/', contract);\n};\n\nasync function contract($) {\n\ttry {\n\t\tvar out = {};\n\t\tvar methods = ['find', 'all', 'read', 'one', 'list', 'check', 'count', 'insert', 'ins', 'update', 'modify', 'mod', 'upd', 'remove', 'rem', 'truncate', 'clear', 'drop'];\n\t\tout.operations = {};\n\t\tfor (var method of methods)\n\t\t\tout.operations[method] = await DB()[method]('contract/items', { name: 'Ann' }).promise();\n\t\tout.scalar = await DB().scalar('contract/items', 'sum', 'amount', 'group').promise();\n\t\tout.command = await DB().command('contract/items', 'vacuum').promise();\n\t\tout.raw = await DB().query('contract', 'SELECT ?', [7]).promise();\n\t\tout.filters = await DB().find('contract/items').id([1, 2]).userid({ id: 'u1' }).equal({ active: true }).where('age', '!=', 0).in('team', [{ id: 'a' }], 'id').notin('team', 'b').between('age', 18, 65).or(function(q) { q.where('role', 'a').where('role', 'b'); }).array('tags', ['x']).month('created', 1).day('created', 2).year('created', '>=', 2020).hour('created', 3).minute('created', 4).search('name', 'ann', 'beg').contains('email').empty('deleted').query('trusted_fragment').join('team', 'teams', 'left', 'a.id', 'b.id').permit('permissions', 'RW', ['team'], 'u1', false).promise();\n\t\tout.options = await DB().find('contract/items').fields('id, name').returning('id, name').sort('name', true).gridsort('id_asc').limit(5).skip(2).primarykey('uid').schema('public').language({ language: 'es' }, '_').debug().promise();\n\t\tout.page = await DB().list('contract/items').paginate('2', '500', 10).promise();\n\t\tout.grid = await DB().find('contract/items').autoquery({ fields: 'id,password', sort: 'password_desc,name_desc', page: '2', limit: '500', age: '>18' }, 'id:String,name:String,age:Number', 'id_asc', 10).promise();\n\t\tout.gridfields = await DB().find('contract/items').gridfields('id', 'id,name').promise();\n\t\tvar queue = DB();\n\t\tqueue.read('contract/items').set('one');\n\t\tqueue.count('contract/items').set('count');\n\t\tqueue.find('contract/items').nobind();\n\t\tout.queue = await queue.promise();\n\t\tvar hooks = [];\n\t\tawait DB().read('contract/empty').data(function(v) { hooks.push(v === null ? 'null' : 'value'); }).promise();\n\t\ttry { await DB().read('contract/empty').error('missing').fail(function() { hooks.push('fail'); }).promise(); } catch (e) { out.missing = String(e); }\n\t\ttry { await DB().find('contract/failure').promise(); } catch (e) { out.failure = String(e); }\n\t\tout.hooks = hooks;\n\t\tout.upsert = await DB().update('contract/upsert', { name: 'new' }, true).where('id', 7).insert(function(payload, role) { payload.role = role; }, 'member').promise();\n\t\tout.upsertCalls = MAIN.upsert;\n\t\tout.load = await DB().load('contract', { table: 'items', exec: 'find', take: 2, filter: [] }).promise();\n\t\t$.json(out);\n\t} catch (e) { $.invalid(e); }\n}\n```"}],"knowledge":{"content_sha256":"f155d117230e8d40e4895b685b1dec15b996d5a867b5d917fe2972d66bac8e8e","source_sha256":"43f8339531846f95ce280dfd43fde0317fe7b7ebfed78dbc55a6b1380eb0e0a8","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["GET","JSON","MAIN","NEWDB","ROUTE","SELECT","SQL"],"language":"en"},"files":[{"path":"definitions/contract.js","language":"js","content":"NEWDB('contract', function(options, callback) {\n\tif (options.table === 'failure')\n\t\treturn callback(new Error('driver failed'));\n\tif (options.table === 'empty')\n\t\treturn callback(null, []);\n\tif (options.table === 'upsert') {\n\t\tMAIN.upsert.push(JSON.parse(JSON.stringify(options)));\n\t\treturn callback(null, options.exec === 'update' ? 0 : options.payload);\n\t}\n\tif (options.exec === 'list')\n\t\treturn callback(null, { items: [], count: 12 });\n\tcallback(null, options.first ? [options] : options);\n});\nMAIN.upsert = [];"},{"path":"controllers/contract.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /contract/', contract);\n};\n\nasync function contract($) {\n\ttry {\n\t\tvar out = {};\n\t\tvar methods = ['find', 'all', 'read', 'one', 'list', 'check', 'count', 'insert', 'ins', 'update', 'modify', 'mod', 'upd', 'remove', 'rem', 'truncate', 'clear', 'drop'];\n\t\tout.operations = {};\n\t\tfor (var method of methods)\n\t\t\tout.operations[method] = await DB()[method]('contract/items', { name: 'Ann' }).promise();\n\t\tout.scalar = await DB().scalar('contract/items', 'sum', 'amount', 'group').promise();\n\t\tout.command = await DB().command('contract/items', 'vacuum').promise();\n\t\tout.raw = await DB().query('contract', 'SELECT ?', [7]).promise();\n\t\tout.filters = await DB().find('contract/items').id([1, 2]).userid({ id: 'u1' }).equal({ active: true }).where('age', '!=', 0).in('team', [{ id: 'a' }], 'id').notin('team', 'b').between('age', 18, 65).or(function(q) { q.where('role', 'a').where('role', 'b'); }).array('tags', ['x']).month('created', 1).day('created', 2).year('created', '>=', 2020).hour('created', 3).minute('created', 4).search('name', 'ann', 'beg').contains('email').empty('deleted').query('trusted_fragment').join('team', 'teams', 'left', 'a.id', 'b.id').permit('permissions', 'RW', ['team'], 'u1', false).promise();\n\t\tout.options = await DB().find('contract/items').fields('id, name').returning('id, name').sort('name', true).gridsort('id_asc').limit(5).skip(2).primarykey('uid').schema('public').language({ language: 'es' }, '_').debug().promise();\n\t\tout.page = await DB().list('contract/items').paginate('2', '500', 10).promise();\n\t\tout.grid = await DB().find('contract/items').autoquery({ fields: 'id,password', sort: 'password_desc,name_desc', page: '2', limit: '500', age: '>18' }, 'id:String,name:String,age:Number', 'id_asc', 10).promise();\n\t\tout.gridfields = await DB().find('contract/items').gridfields('id', 'id,name').promise();\n\t\tvar queue = DB();\n\t\tqueue.read('contract/items').set('one');\n\t\tqueue.count('contract/items').set('count');\n\t\tqueue.find('contract/items').nobind();\n\t\tout.queue = await queue.promise();\n\t\tvar hooks = [];\n\t\tawait DB().read('contract/empty').data(function(v) { hooks.push(v === null ? 'null' : 'value'); }).promise();\n\t\ttry { await DB().read('contract/empty').error('missing').fail(function() { hooks.push('fail'); }).promise(); } catch (e) { out.missing = String(e); }\n\t\ttry { await DB().find('contract/failure').promise(); } catch (e) { out.failure = String(e); }\n\t\tout.hooks = hooks;\n\t\tout.upsert = await DB().update('contract/upsert', { name: 'new' }, true).where('id', 7).insert(function(payload, role) { payload.role = role; }, 'member').promise();\n\t\tout.upsertCalls = MAIN.upsert;\n\t\tout.load = await DB().load('contract', { table: 'items', exec: 'find', take: 2, filter: [] }).promise();\n\t\t$.json(out);\n\t} catch (e) { $.invalid(e); }\n}"}],"test":"const r = await get('/contract/');\nassert(r.status === 200 && r.json, 'contract returns: ' + r.body);\nconst c = r.json;\nassert(c.operations.count.exec === 'count' && c.operations.remove.exec === 'remove' && c.operations.drop.exec === 'drop', 'dispatch operation names');\nfor (const [alias, op] of Object.entries({ all: 'find', one: 'read', ins: 'insert', modify: 'update', mod: 'update', upd: 'update', rem: 'remove', clear: 'truncate' }))\n\tassert(c.operations[alias].exec === op, alias + ' dispatches ' + op);\nassert(c.operations.read.first === true && c.operations.read.take === 1 && c.operations.check.first === 1, 'single result options');\nassert(c.scalar.scalar.type === 'sum' && c.scalar.scalar.key2 === 'group' && c.command.command === 'vacuum', 'scalar and command');\nassert(c.raw.query === 'SELECT ?' && c.raw.params[0] === 7 && c.raw.table === '', 'raw query arguments');\nconst f = c.filters.filter;\nassert(f[0].type === 'in' && f[1].value === 'u1' && f[3].comparer === '<>', 'id, userid, equal and comparer normalization');\nassert(f.find(n => n.type === 'or').value.length === 2, 'nested OR');\nfor (const type of ['in','notin','between','array','month','day','year','hour','minute','search','contains','empty','query','join','permit'])\n\tassert(f.some(n => n.type === type), 'filter record: ' + type);\nassert(f.find(n => n.type === 'permit').value.join(',') === 'Rteam,Wteam', 'permit expands permission prefixes');\nassert(c.options.fields.join(',') === 'id,name' && c.options.returning.join(',') === 'id,name', 'fields and returning');\nassert(c.options.sort.join(',') === 'name_desc,id_asc' && c.options.take === 5 && c.options.skip === 2 && c.options.language === '_es', 'query options');\nassert(c.page.page === 2 && c.page.limit === 10 && c.page.pages === 2, 'list metadata and capped limit');\nassert(c.grid.fields.join(',') === 'id' && c.grid.sort.join(',') === 'name_desc' && c.grid.take === 10 && c.grid.skip === 10, 'autoquery allowlist and pagination');\nassert(!c.gridfields.fields, '0.0.19 gridfields does not apply accepted nonempty fields');\nassert(c.queue.one.exec === 'read' && c.queue.count.exec === 'count', 'named queue results and nobind');\nassert(c.missing.includes('missing') && c.failure.includes('driver failed') && c.hooks.join(',') === 'null,fail', 'empty, error and hooks');\nassert(c.upsert.role === 'member' && c.upsertCalls.length === 2 && c.upsertCalls[1].exec === 'insert' && c.upsertCalls[1].filter.length === 0, 'upsert fallback and payload hook');\nassert(c.load.take === 2, 'load serialized options');","test_kind":"http"}
{"id":"total5/native-components","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/native-components/","markdown_url":"https://forge.totaljs.es/en/total5/native-components.md","tags":["total5","reference"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"eaeb5ef2369d4e452e4997708f146e8cc01fb0902aef536102f54482a457cf0a","checked_at":"2026-10-20T12:18:26.385Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Compile and run a native server component","description":"Compile trusted Total.js component HTML, create an instance, validate messages, reconfigure it and release its resources.","messages":[{"role":"user","content":"Compile and run a native server component\n\nCompile trusted Total.js component HTML, create an instance, validate messages, reconfigure it and release its resources."},{"role":"assistant","content":"`NEWCOMPONENT(html, [callback])` compiles a **server component**, distinct from a browser jComponent. It accepts source text containing `<script total>` or `<script node>`; without a callback it returns a promise. A name loads `components/<name>.html`; a URL downloads source. This recipe uses inline trusted code only, without remote source or npm declarations.\n\nThe exported component supplies metadata, `config` defaults and `make(instance,config)`. `component.create(overrides)` creates an instance with cloned defaults. Declare `inputs:[{id,schema}]` for input transformation. `instance.input(inputId,data,callback)` delivers a message whose `.data` and `.error` reflect schema validation. **The handler still runs on invalid input**, so check `message.error` before processing. `message.send(outputId,data)` emits an output; `message.destroy()` ends it.\n\n`instance.reconfigure(values)` updates its configuration and calls its configure/save hooks. `instance.remove()` calls lifecycle hooks and removes the instance; `component.remove()` removes all instances and invokes uninstall. Components execute with the application's privileges and are not a sandbox.\n\n```js controllers/components.js\nexports.install = function() {\n\tROUTE('GET /native-component/', exercise);\n};\nasync function exercise($) {\n\ttry {\n\t\tvar source = '<script total>exports.id=\"native-echo\";exports.name=\"Native echo\";exports.config={prefix:\"one:\"};exports.inputs=[{id:\"input\",schema:\"*name:String\"}];exports.make=function(instance,config){instance.message=function($){if($.error){$.send(\"invalid\",{invalid:true});$.destroy();return;}$.send(\"output\",{text:config.prefix+$.data.name});$.destroy();};instance.destroy=function(){MAIN.componentClosed=true;};};</script>';\n\t\tvar component = await NEWCOMPONENT(source);\n\t\tcomponent.debugger = false;\n\t\tvar instance = component.create();\n\t\tvar first = await new Promise(resolve => instance.input('input',{name:'Ann'},message => resolve(message.data)));\n\t\tvar invalid = await new Promise(resolve => instance.input('input',{},message => resolve(message.data)));\n\t\tinstance.reconfigure({prefix:'two:'});\n\t\tvar second = await new Promise(resolve => instance.input('input',{name:'Bob'},message => resolve(message.data)));\n\t\tinstance.remove();\n\t\tvar rejected = false;\n\t\ttry { await NEWCOMPONENT('<body>no backend script</body>'); } catch (e) { rejected = true; }\n\t\t$.json({first:first,invalid:invalid,second:second,remaining:component.instances.length,closed:MAIN.componentClosed,rejected:rejected});\n\t} catch (e) { $.invalid(e); }\n}\n```"}],"knowledge":{"content_sha256":"9445f3e45dbdb4d6c027386dcc52d91d2904a31c44893a2f970d9e3ab5324aaf","source_sha256":"eaeb5ef2369d4e452e4997708f146e8cc01fb0902aef536102f54482a457cf0a","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["GET","MAIN","NEWCOMPONENT","ROUTE","URL"],"language":"en"},"files":[{"path":"controllers/components.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /native-component/', exercise);\n};\nasync function exercise($) {\n\ttry {\n\t\tvar source = '<script total>exports.id=\"native-echo\";exports.name=\"Native echo\";exports.config={prefix:\"one:\"};exports.inputs=[{id:\"input\",schema:\"*name:String\"}];exports.make=function(instance,config){instance.message=function($){if($.error){$.send(\"invalid\",{invalid:true});$.destroy();return;}$.send(\"output\",{text:config.prefix+$.data.name});$.destroy();};instance.destroy=function(){MAIN.componentClosed=true;};};</script>';\n\t\tvar component = await NEWCOMPONENT(source);\n\t\tcomponent.debugger = false;\n\t\tvar instance = component.create();\n\t\tvar first = await new Promise(resolve => instance.input('input',{name:'Ann'},message => resolve(message.data)));\n\t\tvar invalid = await new Promise(resolve => instance.input('input',{},message => resolve(message.data)));\n\t\tinstance.reconfigure({prefix:'two:'});\n\t\tvar second = await new Promise(resolve => instance.input('input',{name:'Bob'},message => resolve(message.data)));\n\t\tinstance.remove();\n\t\tvar rejected = false;\n\t\ttry { await NEWCOMPONENT('<body>no backend script</body>'); } catch (e) { rejected = true; }\n\t\t$.json({first:first,invalid:invalid,second:second,remaining:component.instances.length,closed:MAIN.componentClosed,rejected:rejected});\n\t} catch (e) { $.invalid(e); }\n}"}],"test":"const r = await get('/native-component/');\nassert(r.status === 200 && r.json, 'component exercise: ' + r.body);\nassert(r.json.first.text === 'one:Ann' && r.json.second.text === 'two:Bob', 'input and reconfiguration');\nassert(r.json.invalid.invalid === true, 'handler checks input validation error');\nassert(r.json.remaining === 0 && r.json.closed === true, 'instance removal lifecycle');\nassert(r.json.rejected === true, 'missing backend script rejected');","test_kind":"http"}
{"id":"total5/controller-completion-and-cache","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/controller-completion-and-cache/","markdown_url":"https://forge.totaljs.es/en/total5/controller-completion-and-cache.md","tags":["total5","reference"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"082076d69f83c25babbe301f5f32ba3b262c25c3aefb97436759c8402a21263e","checked_at":"2026-10-20T12:17:56.441Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Controller callbacks, manual authorization and conditional responses","description":"Execute jsonstring, respond, done, successful, authorize, custom and Last-Modified handling.","messages":[{"role":"user","content":"Controller callbacks, manual authorization and conditional responses\n\nExecute jsonstring, respond, done, successful, authorize, custom and Last-Modified handling."},{"role":"assistant","content":"These are ordinary route-controller methods, distinct from the action context. `$.jsonstring()` takes already-serialized JSON. `$.respond()` selects the method named by `$.response.output`, defaulting to JSON. `$.done(true)` forwards the callback result inside the success envelope; another argument selects a fixed value. `$.successful(fn)` calls your function only when there is no error.\n\n`await $.authorize()` resolves with the authorization result; an anonymous result must still be handled by your code. For conditional responses pass a **UTC header string** to `$.notmodified()`: in 0.0.19 comparison is exact string equality. `$.httpcache()` sets headers but `$.json()` overwrites cache policy, so the example sends a raw buffer after setting them. Use public caching only for public representations. `$.custom()` transfers response ownership to your handler; finish the Node response yourself.\n\n```js definitions/auth.js\nAUTH(function($) {\n\tif ($.headers['x-demo-user'] === 'alice')\n\t\t$.success({ id: 'alice' });\n\telse\n\t\t$.invalid();\n});\n```\n\n```js controllers/completion.js\nexports.install = function() {\n\tROUTE('GET /serialized/', $ => $.jsonstring('{\"ok\":true}'));\n\tROUTE('GET /respond/', function($) { $.response.output = 'plain'; $.respond('hello'); });\n\tROUTE('GET /done/', $ => $.done(true)(null, { id: 7 }));\n\tROUTE('GET /successful/', $ => $.successful(value => $.json({ value: value }))(null, 9));\n\tROUTE('GET /callback-error/', $ => $.successful(() => $.json({ wrong: true }))('failure'));\n\tROUTE('GET /manual-auth/', async function($) {\n\t\tvar user = await $.authorize();\n\t\tif (user)\n\t\t\t$.json({ id: $.user.id });\n\t\telse\n\t\t\t$.invalid(401);\n\t});\n\tROUTE('GET /conditional/', function($) {\n\t\tvar modified = 'Mon, 05 Jan 2026 00:00:00 GMT';\n\t\tif ($.notmodified(modified))\n\t\t\treturn;\n\t\t$.httpcache(modified);\n\t\t$.binary(Buffer.from('public version 1'), 'text/plain');\n\t});\n\tROUTE('GET /custom/', function($) {\n\t\t$.custom();\n\t\t$.res.writeHead(202, { 'content-type': 'text/plain' });\n\t\t$.res.end('accepted');\n\t});\n};\n```"}],"knowledge":{"content_sha256":"787a7a2bd01ce5f203e2d87e26c6bd63f6e6db685bd180c8648853021e8f2bd0","source_sha256":"082076d69f83c25babbe301f5f32ba3b262c25c3aefb97436759c8402a21263e","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["AUTH","GET","GMT","JSON","ROUTE","UTC"],"language":"en"},"files":[{"path":"definitions/auth.js","language":"js","content":"AUTH(function($) {\n\tif ($.headers['x-demo-user'] === 'alice')\n\t\t$.success({ id: 'alice' });\n\telse\n\t\t$.invalid();\n});"},{"path":"controllers/completion.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /serialized/', $ => $.jsonstring('{\"ok\":true}'));\n\tROUTE('GET /respond/', function($) { $.response.output = 'plain'; $.respond('hello'); });\n\tROUTE('GET /done/', $ => $.done(true)(null, { id: 7 }));\n\tROUTE('GET /successful/', $ => $.successful(value => $.json({ value: value }))(null, 9));\n\tROUTE('GET /callback-error/', $ => $.successful(() => $.json({ wrong: true }))('failure'));\n\tROUTE('GET /manual-auth/', async function($) {\n\t\tvar user = await $.authorize();\n\t\tif (user)\n\t\t\t$.json({ id: $.user.id });\n\t\telse\n\t\t\t$.invalid(401);\n\t});\n\tROUTE('GET /conditional/', function($) {\n\t\tvar modified = 'Mon, 05 Jan 2026 00:00:00 GMT';\n\t\tif ($.notmodified(modified))\n\t\t\treturn;\n\t\t$.httpcache(modified);\n\t\t$.binary(Buffer.from('public version 1'), 'text/plain');\n\t});\n\tROUTE('GET /custom/', function($) {\n\t\t$.custom();\n\t\t$.res.writeHead(202, { 'content-type': 'text/plain' });\n\t\t$.res.end('accepted');\n\t});\n};"}],"test":"assert((await get('/serialized/')).json.ok === true, 'serialized JSON');\nassert((await get('/respond/')).body === 'hello', 'respond output');\nconst done = (await get('/done/')).json;\nassert(done.success === true && done.value.id === 7, 'done forwards result');\nassert((await get('/successful/')).json.value === 9, 'successful callback');\nassert((await get('/callback-error/')).status === 400, 'callback failure');\nassert((await get('/manual-auth/')).status === 401, 'manual auth anonymous');\nassert((await get('/manual-auth/', { headers: { 'x-demo-user': 'alice' } })).json.id === 'alice', 'manual auth approved');\nconst first = await get('/conditional/');\nassert(first.body === 'public version 1' && first.headers.get('last-modified') === 'Mon, 05 Jan 2026 00:00:00 GMT', 'cache date');\nconst same = await get('/conditional/', { headers: { 'if-modified-since': first.headers.get('last-modified') } });\nassert(same.status === 304 && same.body === '', 'conditional 304 without body');\nassert((await get('/conditional/', { headers: { 'if-modified-since': 'Mon, 01 Jan 2024 00:00:00 GMT' } })).status === 200, 'stale date gets representation');\nassert((await get('/custom/')).status === 202, 'custom response status');","test_kind":"http"}
{"id":"total5/action-transport-boundaries","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/action-transport-boundaries/","markdown_url":"https://forge.totaljs.es/en/total5/action-transport-boundaries.md","tags":["total5","reference"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"98eec380ca09c77f54300f73f348169a77715ffa64bdfe3747aa8d2a22c340ab","checked_at":"2026-10-20T12:17:40.033Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Action transport options and explicit TMS publishing","description":"Test csrf, encrypt, compress and publish against real HTTP and TMS rather than assuming metadata enforces behavior.","messages":[{"role":"user","content":"Action transport options and explicit TMS publishing\n\nTest csrf, encrypt, compress and publish against real HTTP and TMS rather than assuming metadata enforces behavior."},{"role":"assistant","content":"In `total5@0.0.19`: `NEWACTION()` stores `csrf`, `encrypt` and `compress` in action metadata, but they do not establish the HTTP guarantees their names might suggest on HTTP routes:\n\n- `csrf:true` alone accepts a POST without a token. Use a middleware that calls `DEF.onCSRFcheck()` with `secret_csrf` configured, as in [csrf-protection](/en/total5/csrf-protection/).\n- `encrypt:true` on a declared route adds `@encrypt` metadata, but this version still accepts plaintext JSON and returns plaintext JSON. Use HTTPS for transport.\n- `compress:false` does not disable ordinary HTTP gzip; response compression follows `$httpcompress`, the client's accepted encoding and response size. A large JSON response can be gzipped even when this action option is false.\n- `publish` declares a TMS topic/schema. Completing an action does not automatically publish its result. Call `$.publish(value)` explicitly and construct the public payload deliberately. Select the fields to publish before calling `$.publish(value)`.\n\nConfigure the TMS token for your application. TMS messages go to subscribers of that topic; application permission and tenancy rules still belong in your code.\n\n```text config\nsecret_csrf : local-csrf-fixture-only\n$tms : true\nsecret_tms : local-tms-fixture-only\n$httpcompress : true\n```\n\n```js definitions/transport.js\nNEWACTION('Transport|csrf', {\n\tcsrf: true,\n\tinput: '*value:String',\n\taction: function($, model) { $.callback(model); }\n});\nNEWACTION('Transport|encrypted', {\n\tencrypt: true,\n\troute: 'POST /encrypted/',\n\tinput: '*value:String',\n\taction: function($, model) { $.callback(model); }\n});\nNEWACTION('Transport|compressed', {\n\tcompress: false,\n\taction: function($) { $.callback({ text: 'abcdefghij'.repeat(400) }); }\n});\nNEWACTION('Transport|notice', {\n\tpublish: 'value:String',\n\tinput: '*value:String',\n\taction: function($, model) {\n\t\t$.publish({ value: model.value });\n\t\t$.success();\n\t}\n});\n```\n\n```js controllers/transport.js\nexports.install = function() {\n\tROUTE('POST /csrf-option/ @json --> Transport|csrf');\n\tROUTE('GET /compression-option/ --> Transport|compressed');\n\tROUTE('POST /notice/ @json --> Transport|notice');\n};\n```"}],"knowledge":{"content_sha256":"8baa757137eb3c808eb19ae317806f99304c91c1a20c49ff788442cfce32e39c","source_sha256":"98eec380ca09c77f54300f73f348169a77715ffa64bdfe3747aa8d2a22c340ab","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["DEF","GET","HTTP","HTTPS","JSON","NEWACTION","POST","ROUTE","TMS"],"language":"en"},"files":[{"path":"config","language":"text","content":"secret_csrf : local-csrf-fixture-only\n$tms : true\nsecret_tms : local-tms-fixture-only\n$httpcompress : true"},{"path":"definitions/transport.js","language":"js","content":"NEWACTION('Transport|csrf', {\n\tcsrf: true,\n\tinput: '*value:String',\n\taction: function($, model) { $.callback(model); }\n});\nNEWACTION('Transport|encrypted', {\n\tencrypt: true,\n\troute: 'POST /encrypted/',\n\tinput: '*value:String',\n\taction: function($, model) { $.callback(model); }\n});\nNEWACTION('Transport|compressed', {\n\tcompress: false,\n\taction: function($) { $.callback({ text: 'abcdefghij'.repeat(400) }); }\n});\nNEWACTION('Transport|notice', {\n\tpublish: 'value:String',\n\tinput: '*value:String',\n\taction: function($, model) {\n\t\t$.publish({ value: model.value });\n\t\t$.success();\n\t}\n});"},{"path":"controllers/transport.js","language":"js","content":"exports.install = function() {\n\tROUTE('POST /csrf-option/ @json --> Transport|csrf');\n\tROUTE('GET /compression-option/ --> Transport|compressed');\n\tROUTE('POST /notice/ @json --> Transport|notice');\n};"}],"test":"const csrf = await post('/csrf-option/', { value: 'no token' });\nassert(csrf.status === 200 && csrf.json.value === 'no token', 'csrf option alone does not enforce token check');\nconst encrypted = await post('/encrypted/', { value: 'plaintext' });\nassert(encrypted.status === 200 && encrypted.json.value === 'plaintext', 'encrypt route still accepts and returns plaintext JSON in 0.0.19');\nconst compressed = await get('/compression-option/', { headers: { 'accept-encoding': 'gzip' } });\nassert(compressed.status === 200 && compressed.headers.get('content-encoding') === 'gzip' && compressed.json.text.length === 4000, 'global gzip applies despite action compress:false');\nconst client = await socket('/$tms/', { headers: { 'x-token': 'local-tms-fixture-only' } });\nconst meta = await client.next();\nassert(meta.type === 'meta' && meta.publish.some(n => n.id === 'Transport|notice'), 'publish option registers topic');\nclient.send({ type: 'subscribers', subscribers: ['Transport|notice'] });\nawait new Promise(resolve => setTimeout(resolve, 150));\nassert((await post('/notice/', { value: 'hello' })).status === 200, 'publishing action completes');\nconst message = await client.next();\nassert(message.type === 'publish' && message.id === 'Transport|notice' && message.data.value === 'hello', 'explicit publish reaches subscriber');\nclient.close();","test_kind":"http"}
{"id":"total5/touch-static-cache","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/touch-static-cache/","markdown_url":"https://forge.totaljs.es/en/total5/touch-static-cache.md","tags":["total5","reference"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"e1b9c13697476f3a6a9bde56ef1a5887eb137216e349015f8b2d2417fbdf01f7","checked_at":"2026-10-20T12:18:55.900Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Invalidate release-mode static caches with TOUCH","description":"Serve and modify a temporary static file, clear its cached representation and remove cached 404 entries.","messages":[{"role":"user","content":"Invalidate release-mode static caches with TOUCH\n\nServe and modify a temporary static file, clear its cached representation and remove cached 404 entries."},{"role":"assistant","content":"`TOUCH(url)` clears three cache entries for that URL: `F.temporary.minified`, `tmp` and `notfound`. `TOUCH()` clears those maps. It does not clear `CACHE`, NoSQL documents or every view/configuration cache. Call it after changing a generated asset in release mode. Keep this operation in trusted server code; the write endpoint below is only a local fixture.\n\nCache invalidation applies to the running process. When an application runs in several processes, coordinate invalidation across those processes.\n\n```text public/generated.txt\nbefore\n```\n\n```js controllers/assets.js\nexports.install = function() {\n\tROUTE('POST /regenerate/', function($) {\n\t\tF.Fs.writeFileSync(PATH.public('generated.txt'), 'after');\n\t\tF.Fs.writeFileSync(PATH.public('appeared.txt'), 'created');\n\t\tTOUCH('/generated.txt');\n\t\tTOUCH('/appeared.txt');\n\t\t$.success();\n\t});\n};\n```"}],"knowledge":{"content_sha256":"74d53a2b7d1a609a9c88ee5f5d38268ea8eb33b899b80fd78848ba95b5ae4e8c","source_sha256":"e1b9c13697476f3a6a9bde56ef1a5887eb137216e349015f8b2d2417fbdf01f7","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["CACHE","PATH","POST","ROUTE","TOUCH","URL"],"language":"en"},"files":[{"path":"public/generated.txt","language":"text","content":"before"},{"path":"controllers/assets.js","language":"js","content":"exports.install = function() {\n\tROUTE('POST /regenerate/', function($) {\n\t\tF.Fs.writeFileSync(PATH.public('generated.txt'), 'after');\n\t\tF.Fs.writeFileSync(PATH.public('appeared.txt'), 'created');\n\t\tTOUCH('/generated.txt');\n\t\tTOUCH('/appeared.txt');\n\t\t$.success();\n\t});\n};"}],"test":"assert((await get('/generated.txt')).body.trim() === 'before', 'initial static file');\nassert((await get('/appeared.txt')).status === 404, 'cache a missing asset');\nassert((await post('/regenerate/', {})).status === 200, 'regenerate fixture');\nassert((await get('/generated.txt')).body === 'after', 'modified asset after TOUCH');\nassert((await get('/appeared.txt')).body === 'created', 'new asset after clearing missing cache');","test_kind":"http"}
{"id":"total5/openclient-protocol","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/openclient-protocol/","markdown_url":"https://forge.totaljs.es/en/total5/openclient-protocol.md","tags":["total5","reference"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"71297c718c2ae5294390e0d443b563503b5dbc7f39bc2ae204268e65bda4663e","checked_at":"2026-10-20T12:18:30.279Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"OPENCLIENT RPC, events and shared connections","description":"Exercise OPENCLIENT with a local protocol fixture, including initialization, callback correlation, timeout and logical-client removal.","messages":[{"role":"user","content":"OPENCLIENT RPC, events and shared connections\n\nExercise OPENCLIENT with a local protocol fixture, including initialization, callback correlation, timeout and logical-client removal."},{"role":"assistant","content":"`OPENCLIENT(url, [id])` is the native Open Platform protocol client. It converts `http`/`https` URLs to `ws`/`wss`. Logical clients with the same URL share a WebSocket. `.online(fn)` receives the server's `{type:'init',id:...}` metadata; `.message(fn)` receives unsolicited messages. `.rpc(message)` returns a promise; `.send(message, callback, [filter], [timeoutMs])` uses a callback; `.cmd(message)` sends without waiting. `.remove()` closes the physical socket only after its last logical client is removed.\n\nThe example includes a local protocol server. `callbackid`, `error` and `response` are protocol fields. A callback timeout is opt-in; set one for requests that need a deadline. Keep real platform credentials and URLs in server configuration.\n\n```js controllers/open.js\nexports.install = function() {\n\tROUTE('SOCKET /fixture/', fixture);\n\tROUTE('GET /openclient/', exercise);\n};\nfunction fixture() {\n\tvar server = this;\n\tserver.on('open', function(client) {\n\t\tclient.send({ type: 'init', id: 'local-fixture', version: 1 });\n\t});\n\tserver.on('message', function(client, message) {\n\t\tif (message.type === 'ignore')\n\t\t\treturn;\n\t\tif (message.type === 'event') {\n\t\t\tclient.send({ type: 'notice', value: message.value });\n\t\t\treturn;\n\t\t}\n\t\tclient.send({ callbackid: message.callbackid, error: message.type === 'fail' ? 'denied' : null, response: { value: message.value } });\n\t});\n}\nasync function exercise($) {\n\tvar a;\n\tvar b;\n\ttry {\n\t\tvar url = $.hostname('/fixture/');\n\t\ta = OPENCLIENT(url, 'first');\n\t\tawait new Promise(function(resolve, reject) {\n\t\t\tvar timer = setTimeout(() => reject(new Error('init deadline')), 2000);\n\t\t\ta.online(function(meta) { if (meta) { clearTimeout(timer); resolve(); } });\n\t\t});\n\t\tb = OPENCLIENT(url, 'second');\n\t\tvar shared = a.ws === b.ws;\n\t\tvar sum = await a.rpc({ type: 'echo', value: 7 });\n\t\tvar event = new Promise(resolve => a.message(resolve));\n\t\ta.cmd({ type: 'event', value: 9 });\n\t\tvar notice = await event;\n\t\tvar denied = await new Promise(resolve => a.send({ type: 'fail' }, err => resolve(String(err)), 300));\n\t\tvar timeout = await new Promise(resolve => a.send({ type: 'ignore' }, err => resolve(String(err)), 50));\n\t\ta.remove();\n\t\ta = null;\n\t\tvar alive = await b.rpc({ type: 'echo', value: 11 });\n\t\tvar meta = b.meta;\n\t\tb.remove();\n\t\tb = null;\n\t\t$.json({ shared: shared, sum: sum, notice: notice, denied: denied, timeout: timeout, alive: alive, meta: meta });\n\t} catch (e) { $.invalid(e); }\n\tfinally { if (a) a.remove(); if (b) b.remove(); }\n}\n```"}],"knowledge":{"content_sha256":"3debacfd6c900cdaaedcd3e3d489b7a1403f947f09e98c01bd4c4e42509ccf42","source_sha256":"71297c718c2ae5294390e0d443b563503b5dbc7f39bc2ae204268e65bda4663e","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["GET","OPENCLIENT","ROUTE","SOCKET","URL"],"language":"en"},"files":[{"path":"controllers/open.js","language":"js","content":"exports.install = function() {\n\tROUTE('SOCKET /fixture/', fixture);\n\tROUTE('GET /openclient/', exercise);\n};\nfunction fixture() {\n\tvar server = this;\n\tserver.on('open', function(client) {\n\t\tclient.send({ type: 'init', id: 'local-fixture', version: 1 });\n\t});\n\tserver.on('message', function(client, message) {\n\t\tif (message.type === 'ignore')\n\t\t\treturn;\n\t\tif (message.type === 'event') {\n\t\t\tclient.send({ type: 'notice', value: message.value });\n\t\t\treturn;\n\t\t}\n\t\tclient.send({ callbackid: message.callbackid, error: message.type === 'fail' ? 'denied' : null, response: { value: message.value } });\n\t});\n}\nasync function exercise($) {\n\tvar a;\n\tvar b;\n\ttry {\n\t\tvar url = $.hostname('/fixture/');\n\t\ta = OPENCLIENT(url, 'first');\n\t\tawait new Promise(function(resolve, reject) {\n\t\t\tvar timer = setTimeout(() => reject(new Error('init deadline')), 2000);\n\t\t\ta.online(function(meta) { if (meta) { clearTimeout(timer); resolve(); } });\n\t\t});\n\t\tb = OPENCLIENT(url, 'second');\n\t\tvar shared = a.ws === b.ws;\n\t\tvar sum = await a.rpc({ type: 'echo', value: 7 });\n\t\tvar event = new Promise(resolve => a.message(resolve));\n\t\ta.cmd({ type: 'event', value: 9 });\n\t\tvar notice = await event;\n\t\tvar denied = await new Promise(resolve => a.send({ type: 'fail' }, err => resolve(String(err)), 300));\n\t\tvar timeout = await new Promise(resolve => a.send({ type: 'ignore' }, err => resolve(String(err)), 50));\n\t\ta.remove();\n\t\ta = null;\n\t\tvar alive = await b.rpc({ type: 'echo', value: 11 });\n\t\tvar meta = b.meta;\n\t\tb.remove();\n\t\tb = null;\n\t\t$.json({ shared: shared, sum: sum, notice: notice, denied: denied, timeout: timeout, alive: alive, meta: meta });\n\t} catch (e) { $.invalid(e); }\n\tfinally { if (a) a.remove(); if (b) b.remove(); }\n}"}],"test":"const r = await get('/openclient/');\nassert(r.status === 200 && r.json, 'OPENCLIENT exercise: ' + r.body);\nconst c = r.json;\nassert(c.shared && c.meta.id === 'local-fixture', 'shared connection and init metadata');\nassert(c.sum.value === 7 && c.alive.value === 11, 'RPC and surviving logical client');\nassert(c.notice.type === 'notice' && c.notice.value === 9, 'unsolicited message');\nassert(c.denied === 'denied' && c.timeout === 'timeout', 'remote error and callback timeout');","test_kind":"http"}
{"id":"total5/flow-files-and-workers","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/flow-files-and-workers/","markdown_url":"https://forge.totaljs.es/en/total5/flow-files-and-workers.md","tags":["total5","reference"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"d8d4b17f3b3a1febf2448b8b180938df2b7493dd063bd1cc0b49454adefed96e","checked_at":"2026-10-20T12:18:08.447Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Load FlowStream files and execute isolated workers","description":"Run the same trusted FlowStream schema as a worker thread and a child process, load .flow files and remove instances.","messages":[{"role":"user","content":"Load FlowStream files and execute isolated workers\n\nRun the same trusted FlowStream schema as a worker thread and a child process, load .flow files and remove instances."},{"role":"assistant","content":"`Flow.init(directory, callback)` reads `.flow` JSON files and loads each trusted schema; names containing `-bk` or `_bk` are skipped. The default directory is `flowstreams/`. `Flow.load(schema, callback)` also accepts `worker:'worker'` for a Node worker thread and `worker:true` for a child process in this version. Flow workers have separate globals: do not expect the parent's `MAIN` objects or closures to be available inside a component.\n\nThe visual Flow designer and its product-specific UI are separate from this backend API. Components are executable trusted code; workers are process/thread isolation, not an untrusted-code sandbox.\n\n```js controllers/flows.js\nexports.install = function() {\n\tROUTE('GET /flow-workers/', exercise);\n};\nasync function exercise($) {\n\tvar ids = [];\n\ttry {\n\t\tvar source = '<script total>exports.id=\"echo\";exports.name=\"Echo\";exports.inputs=[{id:\"input\",name:\"Input\"}];exports.config={prefix:\"\"};exports.make=function(instance,config){instance.message=function($){$.data=config.prefix+String($.data);$.destroy();};};</script>';\n\t\tvar out = [];\n\t\tfor (var worker of ['worker', true]) {\n\t\t\tvar id = worker === true ? 'file-process' : 'file-thread';\n\t\t\tids.push(id);\n\t\t\tvar schema = { id: id, name: id, worker: worker, components: { echo: source }, design: { a: { id: 'a', component: 'echo', config: { prefix: 'one:' }, connections: {} } }, variables: {} };\n\t\t\tvar dir = PATH.root(id);\n\t\t\tF.Fs.mkdirSync(dir, { recursive: true });\n\t\t\tF.Fs.writeFileSync(dir + '/demo.flow', JSON.stringify(schema));\n\t\t\tF.Fs.writeFileSync(dir + '/ignored-bk.flow', JSON.stringify({ id: 'must-not-load' }));\n\t\t\tawait new Promise(resolve => Flow.init(dir, resolve));\n\t\t\tvar instance = Flow.instances[id];\n\t\t\tvar first = await new Promise((resolve, reject) => instance.exec({ id: 'a', data: 'hello', callback: (err, msg) => err ? reject(err) : resolve(msg.data) }));\n\t\t\tinstance.reconfigure('a', { prefix: 'two:' });\n\t\t\tvar second = await new Promise((resolve, reject) => instance.exec({ id: 'a', data: 'again', callback: (err, msg) => err ? reject(err) : resolve(msg.data) }));\n\t\t\tout.push({ worker: worker, first: first, second: second, isolated: !!instance.flow.isworkerthread });\n\t\t\tFlow.remove(id);\n\t\t}\n\t\t$.json({ results: out, removed: ids.every(id => !Flow.instances[id]), backupSkipped: !Flow.instances['must-not-load'] });\n\t} catch (e) { $.invalid(e); }\n\tfinally { for (var id of ids) if (Flow.instances[id]) Flow.remove(id); }\n}\n```"}],"knowledge":{"content_sha256":"141aa8d246eacc9f5b9663819e73f10218ac1ebaff06febc7bd0e8e79a85eb9f","source_sha256":"d8d4b17f3b3a1febf2448b8b180938df2b7493dd063bd1cc0b49454adefed96e","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["API","GET","JSON","MAIN","PATH","ROUTE"],"language":"en"},"files":[{"path":"controllers/flows.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /flow-workers/', exercise);\n};\nasync function exercise($) {\n\tvar ids = [];\n\ttry {\n\t\tvar source = '<script total>exports.id=\"echo\";exports.name=\"Echo\";exports.inputs=[{id:\"input\",name:\"Input\"}];exports.config={prefix:\"\"};exports.make=function(instance,config){instance.message=function($){$.data=config.prefix+String($.data);$.destroy();};};</script>';\n\t\tvar out = [];\n\t\tfor (var worker of ['worker', true]) {\n\t\t\tvar id = worker === true ? 'file-process' : 'file-thread';\n\t\t\tids.push(id);\n\t\t\tvar schema = { id: id, name: id, worker: worker, components: { echo: source }, design: { a: { id: 'a', component: 'echo', config: { prefix: 'one:' }, connections: {} } }, variables: {} };\n\t\t\tvar dir = PATH.root(id);\n\t\t\tF.Fs.mkdirSync(dir, { recursive: true });\n\t\t\tF.Fs.writeFileSync(dir + '/demo.flow', JSON.stringify(schema));\n\t\t\tF.Fs.writeFileSync(dir + '/ignored-bk.flow', JSON.stringify({ id: 'must-not-load' }));\n\t\t\tawait new Promise(resolve => Flow.init(dir, resolve));\n\t\t\tvar instance = Flow.instances[id];\n\t\t\tvar first = await new Promise((resolve, reject) => instance.exec({ id: 'a', data: 'hello', callback: (err, msg) => err ? reject(err) : resolve(msg.data) }));\n\t\t\tinstance.reconfigure('a', { prefix: 'two:' });\n\t\t\tvar second = await new Promise((resolve, reject) => instance.exec({ id: 'a', data: 'again', callback: (err, msg) => err ? reject(err) : resolve(msg.data) }));\n\t\t\tout.push({ worker: worker, first: first, second: second, isolated: !!instance.flow.isworkerthread });\n\t\t\tFlow.remove(id);\n\t\t}\n\t\t$.json({ results: out, removed: ids.every(id => !Flow.instances[id]), backupSkipped: !Flow.instances['must-not-load'] });\n\t} catch (e) { $.invalid(e); }\n\tfinally { for (var id of ids) if (Flow.instances[id]) Flow.remove(id); }\n}"}],"test":"const r = await get('/flow-workers/');\nassert(r.status === 200 && r.json, 'Flow worker exercise: ' + r.body);\nassert(r.json.results.length === 2, 'both worker types executed');\nfor (const result of r.json.results)\n\tassert(result.first === 'one:hello' && result.second === 'two:again' && result.isolated, 'worker result/reconfiguration: ' + JSON.stringify(result));\nassert(r.json.removed && r.json.backupSkipped, 'removal and backup file exclusion');","test_kind":"http"}
{"id":"total5/action-hooks-and-admin","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/action-hooks-and-admin/","markdown_url":"https://forge.totaljs.es/en/total5/action-hooks-and-admin.md","tags":["total5","reference"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"21f4447e802aeecee218f4dc5070b7b40a657a6effaaeed78b6c476b351c2f57","checked_at":"2026-10-20T12:17:36.970Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Action lifecycle hooks, result extension and administrator guards","description":"Exercise oninput, onoutput, extend, sa and su with valid, invalid and failed actions.","messages":[{"role":"user","content":"Action lifecycle hooks, result extension and administrator guards\n\nExercise oninput, onoutput, extend, sa and su with valid, invalid and failed actions."},{"role":"assistant","content":"`oninput($)` runs after input validation and declared middleware, immediately before the action function. `onoutput(error,response,$)` runs on the successful completion path after output filtering and optional extension. In 0.0.19 an invalid input or `$.invalid()` does not call this hook; do not treat it as a universal error/finally hook.\n\n`extend:'result'` adds the action result to its validated input under that key and returns the extended input. A dotted path uses nested assignment. It is useful for deliberate action composition; it changes the public response contract.\n\nEither `sa:true` or `su:true` requires a user with either administrator flag. They do not define two different privilege tiers. Normal permissions checks remain separate. Set these administrator flags only from trusted server authentication.\n\n```js definitions/hooks.js\nMAIN.hooks = [];\nAUTH(function($) {\n\tvar role = $.headers['x-demo-role'];\n\tif (role === 'sa' || role === 'su') $.success({ id:'admin', [role]:true });\n\telse if (role === 'member') $.success({id:'member'});\n\telse $.invalid();\n});\nNEWACTION('Hooks|extend', {\n\tinput: '*name:String',\n\textend: 'result',\n\toninput: function($) { MAIN.hooks.push('input:' + $.payload.name); },\n\tonoutput: function(err, response) { MAIN.hooks.push('output:' + response.result.greeting); },\n\taction: function($, model) { $.callback({greeting:'Hello ' + model.name}); }\n});\nNEWACTION('Hooks|fail', {\n\tonoutput: function() { MAIN.hooks.push('unexpected'); },\n\taction: function($) { $.invalid('failed'); }\n});\nfor (var flag of ['sa','su']) {\n\tvar action = { action:function($) { $.success(); } };\n\taction[flag] = true;\n\tNEWACTION('Hooks|' + flag, action);\n}\n```\n\n```js controllers/hooks.js\nexports.install = function() {\n\tROUTE('POST /extend/ @json --> Hooks|extend');\n\tROUTE('GET /fail/ --> Hooks|fail');\n\tROUTE('GET /sa/ --> Hooks|sa');\n\tROUTE('GET /su/ --> Hooks|su');\n\tROUTE('GET /hook-events/', $ => $.json(MAIN.hooks));\n};\n```"}],"knowledge":{"content_sha256":"96d006ef7a49b5e38bca290fca66dc60e4ee9d0bd9b5924585c9a2bbf2646d17","source_sha256":"21f4447e802aeecee218f4dc5070b7b40a657a6effaaeed78b6c476b351c2f57","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["AUTH","GET","MAIN","NEWACTION","POST","ROUTE"],"language":"en"},"files":[{"path":"definitions/hooks.js","language":"js","content":"MAIN.hooks = [];\nAUTH(function($) {\n\tvar role = $.headers['x-demo-role'];\n\tif (role === 'sa' || role === 'su') $.success({ id:'admin', [role]:true });\n\telse if (role === 'member') $.success({id:'member'});\n\telse $.invalid();\n});\nNEWACTION('Hooks|extend', {\n\tinput: '*name:String',\n\textend: 'result',\n\toninput: function($) { MAIN.hooks.push('input:' + $.payload.name); },\n\tonoutput: function(err, response) { MAIN.hooks.push('output:' + response.result.greeting); },\n\taction: function($, model) { $.callback({greeting:'Hello ' + model.name}); }\n});\nNEWACTION('Hooks|fail', {\n\tonoutput: function() { MAIN.hooks.push('unexpected'); },\n\taction: function($) { $.invalid('failed'); }\n});\nfor (var flag of ['sa','su']) {\n\tvar action = { action:function($) { $.success(); } };\n\taction[flag] = true;\n\tNEWACTION('Hooks|' + flag, action);\n}"},{"path":"controllers/hooks.js","language":"js","content":"exports.install = function() {\n\tROUTE('POST /extend/ @json --> Hooks|extend');\n\tROUTE('GET /fail/ --> Hooks|fail');\n\tROUTE('GET /sa/ --> Hooks|sa');\n\tROUTE('GET /su/ --> Hooks|su');\n\tROUTE('GET /hook-events/', $ => $.json(MAIN.hooks));\n};"}],"test":"const ok = await post('/extend/',{name:'Ann',discarded:'secret'});\nassert(ok.status === 200 && ok.json.name === 'Ann' && ok.json.result.greeting === 'Hello Ann' && !('discarded' in ok.json), 'extend validated payload');\nassert((await post('/extend/',{})).status === 400, 'invalid input');\nassert((await get('/fail/')).status === 400, 'action failure');\nassert((await get('/hook-events/')).json.join(',') === 'input:Ann,output:Hello Ann', 'hooks execute on successful path only');\nfor (const endpoint of ['/sa/','/su/']) {\n\tassert((await get(endpoint)).status === 401, 'anonymous admin guard');\n\tassert((await get(endpoint,{headers:{'x-demo-role':'member'}})).status === 401, 'member admin guard');\n\tfor (const role of ['sa','su'])\n\t\tassert((await get(endpoint,{headers:{'x-demo-role':role}})).status === 200, role + ' passes ' + endpoint);\n}","test_kind":"http"}
{"id":"total5/query-builder-sqlite","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/query-builder-sqlite/","markdown_url":"https://forge.totaljs.es/en/total5/query-builder-sqlite.md","tags":["total5","reference"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"e247f2cec591f64a01a41cdaf4aa5b5a82d37afc8d8c5cc0db464e2afe4289df","checked_at":"2026-10-20T12:18:37.209Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Real SQL with native QueryBuilder and SQLite transactions","description":"Register a bounded SQLite driver, run parameterized CRUD and verify commit, constraint failure and rollback against a file-backed database.","messages":[{"role":"user","content":"Real SQL with native QueryBuilder and SQLite transactions\n\nRegister a bounded SQLite driver, run parameterized CRUD and verify commit, constraint failure and rollback against a file-backed database."},{"role":"assistant","content":"This example connects Total.js's native `DB()/NEWDB()` to a real file-backed SQLite database using Node's built-in `node:sqlite`. The example uses **Node 24.14.1 and total5@0.0.19**, without an npm SQL dependency. Check the [Node 24 SQLite documentation](https://nodejs.org/docs/latest-v24.x/api/sqlite.html) for runtime availability and API stability. `DatabaseSync` is synchronous: this small driver demonstrates correctness, not a recommendation for heavy blocking work on a busy HTTP thread.\n\nThis is an application-owned driver, **not the separate Total.js QueryBuilder SQL package**, nor a PostgreSQL/MySQL compatibility claim. It deliberately supports only one allowlisted table, equality filters, selected columns, sort, bounded pagination and basic CRUD. Unsupported operations fail closed. The broader builder contract is in [query-builder-contract](/en/total5/query-builder-contract/).\n\nValues use bound `?` parameters. Table/column names and order directions are selected by server allowlists; never concatenate arbitrary identifiers from requests. Update/delete require a filter. The HTTP fixture uses fixed demonstration data; add authentication and ownership constraints before exposing real records.\n\nA queue of `DB()` operations is not an atomic transaction. The transfer function explicitly owns one connection, begins a transaction, makes both changes synchronously, commits on success and rolls back on any error. Do not insert asynchronous waits into this single shared connection transaction. Multi-connection async drivers need connection reservation and their own transaction API.\n\n```js definitions/sqlite.js\nvar DatabaseSync = require('node:sqlite').DatabaseSync;\nvar db = new DatabaseSync(PATH.root('ledger.sqlite'));\ndb.exec('CREATE TABLE accounts (id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, balance INTEGER NOT NULL CHECK(balance >= 0)) STRICT');\ndb.prepare('INSERT INTO accounts VALUES (?, ?, ?)').run('a', 'Alice', 100);\ndb.prepare('INSERT INTO accounts VALUES (?, ?, ?)').run('b', 'Bob', 50);\n\nfunction column(name) {\n\tif (!['id', 'name', 'balance'].includes(name))\n\t\tthrow new Error('Column not allowed');\n\treturn '\"' + name + '\"';\n}\nfunction where(options, params) {\n\tvar filters = options.filter.map(function(f) {\n\t\tif (f.type !== 'where' || f.comparer !== '=')\n\t\t\tthrow new Error('Filter not supported');\n\t\tparams.push(f.value);\n\t\treturn column(f.name) + ' = ?';\n\t});\n\treturn filters.length ? ' WHERE ' + filters.join(' AND ') : '';\n}\nfunction integer(value, fallback, max) {\n\tif (value == null)\n\t\treturn fallback;\n\tif (!Number.isInteger(value) || value < 0 || value > max)\n\t\tthrow new Error('Invalid pagination');\n\treturn value;\n}\nNEWDB('sqlite', function(options, callback) {\n\ttry {\n\t\tif (options.table !== 'accounts')\n\t\t\tthrow new Error('Table not allowed');\n\t\tvar params = [];\n\t\tvar condition = where(options, params);\n\t\tvar result;\n\t\tif (['find','read','list','count'].includes(options.exec)) {\n\t\t\tvar count = db.prepare('SELECT COUNT(*) AS value FROM accounts' + condition).get(...params).value;\n\t\t\tif (options.exec === 'count')\n\t\t\t\tresult = count;\n\t\t\telse {\n\t\t\t\tvar fields = (options.fields || ['id','name','balance']).map(column).join(',');\n\t\t\t\tvar sort = (options.sort || ['id_asc']).map(function(s) {\n\t\t\t\t\tvar match = /^(id|name|balance)_(asc|desc)$/.exec(s);\n\t\t\t\t\tif (!match)\n\t\t\t\t\t\tthrow new Error('Sort not allowed');\n\t\t\t\t\treturn column(match[1]) + ' ' + match[2];\n\t\t\t\t}).join(',');\n\t\t\t\tvar take = integer(options.take, 100, 100);\n\t\t\t\tvar skip = integer(options.skip, 0, 100000);\n\t\t\t\tvar rows = db.prepare('SELECT ' + fields + ' FROM accounts' + condition + ' ORDER BY ' + sort + ' LIMIT ? OFFSET ?').all(...params, take, skip);\n\t\t\t\tresult = options.exec === 'list' ? { items: rows, count: count } : rows;\n\t\t\t}\n\t\t} else if (options.exec === 'insert') {\n\t\t\tvar p = options.payload;\n\t\t\tdb.prepare('INSERT INTO accounts (id,name,balance) VALUES (?,?,?)').run(p.id,p.name,p.balance);\n\t\t\tresult = { id: p.id };\n\t\t} else if (options.exec === 'update') {\n\t\t\tif (!condition)\n\t\t\t\tthrow new Error('Filter required');\n\t\t\tvar keys = Object.keys(options.payload);\n\t\t\tif (!keys.length)\n\t\t\t\tthrow new Error('Empty update');\n\t\t\tvar set = keys.map(key => column(key) + ' = ?').join(',');\n\t\t\tresult = db.prepare('UPDATE accounts SET ' + set + condition).run(...keys.map(key => options.payload[key]), ...params).changes;\n\t\t} else if (options.exec === 'remove') {\n\t\t\tif (!condition)\n\t\t\t\tthrow new Error('Filter required');\n\t\t\tresult = db.prepare('DELETE FROM accounts' + condition).run(...params).changes;\n\t\t} else\n\t\t\tthrow new Error('Operation not supported');\n\t\tcallback(null, result);\n\t} catch (e) { callback(e); }\n});\n\nMAIN.transfer = function(amount, fail) {\n\tif (!Number.isSafeInteger(amount) || amount <= 0)\n\t\tthrow new Error('Positive integer amount required');\n\tdb.exec('BEGIN IMMEDIATE');\n\ttry {\n\t\tdb.prepare('UPDATE accounts SET balance = balance - ? WHERE id = ?').run(amount, 'a');\n\t\tif (fail)\n\t\t\tthrow new Error('Simulated failure');\n\t\tdb.prepare('UPDATE accounts SET balance = balance + ? WHERE id = ?').run(amount, 'b');\n\t\tdb.exec('COMMIT');\n\t} catch (e) {\n\t\tdb.exec('ROLLBACK');\n\t\tthrow e;\n\t}\n};\n// Reopen the actual file to verify persisted values on a second connection.\nMAIN.sqlSnapshot = function() {\n\tvar reader = new DatabaseSync(PATH.root('ledger.sqlite'), { readOnly: true });\n\ttry { return reader.prepare('SELECT id,balance FROM accounts ORDER BY id').all(); }\n\tfinally { reader.close(); }\n};\n```\n\n```js controllers/sqlite.js\nexports.install = function() {\n\tROUTE('GET /sql/', exercise);\n};\nasync function exercise($) {\n\ttry {\n\t\tvar out = {};\n\t\tout.initial = await DB().find('sqlite/accounts').fields('id,balance').promise();\n\t\tMAIN.transfer(10, false);\n\t\tout.committed = MAIN.sqlSnapshot();\n\t\ttry { MAIN.transfer(20, true); } catch (e) { out.rollback = e.message; }\n\t\tout.afterRollback = MAIN.sqlSnapshot();\n\t\ttry { MAIN.transfer(999, false); } catch (e) { out.constraint = e.message; }\n\t\tout.afterConstraint = MAIN.sqlSnapshot();\n\t\tvar name = \"x'); DROP TABLE accounts; --\";\n\t\tawait DB().insert('sqlite/accounts', { id: 'c', name: name, balance: 25 }).promise();\n\t\tout.literal = await DB().read('sqlite/accounts').where('name', name).promise();\n\t\tout.updated = await DB().update('sqlite/accounts', { balance: 30 }).id('c').promise();\n\t\tout.page = await DB().list('sqlite/accounts').sort('balance', true).page(2, 1).promise();\n\t\ttry { await DB().insert('sqlite/accounts', { id: 'd', name: 'Alice', balance: 1 }).promise(); } catch (e) { out.duplicate = e.message; }\n\t\ttry { await DB().find('sqlite/accounts').fields('password').promise(); } catch (e) { out.column = e.message; }\n\t\ttry { await DB().remove('sqlite/accounts').promise(); } catch (e) { out.unsafe = e.message; }\n\t\tout.removed = await DB().remove('sqlite/accounts').id('c').promise();\n\t\tout.missing = await DB().read('sqlite/accounts').id('c').promise();\n\t\tout.count = await DB().count('sqlite/accounts').promise();\n\t\t$.json(out);\n\t} catch (e) { $.invalid(e); }\n}\n```"}],"knowledge":{"content_sha256":"0d9c3083d0b11d65b113fcf1df348e839a572228f30f311ffd0b6c58ffdd5cd4","source_sha256":"e247f2cec591f64a01a41cdaf4aa5b5a82d37afc8d8c5cc0db464e2afe4289df","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["AND","API","BEGIN","CHECK","COMMIT","COUNT","CREATE","CRUD","DELETE","DROP","FROM","GET","HTTP","IMMEDIATE","INSERT","INTEGER","INTO","KEY","LIMIT","MAIN","NEWDB","NOT","NULL","Number.isInteger","Number.isSafeInteger","OFFSET","ORDER","PATH","PRIMARY","ROLLBACK","ROUTE","SELECT","SET","SQL","STRICT","TABLE","TEXT","Total.js","UNIQUE","UPDATE","VALUES","WHERE"],"language":"en"},"files":[{"path":"definitions/sqlite.js","language":"js","content":"var DatabaseSync = require('node:sqlite').DatabaseSync;\nvar db = new DatabaseSync(PATH.root('ledger.sqlite'));\ndb.exec('CREATE TABLE accounts (id TEXT PRIMARY KEY, name TEXT NOT NULL UNIQUE, balance INTEGER NOT NULL CHECK(balance >= 0)) STRICT');\ndb.prepare('INSERT INTO accounts VALUES (?, ?, ?)').run('a', 'Alice', 100);\ndb.prepare('INSERT INTO accounts VALUES (?, ?, ?)').run('b', 'Bob', 50);\n\nfunction column(name) {\n\tif (!['id', 'name', 'balance'].includes(name))\n\t\tthrow new Error('Column not allowed');\n\treturn '\"' + name + '\"';\n}\nfunction where(options, params) {\n\tvar filters = options.filter.map(function(f) {\n\t\tif (f.type !== 'where' || f.comparer !== '=')\n\t\t\tthrow new Error('Filter not supported');\n\t\tparams.push(f.value);\n\t\treturn column(f.name) + ' = ?';\n\t});\n\treturn filters.length ? ' WHERE ' + filters.join(' AND ') : '';\n}\nfunction integer(value, fallback, max) {\n\tif (value == null)\n\t\treturn fallback;\n\tif (!Number.isInteger(value) || value < 0 || value > max)\n\t\tthrow new Error('Invalid pagination');\n\treturn value;\n}\nNEWDB('sqlite', function(options, callback) {\n\ttry {\n\t\tif (options.table !== 'accounts')\n\t\t\tthrow new Error('Table not allowed');\n\t\tvar params = [];\n\t\tvar condition = where(options, params);\n\t\tvar result;\n\t\tif (['find','read','list','count'].includes(options.exec)) {\n\t\t\tvar count = db.prepare('SELECT COUNT(*) AS value FROM accounts' + condition).get(...params).value;\n\t\t\tif (options.exec === 'count')\n\t\t\t\tresult = count;\n\t\t\telse {\n\t\t\t\tvar fields = (options.fields || ['id','name','balance']).map(column).join(',');\n\t\t\t\tvar sort = (options.sort || ['id_asc']).map(function(s) {\n\t\t\t\t\tvar match = /^(id|name|balance)_(asc|desc)$/.exec(s);\n\t\t\t\t\tif (!match)\n\t\t\t\t\t\tthrow new Error('Sort not allowed');\n\t\t\t\t\treturn column(match[1]) + ' ' + match[2];\n\t\t\t\t}).join(',');\n\t\t\t\tvar take = integer(options.take, 100, 100);\n\t\t\t\tvar skip = integer(options.skip, 0, 100000);\n\t\t\t\tvar rows = db.prepare('SELECT ' + fields + ' FROM accounts' + condition + ' ORDER BY ' + sort + ' LIMIT ? OFFSET ?').all(...params, take, skip);\n\t\t\t\tresult = options.exec === 'list' ? { items: rows, count: count } : rows;\n\t\t\t}\n\t\t} else if (options.exec === 'insert') {\n\t\t\tvar p = options.payload;\n\t\t\tdb.prepare('INSERT INTO accounts (id,name,balance) VALUES (?,?,?)').run(p.id,p.name,p.balance);\n\t\t\tresult = { id: p.id };\n\t\t} else if (options.exec === 'update') {\n\t\t\tif (!condition)\n\t\t\t\tthrow new Error('Filter required');\n\t\t\tvar keys = Object.keys(options.payload);\n\t\t\tif (!keys.length)\n\t\t\t\tthrow new Error('Empty update');\n\t\t\tvar set = keys.map(key => column(key) + ' = ?').join(',');\n\t\t\tresult = db.prepare('UPDATE accounts SET ' + set + condition).run(...keys.map(key => options.payload[key]), ...params).changes;\n\t\t} else if (options.exec === 'remove') {\n\t\t\tif (!condition)\n\t\t\t\tthrow new Error('Filter required');\n\t\t\tresult = db.prepare('DELETE FROM accounts' + condition).run(...params).changes;\n\t\t} else\n\t\t\tthrow new Error('Operation not supported');\n\t\tcallback(null, result);\n\t} catch (e) { callback(e); }\n});\n\nMAIN.transfer = function(amount, fail) {\n\tif (!Number.isSafeInteger(amount) || amount <= 0)\n\t\tthrow new Error('Positive integer amount required');\n\tdb.exec('BEGIN IMMEDIATE');\n\ttry {\n\t\tdb.prepare('UPDATE accounts SET balance = balance - ? WHERE id = ?').run(amount, 'a');\n\t\tif (fail)\n\t\t\tthrow new Error('Simulated failure');\n\t\tdb.prepare('UPDATE accounts SET balance = balance + ? WHERE id = ?').run(amount, 'b');\n\t\tdb.exec('COMMIT');\n\t} catch (e) {\n\t\tdb.exec('ROLLBACK');\n\t\tthrow e;\n\t}\n};\n// Reopen the actual file to verify persisted values on a second connection.\nMAIN.sqlSnapshot = function() {\n\tvar reader = new DatabaseSync(PATH.root('ledger.sqlite'), { readOnly: true });\n\ttry { return reader.prepare('SELECT id,balance FROM accounts ORDER BY id').all(); }\n\tfinally { reader.close(); }\n};"},{"path":"controllers/sqlite.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /sql/', exercise);\n};\nasync function exercise($) {\n\ttry {\n\t\tvar out = {};\n\t\tout.initial = await DB().find('sqlite/accounts').fields('id,balance').promise();\n\t\tMAIN.transfer(10, false);\n\t\tout.committed = MAIN.sqlSnapshot();\n\t\ttry { MAIN.transfer(20, true); } catch (e) { out.rollback = e.message; }\n\t\tout.afterRollback = MAIN.sqlSnapshot();\n\t\ttry { MAIN.transfer(999, false); } catch (e) { out.constraint = e.message; }\n\t\tout.afterConstraint = MAIN.sqlSnapshot();\n\t\tvar name = \"x'); DROP TABLE accounts; --\";\n\t\tawait DB().insert('sqlite/accounts', { id: 'c', name: name, balance: 25 }).promise();\n\t\tout.literal = await DB().read('sqlite/accounts').where('name', name).promise();\n\t\tout.updated = await DB().update('sqlite/accounts', { balance: 30 }).id('c').promise();\n\t\tout.page = await DB().list('sqlite/accounts').sort('balance', true).page(2, 1).promise();\n\t\ttry { await DB().insert('sqlite/accounts', { id: 'd', name: 'Alice', balance: 1 }).promise(); } catch (e) { out.duplicate = e.message; }\n\t\ttry { await DB().find('sqlite/accounts').fields('password').promise(); } catch (e) { out.column = e.message; }\n\t\ttry { await DB().remove('sqlite/accounts').promise(); } catch (e) { out.unsafe = e.message; }\n\t\tout.removed = await DB().remove('sqlite/accounts').id('c').promise();\n\t\tout.missing = await DB().read('sqlite/accounts').id('c').promise();\n\t\tout.count = await DB().count('sqlite/accounts').promise();\n\t\t$.json(out);\n\t} catch (e) { $.invalid(e); }\n}"}],"test":"const r = await get('/sql/');\nassert(r.status === 200 && r.json, 'SQLite exercise: ' + r.body);\nconst s = r.json;\nassert(s.initial[0].balance === 100 && s.initial[1].balance === 50, 'initial SQL rows');\nassert(s.committed[0].balance === 90 && s.committed[1].balance === 60, 'committed transfer persisted on second connection');\nassert(s.rollback === 'Simulated failure' && JSON.stringify(s.afterRollback) === JSON.stringify(s.committed), 'rollback undoes first write');\nassert(s.constraint.includes('CHECK') && JSON.stringify(s.afterConstraint) === JSON.stringify(s.committed), 'SQL constraint and rollback');\nassert(s.literal.name === \"x'); DROP TABLE accounts; --\" && s.literal.id === 'c', 'parameter binding keeps SQL-looking value literal');\nassert(s.updated === 1 && s.page.items[0].id === 'b' && s.page.count === 3 && s.page.page === 2 && s.page.pages === 3, 'update, sort and real SQL list pagination');\nassert(s.duplicate.includes('UNIQUE') && s.column === 'Column not allowed' && s.unsafe === 'Filter required', 'constraint, column allowlist and unfiltered delete refusal');\nassert(s.removed === 1 && s.missing === null && s.count === 2, 'delete, absent row and count');","test_kind":"http"}
{"id":"total5/load-and-npminstall","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/load-and-npminstall/","markdown_url":"https://forge.totaljs.es/en/total5/load-and-npminstall.md","tags":["total5","reference"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"6ec4153b4c071b6221c2386744678e5bccd2a939f5325a7eff7bd4b2bdae1946","checked_at":"2026-10-20T12:18:20.575Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Load application resources and avoid runtime package surprises","description":"Run LOAD in a separate process and verify NPMINSTALL reuse of an already available package without network access.","messages":[{"role":"user","content":"Load application resources and avoid runtime package surprises\n\nRun LOAD in a separate process and verify NPMINSTALL reuse of an already available package without network access."},{"role":"assistant","content":"`LOAD(types, [callback], [clear=true])` is the application loader. `types` is an array or comma-separated list of resource groups (such as `config`, `definitions`, `controllers`, `actions`, `modules`, `resources`, `jsonschemas`, `flowstreams`). With no list it loads the normal application groups. It extracts bundles first and clears temporary files by default. Use it in a standalone script when you need application definitions without starting an HTTP listener; do not repeatedly call it in a request handler.\n\n`NPMINSTALL(name, [callback])` checks the package under `CONF.$nodemodules`: an already-installed matching version returns without running npm. Otherwise it invokes `npm install` there. The name must be a trusted fixed application value, never request input. Deploy dependencies through a pinned package manifest/lockfile when reproducibility matters. The test below covers only the installed-package branch: it does not download, upgrade or certify any third-party package.\n\n```js controllers/loading.js\nexports.install = function() {\n\tROUTE('GET /loader-contract/', function($) { $.json({ available: typeof LOAD === 'function' && typeof NPMINSTALL === 'function' }); });\n};\n```"}],"knowledge":{"content_sha256":"b0349301366b0df42a87c482e3d35d66cf8cc6fa1505cc377a34aea6d934523f","source_sha256":"6ec4153b4c071b6221c2386744678e5bccd2a939f5325a7eff7bd4b2bdae1946","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["CONF","GET","HTTP","LOAD","NPMINSTALL","ROUTE"],"language":"en"},"files":[{"path":"controllers/loading.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /loader-contract/', function($) { $.json({ available: typeof LOAD === 'function' && typeof NPMINSTALL === 'function' }); });\n};"}],"test":"const fs = process.getBuiltinModule('fs');\nconst path = process.getBuiltinModule('path');\nconst os = process.getBuiltinModule('os');\nconst cp = process.getBuiltinModule('child_process');\nconst dir = fs.mkdtempSync(path.join(os.tmpdir(), 'forge-loader-'));\ntry {\n\tfs.mkdirSync(path.join(dir,'definitions'));\n\tfs.mkdirSync(path.join(dir,'packages','fixture-package'), { recursive:true });\n\tfs.writeFileSync(path.join(dir,'definitions','fixture.js'), 'MAIN.loaded = 42;');\n\tfs.writeFileSync(path.join(dir,'packages','fixture-package','package.json'), JSON.stringify({ name:'fixture-package',version:'1.2.3' }));\n\tconst total = path.join(process.cwd(),'node_modules','total5');\n\tconst script = 'require(' + JSON.stringify(total) + '); (async function(){ await LOAD(\"definitions\", null, false); CONF.$nodemodules=' + JSON.stringify(path.join(dir,'packages')) + '; F.Child.exec=function(){ throw new Error(\"Network/npm branch forbidden by fixture\"); }; await NPMINSTALL(\"fixture-package@1.2.3\"); console.log(\"LOADER_RESULT=\"+MAIN.loaded); process.exit(0); })().catch(e=>{console.error(e);process.exit(1);});';\n\tfs.writeFileSync(path.join(dir,'run.cjs'),script);\n\tconst result = cp.spawnSync(process.execPath,[path.join(dir,'run.cjs')],{cwd:dir,encoding:'utf8',timeout:5000});\n\tassert(result.status === 0 && result.stdout.includes('LOADER_RESULT=42'), 'isolated LOAD and installed NPMINSTALL branch: ' + result.stdout + result.stderr);\n} finally { fs.rmSync(dir,{recursive:true,force:true}); }\nassert((await get('/loader-contract/')).json.available === true, 'public loader globals available');","test_kind":"http"}
{"id":"total5/ldap-openldap","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/ldap-openldap/","markdown_url":"https://forge.totaljs.es/en/total5/ldap-openldap.md","tags":["total5","reference"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"959c2c7fbc5f770669d666469517001ebbc9cf3a55ab2a3d363af1277a6bc116","checked_at":"2026-10-20T12:18:19.806Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Query a real local LDAP directory","description":"Query an isolated OpenLDAP instance and verify the bind result in total5 0.0.19.","messages":[{"role":"user","content":"Query a real local LDAP directory\n\nQuery an isolated OpenLDAP instance and verify the bind result in total5 0.0.19."},{"role":"assistant","content":"`LDAP(options, callback)` is the native read client. `options.ldap` supplies `host` and `port`; `user` is the bind DN, `password` its password, `dn` the search base, and `type` the requested object class (`person` or `group`). `type:'profile'` uses `login` to search `sAMAccountName` and returns one object or `null`. `noauth:true` skips bind; it does not grant directory permissions. Always specify a search base explicitly.\n\n`attributes` maps returned attribute names to conversion types (`string`, `number`, `date`, `buffer`, `hex`, `base64`, etc.); it is **not** an allowlist of attributes to retrieve. A directory may return scalar values or arrays when an attribute repeats. Restrict the data your HTTP API exposes.\n\nThis executable fixture requires local **OpenLDAP** tools: `slapd`, `ldapadd` and the core/cosine/inetorgperson schema files. On macOS the defaults below use the system tools; other installations can set `FORGE_SLAPD`, `FORGE_LDAPADD` and `FORGE_LDAP_SCHEMA`. It runs a separate unprivileged loopback server and temporary LDIF database, then terminates it. It never uses an existing directory. The test covers search, multiple attribute values and an invalid-bind boundary; it does not certify Active Directory compatibility, TLS/StartTLS, groups or reconnect behavior. Never send real credentials over an unprotected network.\n\n**Decide authentication from the bind.** Check the result of the bind in your own code, and treat a returned profile as authenticated only after the bind has succeeded.\n\n```js controllers/directory.js\nexports.install = function() {\n\tROUTE('POST /directory-fixture/ @json', directory);\n};\nfunction directory($) {\n\tvar port = Number($.body.port);\n\tif (!Number.isInteger(port) || port < 1024 || port > 65535)\n\t\treturn $.invalid(400);\n\tLDAP({\n\t\tldap: { host: '127.0.0.1', port: port },\n\t\tuser: 'cn=admin,dc=forge,dc=test',\n\t\tpassword: $.body.wrong ? 'incorrect' : 'local-fixture-only',\n\t\tdn: 'dc=forge,dc=test',\n\t\ttype: 'person',\n\t\tattributes: { cn: 'string', mail: 'string' }\n\t}, function(err, people) {\n\t\tif (err)\n\t\t\t$.json({ failed: true });\n\t\telse\n\t\t\t$.json({ people: people.map(person => ({ cn: person.cn, mail: person.mail })) });\n\t});\n}\n```"}],"knowledge":{"content_sha256":"acf740f174412263663ffdc967c13b624ab592c51d4cde00e0cd491fb1103357","source_sha256":"959c2c7fbc5f770669d666469517001ebbc9cf3a55ab2a3d363af1277a6bc116","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["API","FORGE_LDAPADD","FORGE_LDAP_SCHEMA","FORGE_SLAPD","HTTP","LDAP","LDIF","Number.isInteger","POST","ROUTE","TLS"],"language":"en"},"files":[{"path":"controllers/directory.js","language":"js","content":"exports.install = function() {\n\tROUTE('POST /directory-fixture/ @json', directory);\n};\nfunction directory($) {\n\tvar port = Number($.body.port);\n\tif (!Number.isInteger(port) || port < 1024 || port > 65535)\n\t\treturn $.invalid(400);\n\tLDAP({\n\t\tldap: { host: '127.0.0.1', port: port },\n\t\tuser: 'cn=admin,dc=forge,dc=test',\n\t\tpassword: $.body.wrong ? 'incorrect' : 'local-fixture-only',\n\t\tdn: 'dc=forge,dc=test',\n\t\ttype: 'person',\n\t\tattributes: { cn: 'string', mail: 'string' }\n\t}, function(err, people) {\n\t\tif (err)\n\t\t\t$.json({ failed: true });\n\t\telse\n\t\t\t$.json({ people: people.map(person => ({ cn: person.cn, mail: person.mail })) });\n\t});\n}"}],"test":"const fs = process.getBuiltinModule('fs');\nconst path = process.getBuiltinModule('path');\nconst os = process.getBuiltinModule('os');\nconst net = process.getBuiltinModule('net');\nconst cp = process.getBuiltinModule('child_process');\nconst dir = fs.mkdtempSync(path.join(os.tmpdir(), 'forge-ldap-'));\nconst slapd = process.env.FORGE_SLAPD || '/usr/libexec/slapd';\nconst ldapadd = process.env.FORGE_LDAPADD || '/usr/bin/ldapadd';\nconst schemas = process.env.FORGE_LDAP_SCHEMA || '/etc/openldap/schema';\nlet server;\ntry {\n\tfor (const name of [slapd, ldapadd, path.join(schemas, 'inetorgperson.schema')])\n\t\tif (!fs.existsSync(name)) throw new Error('OpenLDAP prerequisite missing: ' + name);\n\tconst probe = net.createServer();\n\tawait new Promise(resolve => probe.listen(0, '127.0.0.1', resolve));\n\tconst port = probe.address().port;\n\tawait new Promise(resolve => probe.close(resolve));\n\tfs.mkdirSync(path.join(dir, 'db'));\n\tconst conf = ['include ' + path.join(schemas,'core.schema'), 'include ' + path.join(schemas,'cosine.schema'), 'include ' + path.join(schemas,'inetorgperson.schema'), 'pidfile ' + path.join(dir,'pid'), 'argsfile ' + path.join(dir,'args'), 'database ldif', 'suffix \"dc=forge,dc=test\"', 'rootdn \"cn=admin,dc=forge,dc=test\"', 'rootpw {SSHA}uiutQOk0MKPmVsqJcVy6EmB+4HJmb3JnZTEyMw==', 'directory ' + path.join(dir,'db'), 'access to * by * read'].join('\\n') + '\\n';\n\tfs.writeFileSync(path.join(dir,'slapd.conf'), conf);\n\tserver = cp.spawn(slapd, ['-f',path.join(dir,'slapd.conf'),'-h','ldap://127.0.0.1:' + port,'-d','0'], { stdio: 'ignore' });\n\tlet ready = false;\n\tfor (let i = 0; i < 40 && !ready; i++) {\n\t\tready = await new Promise(resolve => { const s = net.connect(port,'127.0.0.1'); s.once('connect', () => { s.destroy(); resolve(true); }); s.once('error', () => resolve(false)); });\n\t\tif (!ready) await new Promise(resolve => setTimeout(resolve,50));\n\t}\n\tif (!ready) throw new Error('Local slapd did not start');\n\tconst ldif = 'dn: dc=forge,dc=test\\nobjectClass: domain\\ndc: forge\\n\\ndn: uid=alice,dc=forge,dc=test\\nobjectClass: inetOrgPerson\\nuid: alice\\ncn: Alice Example\\nsn: Example\\nmail: alice@example.test\\nmail: second@example.test\\n\\n';\n\tconst added = cp.spawnSync(ldapadd, ['-x','-H','ldap://127.0.0.1:' + port,'-D','cn=admin,dc=forge,dc=test','-w','local-fixture-only'], { input: ldif, encoding:'utf8' });\n\tassert(added.status === 0, 'seed isolated LDAP: ' + added.stderr);\n\tconst ok = await post('/directory-fixture/', { port });\n\tassert(ok.status === 200 && ok.json.people && ok.json.people.length === 1, 'native LDAP reads person: ' + ok.body);\n\tassert(ok.json.people[0].cn === 'Alice Example' && ok.json.people[0].mail.length === 2, 'text conversion and repeated attributes');\n\tconst bad = await post('/directory-fixture/', { port, wrong: true });\n\tassert(bad.json.people && bad.json.people.length === 1, '0.0.19 ignores status-only bind failure and continues anonymous search: ' + bad.body);\n\tconst rejected = cp.spawnSync(ldapadd, ['-x','-H','ldap://127.0.0.1:' + port,'-D','cn=admin,dc=forge,dc=test','-w','incorrect'], { input: '', encoding: 'utf8' });\n\tassert(rejected.status === 49, 'independent server confirmation: credentials rejected with LDAP status 49');\n} finally {\n\tif (server && server.exitCode === null) {\n\t\tconst stopped = new Promise(resolve => server.once('exit',resolve));\n\t\tserver.kill('SIGTERM');\n\t\tawait stopped;\n\t}\n\tfs.rmSync(dir, { recursive:true, force:true });\n}","test_kind":"http"}
{"id":"total5/application-bundles","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/application-bundles/","markdown_url":"https://forge.totaljs.es/en/total5/application-bundles.md","tags":["total5","reference"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"0168aef240d4ed7c683cd0b840a686efcb4af22ffdec9b543d8265dd7b091f94","checked_at":"2026-10-20T12:17:47.386Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Package application files as a Total.js bundle","description":"Create a local bundle, load its definitions through the framework startup loader and verify extraction into .src.","messages":[{"role":"user","content":"Package application files as a Total.js bundle\n\nCreate a local bundle, load its definitions through the framework startup loader and verify extraction into .src."},{"role":"assistant","content":"A Total.js application bundle is an archive created with `BACKUP()`, placed in the application's `bundles/` directory with the `.bundle` suffix. At startup `LOAD()` extracts bundles and combines application files into `.src/`; the framework then uses that directory as its application root. Bundle filenames containing `-bk.bundle` or `_bk.bundle` are ignored. A `bundles.debug` file reuses the already extracted `.src` directory.\n\nThe source also supports `.url` files that fetch remote bundle archives. Bundle files contain executable application code: load trusted archives only. Keep credentials, runtime databases and private files out of distributable archives. `BACKUP` is packaging, not encryption.\n\n```js controllers/bundles.js\nexports.install = function() {\n\tROUTE('GET /bundle-loader/', $ => $.json({ load: typeof LOAD, backup: typeof BACKUP }));\n};\n```"}],"knowledge":{"content_sha256":"0d5c2183cc37e702073e256ef69943b26b22add5405e25c67915140353b0a76c","source_sha256":"0168aef240d4ed7c683cd0b840a686efcb4af22ffdec9b543d8265dd7b091f94","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["BACKUP","GET","LOAD","ROUTE","Total.js"],"language":"en"},"files":[{"path":"controllers/bundles.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /bundle-loader/', $ => $.json({ load: typeof LOAD, backup: typeof BACKUP }));\n};"}],"test":"const fs = process.getBuiltinModule('fs');\nconst path = process.getBuiltinModule('path');\nconst os = process.getBuiltinModule('os');\nconst cp = process.getBuiltinModule('child_process');\nconst dir = fs.mkdtempSync(path.join(os.tmpdir(),'forge-bundle-'));\ntry {\n\tconst app = path.join(dir,'app');\n\tconst source = path.join(dir,'source');\n\tfs.mkdirSync(path.join(source,'definitions'), {recursive:true});\n\tfs.mkdirSync(path.join(app,'bundles'), {recursive:true});\n\tfs.writeFileSync(path.join(source,'definitions','bundled.js'),'MAIN.bundleValue = \"from-bundle\";');\n\tconst total = path.join(process.cwd(),'node_modules','total5');\n\tconst script = 'require(' + JSON.stringify(total) + '); (async function(){ await BACKUP(PATH.root(\"bundles/demo.bundle\"),' + JSON.stringify(source) + '); F.Fs.writeFileSync(PATH.root(\"bundles/ignored-bk.bundle\"),\"not an archive\"); await LOAD(\"definitions\",null,false); console.log(\"BUNDLE_RESULT=\"+JSON.stringify({value:MAIN.bundleValue,bundle:F.isBundle,root:F.directory})); process.exit(0); })().catch(e=>{console.error(e);process.exit(1);});';\n\tfs.writeFileSync(path.join(app,'run.cjs'),script);\n\tconst result = cp.spawnSync(process.execPath,[path.join(app,'run.cjs')],{cwd:app,encoding:'utf8',timeout:10000});\n\tassert(result.status === 0 && result.stdout.includes('\"value\":\"from-bundle\"') && result.stdout.includes('\"bundle\":true'), 'actual archive load: ' + result.stdout + result.stderr);\n\tassert(fs.existsSync(path.join(app,'.src','definitions','bundled.js')), 'bundle extracted into .src');\n} finally { fs.rmSync(dir,{recursive:true,force:true}); }\nassert((await get('/bundle-loader/')).json.backup === 'function', 'native packaging APIs');","test_kind":"http"}
{"id":"total5/upload-lifecycle-and-href","section":"total5","kind":"recipe","framework":"Total.js 5","framework_version":"total5@0.0.19","language":"en","url":"https://forge.totaljs.es/en/total5/upload-lifecycle-and-href/","markdown_url":"https://forge.totaljs.es/en/total5/upload-lifecycle-and-href.md","tags":["total5","reference"],"license":"CC-BY-4.0","verified":true,"verification":"executed against total5@0.0.19","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"233bcd2527b9b56c5dbbbed68600373379537434b780aab780c2bd5618d9a0a3","checked_at":"2026-10-20T12:18:58.077Z","framework":"total5@0.0.19","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Own uploaded-file cleanup and build query links","description":"Verify default upload cleanup, autoclear(false), explicit clear and href query replacement.","messages":[{"role":"user","content":"Own uploaded-file cleanup and build query links\n\nVerify default upload cleanup, autoclear(false), explicit clear and href query replacement."},{"role":"assistant","content":"Uploaded temporary files normally disappear when the request is released. `$.autoclear(false)` keeps them for work that outlives the response: your application must then move or remove them. `$.clear()` schedules removal of removable uploads and empties `$.files`; disk deletion is asynchronous. Never store arbitrary paths supplied by a client for later deletion.\n\nIn 0.0.19 build query links with the public `QUERIFY()` helper and a copy of the query, as this recipe shows. It produces a query string, not a redirect or an absolute URL.\n\nThe retained-file map and cleanup endpoints below are a local fixture; an application should use scoped ownership and durable storage rather than expose these endpoints.\n\n```js controllers/lifecycle.js\nvar paths = Object.create(null);\nexports.install = function() {\n\tROUTE('POST /upload/{mode}/ @upload <1mb', function($) {\n\t\tvar file = $.files[0];\n\t\tif (!file) return $.invalid(400);\n\t\tvar id = UID();\n\t\tpaths[id] = file.path;\n\t\tif ($.params.mode === 'retain') $.autoclear(false);\n\t\tif ($.params.mode === 'clear') $.clear();\n\t\t$.json({ id: id, files: $.files.length });\n\t});\n\tROUTE('GET /exists/{id}/', $ => $.json({ exists: !!paths[$.params.id] && F.Fs.existsSync(paths[$.params.id]) }));\n\tROUTE('POST /cleanup/{id}/', function($) {\n\t\tvar path = paths[$.params.id];\n\t\tif (path) { F.Fs.rmSync(path, { force: true }); delete paths[$.params.id]; }\n\t\t$.success();\n\t});\n\tROUTE('GET /link/', function($) {\n\t\tvar broken = false;\n\t\ttry { $.href('page', '2'); } catch (e) { broken = e instanceof TypeError; }\n\t\t$.json({ broken: broken, link: QUERIFY('', Object.assign({}, $.query, { page: '2 & more' })) });\n\t});\n};\n```"}],"knowledge":{"content_sha256":"5d7d9072b52f3dcc841f23b413c213e6a47f83eae4a521c8252f4f81d7f3f3c9","source_sha256":"233bcd2527b9b56c5dbbbed68600373379537434b780aab780c2bd5618d9a0a3","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"total5-installed","url":"https://registry.npmjs.org/total5/-/total5-0.0.19.tgz","revision":null,"version":"total5@0.0.19","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"7b43194a112c7ad00582e1cf57e6f8d28ef651b0d16ca09faa4e6d45459d02ff","license_files":[{"path":"LICENSE","sha256":"32be66ff50dc625b647118e326607c6916fa9615330bcc922caedf834b78627c","text":true,"characters":10265}],"scope":"Runtime source"}],"review":"not-recorded","symbols":["GET","POST","QUERIFY","ROUTE","UID","URL"],"language":"en"},"files":[{"path":"controllers/lifecycle.js","language":"js","content":"var paths = Object.create(null);\nexports.install = function() {\n\tROUTE('POST /upload/{mode}/ @upload <1mb', function($) {\n\t\tvar file = $.files[0];\n\t\tif (!file) return $.invalid(400);\n\t\tvar id = UID();\n\t\tpaths[id] = file.path;\n\t\tif ($.params.mode === 'retain') $.autoclear(false);\n\t\tif ($.params.mode === 'clear') $.clear();\n\t\t$.json({ id: id, files: $.files.length });\n\t});\n\tROUTE('GET /exists/{id}/', $ => $.json({ exists: !!paths[$.params.id] && F.Fs.existsSync(paths[$.params.id]) }));\n\tROUTE('POST /cleanup/{id}/', function($) {\n\t\tvar path = paths[$.params.id];\n\t\tif (path) { F.Fs.rmSync(path, { force: true }); delete paths[$.params.id]; }\n\t\t$.success();\n\t});\n\tROUTE('GET /link/', function($) {\n\t\tvar broken = false;\n\t\ttry { $.href('page', '2'); } catch (e) { broken = e instanceof TypeError; }\n\t\t$.json({ broken: broken, link: QUERIFY('', Object.assign({}, $.query, { page: '2 & more' })) });\n\t});\n};"}],"test":"for (const mode of ['default','retain','clear']) {\n\tconst form = new FormData();\n\tform.append('file',new Blob(['temporary'],{type:'text/plain'}),'demo.txt');\n\tconst uploaded = await post('/upload/' + mode + '/',form);\n\tassert(uploaded.status === 200, 'upload ' + mode + ': ' + uploaded.body);\n\tconst id = uploaded.json.id;\n\tlet exists;\n\tfor (let i=0;i<30;i++) {\n\t\texists = (await get('/exists/' + id + '/')).json.exists;\n\t\tif (exists === (mode === 'retain')) break;\n\t\tawait new Promise(resolve => setTimeout(resolve,20));\n\t}\n\tassert(exists === (mode === 'retain'), 'cleanup policy ' + mode);\n\tassert(uploaded.json.files === (mode === 'clear' ? 0 : 1), 'clear empties files');\n\tawait post('/cleanup/' + id + '/',{});\n\tassert((await get('/exists/' + id + '/')).json.exists === false, 'retained file explicitly removed');\n}\nconst linked = (await get('/link/?page=1&filter=active')).json;\nassert(linked.broken === true, 'controller href TypeError in 0.0.19');\nconst link = linked.link;\nconst params = new URLSearchParams(link.replace(/^\\?/,''));\nassert(params.get('page') === '2 & more' && params.get('filter') === 'active', 'QUERIFY replaces and encodes a parameter');","test_kind":"http"}
{"id":"jcomponent/getting-started","section":"jcomponent","kind":"recipe","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/jcomponent/getting-started/","markdown_url":"https://forge.totaljs.es/en/jcomponent/getting-started.md","tags":["jcomponent","frontend","binding"],"license":"CC-BY-4.0","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"1ae48376dcc63cb9de7b901bde95ab0e979216305b96820d2f5cb4fabf88d213","checked_at":"2026-10-20T12:19:25.508Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"browser","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Get started with jComponent 20","description":"Load the jComponent bundle, set a value in the global state and show it with a ui-bind element.","messages":[{"role":"user","content":"Get started with jComponent 20\n\nLoad the jComponent bundle, set a value in the global state and show it with a ui-bind element."},{"role":"assistant","content":"jComponent 20 is a single file (`spa.min@20.js`) that already contains jQuery. It keeps a global **state** object: you write to it with `SET(path, value)`, read from it with `GET(path)`, and elements react to changes. The bundle also has a stylesheet, `spa.min@20.css`, with the Total.js icon font. Load both from the CDN, as every example of this site does: `<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>` and `<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />`. If you only want the icons, load just `//cdn.componentator.com/icons.min.css` (no script is needed). The checks of this site answer the requests for `spa.min@20.js` and `spa.min@20.css` with a local copy of the same version and do not load the icons file.\n\n`<ui-bind>` binds an element to a path in the state. The `config` attribute lists commands separated by semicolons; `text:value` sets the text of the element to the value of the path.\n\n```html public/index.html\n<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\n\t<h1>Hello <ui-bind path=\"user.name\" config=\"text:value\"></ui-bind></h1>\n\n\t<script>\n\t\tSET('user.name', 'Peter');\n\t</script>\n\n</body>\n</html>\n```\n\nCall `SET('user.name', 'Louis')` again and the heading changes without any DOM code of yours."}],"knowledge":{"content_sha256":"d000a133f8599ffc3236793b3d02f382adbbf017f136adfafbc88bff4f39c05d","source_sha256":"1ae48376dcc63cb9de7b901bde95ab0e979216305b96820d2f5cb4fabf88d213","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"jcomponent20","url":"https://github.com/totaljs/jComponent","revision":"3fedd009a277f50fddc608b97afbec553b67c51b","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"01ce7c4d3dbd3d14b5a7ca9f44172ed332fa1eed250505811d4f828b38254bf9","license_files":[{"path":"LICENSE","sha256":"abf1260b51729c2fc0da7917e3564878725212bd7859683e7d5af91b6f9192bd","text":true,"characters":1095},{"path":"license.txt","sha256":"7226fe6e0be959f3c5e337bec8ca39ec08e694c8051dd7bc09d3e1a242abdc88","text":true,"characters":1099}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DOCTYPE","DOM","GET","SET","Total.js"],"language":"en"},"files":[{"path":"public/index.html","language":"html","content":"<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\n\t<h1>Hello <ui-bind path=\"user.name\" config=\"text:value\"></ui-bind></h1>\n\n\t<script>\n\t\tSET('user.name', 'Peter');\n\t</script>\n\n</body>\n</html>"}],"test":"const results = [];\nconst check = (ok, msg) => ok || results.push(msg);\nconst until = async (fn, ms = 4000) => { const t = Date.now(); while (Date.now() - t < ms) { try { if (fn()) return true; } catch (e) {} await new Promise(r => setTimeout(r, 50)); } return false; };\nawait until(() => $('ui-bind').text() === 'Peter');\ncheck(typeof SET === 'function' && typeof GET === 'function', 'SET and GET are global');\ncheck(GET('user.name') === 'Peter', 'GET returns the value');\ncheck($('ui-bind').text() === 'Peter', 'ui-bind shows the value, got: ' + $('ui-bind').text());\nSET('user.name', 'Louis');\nawait until(() => $('ui-bind').text() === 'Louis');\ncheck($('ui-bind').text() === 'Louis', 'ui-bind follows SET, got: ' + $('ui-bind').text());\nresults","test_kind":"browser"}
{"id":"jcomponent/state-api","section":"jcomponent","kind":"recipe","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/jcomponent/state-api/","markdown_url":"https://forge.totaljs.es/en/jcomponent/state-api.md","tags":["jcomponent","frontend","state"],"license":"CC-BY-4.0","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"bd4d48ecd7a4185523fe63983c6fd599693313ef8cfe1326ac16b2782c8020c3","checked_at":"2026-10-20T12:19:28.837Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"browser","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Read, change and watch the state","description":"Use SET, GET, UPD, PUSH and WATCH to work with the global state and react when a path changes.","messages":[{"role":"user","content":"Read, change and watch the state\n\nUse SET, GET, UPD, PUSH and WATCH to work with the global state and react when a path changes."},{"role":"assistant","content":"The state is one global object addressed by **paths** such as `cart.items`. The functions to work with it:\n\n- `SET(path, value)` writes a value and notifies everything bound to the path\n- `GET(path)` reads a value\n- `UPD(path)` notifies the path after you changed the object in place (`GET('cart').discount = 5; UPD('cart')`)\n- `PUSH(path, value)` appends to an array and notifies; with a `^` before the path it adds the value to the beginning\n- `WATCH(path, callback)` runs the callback with the new value every time the path changes; join several paths with `+`\n\n```html public/index.html\n<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<p><ui-bind path=\"cart.items\" config=\"text:value ? value.length + ' items' : 'empty'\"></ui-bind></p>\n\n\t<script>\n\n\t\tvar log = [];\n\n\t\tWATCH('cart.items', function(value) {\n\t\t\tlog.push(value.length);\n\t\t});\n\n\t\tSET('cart.items', []);\n\t\tPUSH('cart.items', 'book');\n\t\tPUSH('^cart.items', 'pen');\n\n\t\tGET('cart.items').push('lamp');\n\t\tUPD('cart.items');\n\n\t</script>\n\n</body>\n</html>\n```"}],"knowledge":{"content_sha256":"2211f1567f71e3f701549215ecc3a1e32c676105199a4d8d5a61a52708a78c4c","source_sha256":"bd4d48ecd7a4185523fe63983c6fd599693313ef8cfe1326ac16b2782c8020c3","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"jcomponent20","url":"https://github.com/totaljs/jComponent","revision":"3fedd009a277f50fddc608b97afbec553b67c51b","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"01ce7c4d3dbd3d14b5a7ca9f44172ed332fa1eed250505811d4f828b38254bf9","license_files":[{"path":"LICENSE","sha256":"abf1260b51729c2fc0da7917e3564878725212bd7859683e7d5af91b6f9192bd","text":true,"characters":1095},{"path":"license.txt","sha256":"7226fe6e0be959f3c5e337bec8ca39ec08e694c8051dd7bc09d3e1a242abdc88","text":true,"characters":1099}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["DOCTYPE","GET","PUSH","SET","UPD","WATCH"],"language":"en"},"files":[{"path":"public/index.html","language":"html","content":"<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<p><ui-bind path=\"cart.items\" config=\"text:value ? value.length + ' items' : 'empty'\"></ui-bind></p>\n\n\t<script>\n\n\t\tvar log = [];\n\n\t\tWATCH('cart.items', function(value) {\n\t\t\tlog.push(value.length);\n\t\t});\n\n\t\tSET('cart.items', []);\n\t\tPUSH('cart.items', 'book');\n\t\tPUSH('^cart.items', 'pen');\n\n\t\tGET('cart.items').push('lamp');\n\t\tUPD('cart.items');\n\n\t</script>\n\n</body>\n</html>"}],"test":"const results = [];\nconst check = (ok, msg) => ok || results.push(msg);\nconst until = async (fn, ms = 4000) => { const t = Date.now(); while (Date.now() - t < ms) { try { if (fn()) return true; } catch (e) {} await new Promise(r => setTimeout(r, 50)); } return false; };\nawait until(() => $('ui-bind').text() === '3 items');\ncheck(GET('cart.items').join() === 'pen,book,lamp', 'PUSH appends, ^PUSH prepends, got: ' + GET('cart.items'));\ncheck($('ui-bind').text() === '3 items', 'ui-bind shows the count, got: ' + $('ui-bind').text());\ncheck(log.join() === '0,1,2,3', 'WATCH ran for every change, got: ' + log.join());\nSET('cart.items', ['one']);\nawait until(() => log.length === 5);\ncheck(log[4] === 1, 'WATCH runs for SET too, got: ' + log.join());\nresults","test_kind":"browser"}
{"id":"jcomponent/custom-component","section":"jcomponent","kind":"recipe","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/jcomponent/custom-component/","markdown_url":"https://forge.totaljs.es/en/jcomponent/custom-component.md","tags":["jcomponent","frontend","components"],"license":"CC-BY-4.0","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"68087d3dbf800cb722f612e3aa8799338f8eda90482e686edeb44b433dab43fc","checked_at":"2026-10-20T12:19:18.274Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"browser","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":2},"title":"Write a custom jComponent","description":"Register a reusable component with COMPONENT(), render it in make(), react to state changes in setter() and write back with set().","messages":[{"role":"user","content":"Write a custom jComponent\n\nRegister a reusable component with COMPONENT(), render it in make(), react to state changes in setter() and write back with set()."},{"role":"assistant","content":"A component is declared with `COMPONENT(name, config, declaration)`. The second argument is the default configuration as a string (`key:value;key:value`). The declaration receives `self` (the component instance) and `config` (the parsed configuration).\n\n- `self.make` runs once, when the component is created. Build the DOM here.\n- `self.setter(value)` runs every time the value of the component's `path` changes.\n- `self.set(value)` writes a new value to the `path`; `self.get()` reads it.\n\nUse the component with a `<ui-component>` element. `name` selects the declaration, `path` the state and `config` overrides the defaults.\n\n```html public/index.html\n<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\n\t<ui-component name=\"counter\" path=\"cart.count\" config=\"step:2\"></ui-component>\n\t<p>Total: <ui-bind path=\"cart.count\" config=\"text:value\"></ui-bind></p>\n\n\t<script>\n\n\t\tCOMPONENT('counter', 'step:1', function(self, config) {\n\n\t\t\tself.make = function() {\n\t\t\t\tself.html('<button type=\"button\">Add</button> <b class=\"value\">0</b>');\n\t\t\t\tself.event('click', 'button', function() {\n\t\t\t\t\tself.set((self.get() || 0) + config.step);\n\t\t\t\t});\n\t\t\t};\n\n\t\t\tself.setter = function(value) {\n\t\t\t\tself.find('.value').text(value || 0);\n\t\t\t};\n\n\t\t});\n\n\t\tSET('cart.count', 10);\n\n\t</script>\n\n</body>\n</html>\n```\n\nThe `ui-bind` next to the component shows that both read the same state: clicking the button changes the path and every element bound to it updates."}],"knowledge":{"content_sha256":"e7cce28ba59dc0aeba03d3e4d38b42f387d4ab3717e831f55440d79e4914233b","source_sha256":"68087d3dbf800cb722f612e3aa8799338f8eda90482e686edeb44b433dab43fc","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"jcomponent20","url":"https://github.com/totaljs/jComponent","revision":"3fedd009a277f50fddc608b97afbec553b67c51b","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"01ce7c4d3dbd3d14b5a7ca9f44172ed332fa1eed250505811d4f828b38254bf9","license_files":[{"path":"LICENSE","sha256":"abf1260b51729c2fc0da7917e3564878725212bd7859683e7d5af91b6f9192bd","text":true,"characters":1095},{"path":"license.txt","sha256":"7226fe6e0be959f3c5e337bec8ca39ec08e694c8051dd7bc09d3e1a242abdc88","text":true,"characters":1099}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["COMPONENT","DOCTYPE","DOM","SET"],"language":"en"},"files":[{"path":"public/index.html","language":"html","content":"<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\n\t<ui-component name=\"counter\" path=\"cart.count\" config=\"step:2\"></ui-component>\n\t<p>Total: <ui-bind path=\"cart.count\" config=\"text:value\"></ui-bind></p>\n\n\t<script>\n\n\t\tCOMPONENT('counter', 'step:1', function(self, config) {\n\n\t\t\tself.make = function() {\n\t\t\t\tself.html('<button type=\"button\">Add</button> <b class=\"value\">0</b>');\n\t\t\t\tself.event('click', 'button', function() {\n\t\t\t\t\tself.set((self.get() || 0) + config.step);\n\t\t\t\t});\n\t\t\t};\n\n\t\t\tself.setter = function(value) {\n\t\t\t\tself.find('.value').text(value || 0);\n\t\t\t};\n\n\t\t});\n\n\t\tSET('cart.count', 10);\n\n\t</script>\n\n</body>\n</html>"}],"test":"const results = [];\nconst check = (ok, msg) => ok || results.push(msg);\nconst until = async (fn, ms = 4000) => { const t = Date.now(); while (Date.now() - t < ms) { try { if (fn()) return true; } catch (e) {} await new Promise(r => setTimeout(r, 50)); } return false; };\nconst value = () => $('ui-component[name=counter] .value').text();\nawait until(() => value() === '10');\ncheck(value() === '10', 'setter renders the initial value, got: ' + value());\n$('ui-component[name=counter] button').trigger('click');\nawait until(() => GET('cart.count') === 12 && value() === '12');\ncheck(GET('cart.count') === 12, 'click adds config.step (2), got: ' + GET('cart.count'));\ncheck(value() === '12', 'component re-renders after set()');\ncheck($('ui-bind').text() === '12', 'ui-bind follows the same path, got: ' + $('ui-bind').text());\nresults","test_kind":"browser"}
{"id":"jcomponent/component-config","section":"jcomponent","kind":"recipe","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/jcomponent/component-config/","markdown_url":"https://forge.totaljs.es/en/jcomponent/component-config.md","tags":["jcomponent","frontend","components"],"license":"CC-BY-4.0","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"8341a8c7c8a55953d75473e03fe57f11e76f88856b00b9685b7b15df9d480668","checked_at":"2026-10-20T12:19:11.918Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"browser","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Configure a component and reconfigure it later","description":"Give a component default options, override them in the config attribute, react in configure() and change them at runtime with reconfigure().","messages":[{"role":"user","content":"Configure a component and reconfigure it later\n\nGive a component default options, override them in the config attribute, react in configure() and change them at runtime with reconfigure()."},{"role":"assistant","content":"The second argument of `COMPONENT()` holds the default options. The `config` attribute of the element overrides them, and the declaration reads the merged values from `config`. Implement `self.configure(key, value)` to react when an option is set or changed. At runtime `self.reconfigure('key:value;key2:value2')` (or `CONFIG(selector, config)` for many components) changes options and calls `configure` for each changed key.\n\n```html public/index.html\n<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<ui-component name=\"badge\" config=\"label:Sale\"></ui-component>\n\t<ui-component name=\"badge\" id=\"second\"></ui-component>\n\n\t<script>\n\n\t\tCOMPONENT('badge', 'color:blue;label:New', function(self, config) {\n\n\t\t\tself.make = function() {\n\t\t\t\tself.html('<b class=\"label\"></b>');\n\t\t\t\tself.draw();\n\t\t\t};\n\n\t\t\tself.draw = function() {\n\t\t\t\tself.find('.label').text(config.label).css('color', config.color);\n\t\t\t};\n\n\t\t\tself.configure = function(key, value) {\n\t\t\t\tself.find('.label').length && self.draw();\n\t\t\t};\n\n\t\t});\n\n\t</script>\n\n</body>\n</html>\n```"}],"knowledge":{"content_sha256":"0029f1b2c2a136827eb199e9ed2afef07a7637807acf316edd4f61f7011cad76","source_sha256":"8341a8c7c8a55953d75473e03fe57f11e76f88856b00b9685b7b15df9d480668","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"jcomponent20","url":"https://github.com/totaljs/jComponent","revision":"3fedd009a277f50fddc608b97afbec553b67c51b","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"01ce7c4d3dbd3d14b5a7ca9f44172ed332fa1eed250505811d4f828b38254bf9","license_files":[{"path":"LICENSE","sha256":"abf1260b51729c2fc0da7917e3564878725212bd7859683e7d5af91b6f9192bd","text":true,"characters":1095},{"path":"license.txt","sha256":"7226fe6e0be959f3c5e337bec8ca39ec08e694c8051dd7bc09d3e1a242abdc88","text":true,"characters":1099}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["COMPONENT","CONFIG","DOCTYPE"],"language":"en"},"files":[{"path":"public/index.html","language":"html","content":"<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<ui-component name=\"badge\" config=\"label:Sale\"></ui-component>\n\t<ui-component name=\"badge\" id=\"second\"></ui-component>\n\n\t<script>\n\n\t\tCOMPONENT('badge', 'color:blue;label:New', function(self, config) {\n\n\t\t\tself.make = function() {\n\t\t\t\tself.html('<b class=\"label\"></b>');\n\t\t\t\tself.draw();\n\t\t\t};\n\n\t\t\tself.draw = function() {\n\t\t\t\tself.find('.label').text(config.label).css('color', config.color);\n\t\t\t};\n\n\t\t\tself.configure = function(key, value) {\n\t\t\t\tself.find('.label').length && self.draw();\n\t\t\t};\n\n\t\t});\n\n\t</script>\n\n</body>\n</html>"}],"test":"const results = [];\nconst check = (ok, msg) => ok || results.push(msg);\nconst until = async (fn, ms = 4000) => { const t = Date.now(); while (Date.now() - t < ms) { try { if (fn()) return true; } catch (e) {} await new Promise(r => setTimeout(r, 50)); } return false; };\nawait until(() => $('ui-component .label').length === 2);\nconst first = $('ui-component').eq(0).find('.label');\nconst second = $('#second .label');\ncheck(first.text() === 'Sale', 'the attribute overrides the default, got: ' + first.text());\ncheck(second.text() === 'New', 'a default is used when the attribute is missing, got: ' + second.text());\ncheck(second.css('color') === 'rgb(0, 0, 255)', 'the default color is applied, got: ' + second.css('color'));\nconst instance = $('#second')[0].$uicomponent;\ninstance.reconfigure('label:Hot;color:red');\nawait until(() => $('#second .label').text() === 'Hot');\ncheck($('#second .label').text() === 'Hot', 'reconfigure() changes the option and configure() redraws, got: ' + $('#second .label').text());\ncheck($('#second .label').css('color') === 'rgb(255, 0, 0)', 'the color changed too, got: ' + $('#second .label').css('color'));\nresults","test_kind":"browser"}
{"id":"jcomponent/component-lifecycle","section":"jcomponent","kind":"recipe","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/jcomponent/component-lifecycle/","markdown_url":"https://forge.totaljs.es/en/jcomponent/component-lifecycle.md","tags":["jcomponent","frontend","components"],"license":"CC-BY-4.0","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"bf16119da6e46cf8fdd5aade91dc7f9c77957ac9e3e3fd6d89f20f93910ee215","checked_at":"2026-10-20T12:19:16.633Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"browser","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Component lifecycle: make, setter and destroy","description":"Know when jComponent creates, updates and removes a component, and clean up timers and listeners in destroy().","messages":[{"role":"user","content":"Component lifecycle: make, setter and destroy\n\nKnow when jComponent creates, updates and removes a component, and clean up timers and listeners in destroy()."},{"role":"assistant","content":"A component goes through three moments you can hook into:\n\n- `self.make()` runs once, when the component is created and its element is ready: build the DOM and register events here\n- `self.setter(value, path, type)` runs every time the value of the `path` changes (also the first time)\n- `self.destroy()` runs after the element was removed from the page: stop timers and release what you created. jComponent detects removed elements with a cleanup that runs every minute; call `FREE()` to run it immediately\n\n`self.readonly()` tells jComponent the component never writes to its path, which turns off its validation state.\n\n```html public/index.html\n<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<div id=\"host\"><ui-component name=\"clock\" path=\"time.now\"></ui-component></div>\n\n\t<script>\n\n\t\tvar log = [];\n\n\t\tCOMPONENT('clock', function(self, config) {\n\n\t\t\tvar timer;\n\n\t\t\tself.readonly();\n\n\t\t\tself.make = function() {\n\t\t\t\tlog.push('make');\n\t\t\t\ttimer = setInterval(function() {}, 1000);\n\t\t\t};\n\n\t\t\tself.setter = function(value) {\n\t\t\t\tlog.push('setter:' + value);\n\t\t\t\tself.html('<span>' + value + '</span>');\n\t\t\t};\n\n\t\t\tself.destroy = function() {\n\t\t\t\tlog.push('destroy');\n\t\t\t\tclearInterval(timer);\n\t\t\t};\n\n\t\t});\n\n\t\tSET('time.now', '10:00');\n\n\t</script>\n\n</body>\n</html>\n```"}],"knowledge":{"content_sha256":"a91ae5a1c166759e760b643f70e1f3cfe3cd06a062d201b231e646ef7081f169","source_sha256":"bf16119da6e46cf8fdd5aade91dc7f9c77957ac9e3e3fd6d89f20f93910ee215","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"jcomponent20","url":"https://github.com/totaljs/jComponent","revision":"3fedd009a277f50fddc608b97afbec553b67c51b","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"01ce7c4d3dbd3d14b5a7ca9f44172ed332fa1eed250505811d4f828b38254bf9","license_files":[{"path":"LICENSE","sha256":"abf1260b51729c2fc0da7917e3564878725212bd7859683e7d5af91b6f9192bd","text":true,"characters":1095},{"path":"license.txt","sha256":"7226fe6e0be959f3c5e337bec8ca39ec08e694c8051dd7bc09d3e1a242abdc88","text":true,"characters":1099}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["COMPONENT","DOCTYPE","DOM","FREE","SET"],"language":"en"},"files":[{"path":"public/index.html","language":"html","content":"<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<div id=\"host\"><ui-component name=\"clock\" path=\"time.now\"></ui-component></div>\n\n\t<script>\n\n\t\tvar log = [];\n\n\t\tCOMPONENT('clock', function(self, config) {\n\n\t\t\tvar timer;\n\n\t\t\tself.readonly();\n\n\t\t\tself.make = function() {\n\t\t\t\tlog.push('make');\n\t\t\t\ttimer = setInterval(function() {}, 1000);\n\t\t\t};\n\n\t\t\tself.setter = function(value) {\n\t\t\t\tlog.push('setter:' + value);\n\t\t\t\tself.html('<span>' + value + '</span>');\n\t\t\t};\n\n\t\t\tself.destroy = function() {\n\t\t\t\tlog.push('destroy');\n\t\t\t\tclearInterval(timer);\n\t\t\t};\n\n\t\t});\n\n\t\tSET('time.now', '10:00');\n\n\t</script>\n\n</body>\n</html>"}],"test":"const results = [];\nconst check = (ok, msg) => ok || results.push(msg);\nconst until = async (fn, ms = 4000) => { const t = Date.now(); while (Date.now() - t < ms) { try { if (fn()) return true; } catch (e) {} await new Promise(r => setTimeout(r, 50)); } return false; };\nawait until(() => $('#host span').text() === '10:00');\ncheck(log[0] === 'make', 'make runs first, got: ' + log.join());\ncheck(log.includes('setter:10:00'), 'setter runs with the value, got: ' + log.join());\nSET('time.now', '10:01');\nawait until(() => $('#host span').text() === '10:01');\ncheck(log.includes('setter:10:01'), 'setter runs again after SET');\ncheck(log.filter(n => n === 'make').length === 1, 'make runs only once');\n$('#host').empty();\ncheck(!log.includes('destroy'), 'destroy does not run at the moment of removal');\nFREE();\nawait until(() => log.includes('destroy'));\ncheck(log.includes('destroy'), 'destroy runs when the cleanup finds the removed element, got: ' + log.join());\nresults","test_kind":"browser"}
{"id":"jcomponent/validation","section":"jcomponent","kind":"recipe","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/jcomponent/validation/","markdown_url":"https://forge.totaljs.es/en/jcomponent/validation.md","tags":["jcomponent","frontend","validation","forms"],"license":"CC-BY-4.0","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"ee46b047fedb7fce08524a92b2aac6d0e04d2e99acb9eb208d5016d1fd441c71","checked_at":"2026-10-20T12:19:42.697Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"browser","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Validate input with a component's validate()","description":"Implement validate() in a component, read the invalid, touched and modified states, and find invalid fields with ERRORS().","messages":[{"role":"user","content":"Validate input with a component's validate()\n\nImplement validate() in a component, read the invalid, touched and modified states, and find invalid fields with ERRORS()."},{"role":"assistant","content":"A component that edits a value can validate it. Define `self.validate(value)` and return `true` (or an empty string) when the value is valid, or `false` (or a message) when it is not. Write the value back with `self.modify(value)`: it also marks the component as **modified** and **touched**.\n\njComponent keeps these states in `config` (`invalid`, `touched`, `modified`) and as CSS classes (`ui-invalid`, `ui-touched`, `ui-modified`), so styling needs no code. `ERRORS(path, callback)` returns the invalid components below a path, which is what a form needs before it submits, and `RESET(path)` clears the touched and modified states.\n\n```html public/index.html\n<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<style>.ui-invalid input { border: 2px solid crimson; }</style>\n\n\t<ui-component name=\"required\" path=\"form.name\"></ui-component>\n\t<ui-component name=\"required\" path=\"form.email\"></ui-component>\n\n\t<script>\n\n\t\tCOMPONENT('required', function(self) {\n\n\t\t\tself.make = function() {\n\t\t\t\tself.html('<input type=\"text\" />');\n\t\t\t\tself.find('input').on('input', function() {\n\t\t\t\t\tself.modify(this.value);\n\t\t\t\t});\n\t\t\t};\n\n\t\t\tself.validate = function(value) {\n\t\t\t\treturn !!value && value.length > 2;\n\t\t\t};\n\n\t\t\tself.setter = function(value) {\n\t\t\t\tself.find('input').val(value || '');\n\t\t\t};\n\t\t});\n\n\t\tSET('form.name', 'Peter');\n\t\tSET('form.email', '');\n\n\t</script>\n\n</body>\n</html>\n```"}],"knowledge":{"content_sha256":"da083e2d978ec1a4472abb4b2d8a9ae7b2b3d28b560d6e54ea3a9114ed7be5f7","source_sha256":"ee46b047fedb7fce08524a92b2aac6d0e04d2e99acb9eb208d5016d1fd441c71","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"jcomponent20","url":"https://github.com/totaljs/jComponent","revision":"3fedd009a277f50fddc608b97afbec553b67c51b","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"01ce7c4d3dbd3d14b5a7ca9f44172ed332fa1eed250505811d4f828b38254bf9","license_files":[{"path":"LICENSE","sha256":"abf1260b51729c2fc0da7917e3564878725212bd7859683e7d5af91b6f9192bd","text":true,"characters":1095},{"path":"license.txt","sha256":"7226fe6e0be959f3c5e337bec8ca39ec08e694c8051dd7bc09d3e1a242abdc88","text":true,"characters":1099}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["COMPONENT","CSS","DOCTYPE","ERRORS","RESET","SET"],"language":"en"},"files":[{"path":"public/index.html","language":"html","content":"<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<style>.ui-invalid input { border: 2px solid crimson; }</style>\n\n\t<ui-component name=\"required\" path=\"form.name\"></ui-component>\n\t<ui-component name=\"required\" path=\"form.email\"></ui-component>\n\n\t<script>\n\n\t\tCOMPONENT('required', function(self) {\n\n\t\t\tself.make = function() {\n\t\t\t\tself.html('<input type=\"text\" />');\n\t\t\t\tself.find('input').on('input', function() {\n\t\t\t\t\tself.modify(this.value);\n\t\t\t\t});\n\t\t\t};\n\n\t\t\tself.validate = function(value) {\n\t\t\t\treturn !!value && value.length > 2;\n\t\t\t};\n\n\t\t\tself.setter = function(value) {\n\t\t\t\tself.find('input').val(value || '');\n\t\t\t};\n\t\t});\n\n\t\tSET('form.name', 'Peter');\n\t\tSET('form.email', '');\n\n\t</script>\n\n</body>\n</html>"}],"test":"const results = [];\nconst check = (ok, msg) => ok || results.push(msg);\nconst until = async (fn, ms = 4000) => { const t = Date.now(); while (Date.now() - t < ms) { try { if (fn()) return true; } catch (e) {} await new Promise(r => setTimeout(r, 50)); } return false; };\nconst first = () => $('ui-component').eq(0);\nconst second = () => $('ui-component').eq(1);\nawait until(() => $('input').length === 2 && first().hasClass('ui-invalid') === false && GET('form.name') === 'Peter');\nawait new Promise(r => setTimeout(r, 300));\ncheck(!first().hasClass('ui-invalid'), 'a valid value is not invalid');\nsecond().find('input').val('ab').trigger('input');\nawait until(() => second().hasClass('ui-invalid'));\ncheck(second().hasClass('ui-invalid'), 'a short value is invalid, classes: ' + second().attr('class'));\ncheck(second().hasClass('ui-modified') && second().hasClass('ui-touched'), 'modify() marks the component as modified and touched, classes: ' + second().attr('class'));\nlet errors = null;\nERRORS('form', function(arr) { errors = arr; });\nawait until(() => errors !== null);\ncheck(errors && errors.length === 1 && errors[0].path.path === 'form.email', 'ERRORS lists the invalid components, got: ' + (errors && errors.length));\nsecond().find('input').val('abc').trigger('input');\nawait until(() => !second().hasClass('ui-invalid'));\ncheck(!second().hasClass('ui-invalid'), 'a valid value clears the invalid state');\nRESET('form');\nawait until(() => !second().hasClass('ui-modified'));\ncheck(!second().hasClass('ui-modified') && !second().hasClass('ui-touched'), 'RESET clears modified and touched, classes: ' + second().attr('class'));\nresults","test_kind":"browser"}
{"id":"jcomponent/path-prefixes","section":"jcomponent","kind":"recipe","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/jcomponent/path-prefixes/","markdown_url":"https://forge.totaljs.es/en/jcomponent/path-prefixes.md","tags":["jcomponent","frontend","paths","state"],"license":"CC-BY-4.0","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"a116641e2429e8c67b94718b9347b64d5e7a3126164fb03e59a7c60ef2abb1fb","checked_at":"2026-10-20T12:19:26.359Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"browser","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Path prefixes: common, code lists, temporary state and plugin paths","description":"The first character of a jComponent path chooses where the value lives: * the common state, # the code lists, % the temporary state, ? the plugin state and plugin|path a property of a plugin object.","messages":[{"role":"user","content":"Path prefixes: common, code lists, temporary state and plugin paths\n\nThe first character of a jComponent path chooses where the value lives: * the common state, # the code lists, % the temporary state, ? the plugin state and plugin|path a property of a plugin object."},{"role":"assistant","content":"A path is where a value lives in the state of the page, and in jComponent 20 its **first character** can change the place.\n\n| Prefix | Example | Where it points |\n|---|---|---|\n| `*` | `*user.name` | the common state: `common.user.name` (`*` alone is `common`) |\n| `#` | `#countries` | the code lists: `DEF.cl.countries`, where `CL()` stores the lists it loads |\n| `%` | `%step` | the temporary state: `DEF.tmp.step`, for values that do not belong to the model |\n| `?` | `?.items` | inside a `<ui-plugin>`: the path of that plugin (see the plugin recipe) |\n| `plugin\\|path` | `cart\\|items` | a property of the plugin object called `cart`, from anywhere: `PLUGINS[\"cart\"].items` (it is **not** the state path `cart.items`) |\n\nThe flags of a path (`@visible`, `@change`...) are a different thing: they come after a space (see the custom flags recipe). Paths without a prefix are plain paths of the state (`user.name`).\n\n```html public/index.html\n<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\n\t<ui-bind path=\"*user.name\" config=\"text:value\" class=\"common\"></ui-bind>\n\t<ui-bind path=\"#greeting\" config=\"text:value\" class=\"cl\"></ui-bind>\n\t<ui-bind path=\"%step\" config=\"text:value\" class=\"tmp\"></ui-bind>\n\t<ui-bind path=\"plain.value\" config=\"text:value\" class=\"plain\"></ui-bind>\n\n\t<ui-plugin path=\"cart\">\n\t\t<ui-bind path=\"?.items\" config=\"text:value\" class=\"own\"></ui-bind>\n\t</ui-plugin>\n\n\t<script>\n\t\tPLUGIN('cart', function(self) {});\n\t\tSET('*user.name', 'Peter');\n\t\tSET('#greeting', 'Hello');\n\t\tSET('%step', 3);\n\t\tSET('plain.value', 'plain');\n\t\tSET('cart.items', 7);\n\t</script>\n\n</body>\n</html>\n```"}],"knowledge":{"content_sha256":"bb875905d62b28b998f81784d6ebbe4244d28c88f46adfb3bfbf9b45754c2c9b","source_sha256":"a116641e2429e8c67b94718b9347b64d5e7a3126164fb03e59a7c60ef2abb1fb","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"jcomponent20","url":"https://github.com/totaljs/jComponent","revision":"3fedd009a277f50fddc608b97afbec553b67c51b","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"01ce7c4d3dbd3d14b5a7ca9f44172ed332fa1eed250505811d4f828b38254bf9","license_files":[{"path":"LICENSE","sha256":"abf1260b51729c2fc0da7917e3564878725212bd7859683e7d5af91b6f9192bd","text":true,"characters":1095},{"path":"license.txt","sha256":"7226fe6e0be959f3c5e337bec8ca39ec08e694c8051dd7bc09d3e1a242abdc88","text":true,"characters":1099}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["DEF","DOCTYPE","PLUGIN","PLUGINS","SET"],"language":"en"},"files":[{"path":"public/index.html","language":"html","content":"<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\n\t<ui-bind path=\"*user.name\" config=\"text:value\" class=\"common\"></ui-bind>\n\t<ui-bind path=\"#greeting\" config=\"text:value\" class=\"cl\"></ui-bind>\n\t<ui-bind path=\"%step\" config=\"text:value\" class=\"tmp\"></ui-bind>\n\t<ui-bind path=\"plain.value\" config=\"text:value\" class=\"plain\"></ui-bind>\n\n\t<ui-plugin path=\"cart\">\n\t\t<ui-bind path=\"?.items\" config=\"text:value\" class=\"own\"></ui-bind>\n\t</ui-plugin>\n\n\t<script>\n\t\tPLUGIN('cart', function(self) {});\n\t\tSET('*user.name', 'Peter');\n\t\tSET('#greeting', 'Hello');\n\t\tSET('%step', 3);\n\t\tSET('plain.value', 'plain');\n\t\tSET('cart.items', 7);\n\t</script>\n\n</body>\n</html>"}],"test":"const results = [];\nconst check = (ok, msg) => ok || results.push(msg);\nconst until = async (fn, ms = 4000) => { const t = Date.now(); while (Date.now() - t < ms) { try { if (fn()) return true; } catch (e) {} await new Promise(r => setTimeout(r, 50)); } return false; };\nawait until(() => $('.common').text() === 'Peter');\ncheck($('.common').text() === 'Peter', 'common: text, got: ' + $('.common').text());\ncheck(GET('common.user.name') === 'Peter', 'common: * is common., got: ' + GET('common.user.name'));\ncheck(GET('*user.name') === 'Peter', 'common: GET with *, got: ' + GET('*user.name'));\ncheck($('.cl').text() === 'Hello', 'cl: text, got: ' + $('.cl').text());\ncheck(DEF.cl.greeting === 'Hello', '# is DEF.cl., got: ' + JSON.stringify(DEF.cl.greeting));\ncheck($('.tmp').text() === '3', 'tmp: text, got: ' + $('.tmp').text());\ncheck(DEF.tmp.step === 3, '% is DEF.tmp., got: ' + JSON.stringify(DEF.tmp.step));\ncheck($('.plain').text() === 'plain', 'plain: text, got: ' + $('.plain').text());\ncheck($('.own').text() === '7', '? is the plugin path, got: ' + $('.own').text());\nSET('cart|items', 9);\ncheck(PLUGINS.cart.items === 9, 'plugin|path is a property of the plugin object, got: ' + JSON.stringify(PLUGINS.cart.items));\ncheck(GET('cart|items') === 9, 'plugin|path reads it back, got: ' + GET('cart|items'));\ncheck(GET('cart.items') === 7 && $('.own').text() === '7', 'the state path of the plugin is untouched, got: ' + GET('cart.items'));\nresults","test_kind":"browser"}
{"id":"jcomponent/component-dependencies","section":"jcomponent","kind":"recipe","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/jcomponent/component-dependencies/","markdown_url":"https://forge.totaljs.es/en/jcomponent/component-dependencies.md","tags":["jcomponent","frontend","components","dependencies"],"license":"CC-BY-4.0","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"3801baec80c1a5118c997f6e5e01ccf6dd89739b6296f8fba0149c26b17e807a","checked_at":"2026-10-20T12:19:12.754Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"browser","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Load a library before a component starts","description":"List scripts and styles as the dependencies of a component so jComponent downloads them once and starts the component afterwards.","messages":[{"role":"user","content":"Load a library before a component starts\n\nList scripts and styles as the dependencies of a component so jComponent downloads them once and starts the component afterwards."},{"role":"assistant","content":"The fourth argument of `COMPONENT()` is a list of **dependencies**: URLs of scripts (`.js`) or styles (`.css`) that the component needs. jComponent downloads each one only once, in order, and creates the component after they are loaded, so `make()` can rely on the library being there. This is the way to wrap a third-party library into a component without loading it on pages that do not use it.\n\n```js public/js/starslib.js\nwindow.starslib = {\n\trender: function(count) {\n\t\tvar html = '';\n\t\tfor (var i = 0; i < count; i++)\n\t\t\thtml += '<span class=\"star\">*</span>';\n\t\treturn html;\n\t}\n};\n```\n\n```html public/index.html\n<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<ui-component name=\"stars\" config=\"count:3\"></ui-component>\n\n\t<script>\n\n\t\tCOMPONENT('stars', 'count:5', function(self, config) {\n\n\t\t\tself.make = function() {\n\t\t\t\t// starslib.js was loaded before make()\n\t\t\t\tself.html(window.starslib.render(config.count));\n\t\t\t};\n\n\t\t}, ['/js/starslib.js']);\n\n\t</script>\n\n</body>\n</html>\n```"}],"knowledge":{"content_sha256":"1343e12a6dec77d0fadee875a09f8a990ec5f2291032789c23e3d9ad97689498","source_sha256":"3801baec80c1a5118c997f6e5e01ccf6dd89739b6296f8fba0149c26b17e807a","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"jcomponent20","url":"https://github.com/totaljs/jComponent","revision":"3fedd009a277f50fddc608b97afbec553b67c51b","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"01ce7c4d3dbd3d14b5a7ca9f44172ed332fa1eed250505811d4f828b38254bf9","license_files":[{"path":"LICENSE","sha256":"abf1260b51729c2fc0da7917e3564878725212bd7859683e7d5af91b6f9192bd","text":true,"characters":1095},{"path":"license.txt","sha256":"7226fe6e0be959f3c5e337bec8ca39ec08e694c8051dd7bc09d3e1a242abdc88","text":true,"characters":1099}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["COMPONENT","DOCTYPE","URL"],"language":"en"},"files":[{"path":"public/js/starslib.js","language":"js","content":"window.starslib = {\n\trender: function(count) {\n\t\tvar html = '';\n\t\tfor (var i = 0; i < count; i++)\n\t\t\thtml += '<span class=\"star\">*</span>';\n\t\treturn html;\n\t}\n};"},{"path":"public/index.html","language":"html","content":"<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<ui-component name=\"stars\" config=\"count:3\"></ui-component>\n\n\t<script>\n\n\t\tCOMPONENT('stars', 'count:5', function(self, config) {\n\n\t\t\tself.make = function() {\n\t\t\t\t// starslib.js was loaded before make()\n\t\t\t\tself.html(window.starslib.render(config.count));\n\t\t\t};\n\n\t\t}, ['/js/starslib.js']);\n\n\t</script>\n\n</body>\n</html>"}],"test":"const results = [];\nconst check = (ok, msg) => ok || results.push(msg);\nconst until = async (fn, ms = 4000) => { const t = Date.now(); while (Date.now() - t < ms) { try { if (fn()) return true; } catch (e) {} await new Promise(r => setTimeout(r, 50)); } return false; };\nawait until(() => $('.star').length === 3);\ncheck($('.star').length === 3, 'the component used the library that was loaded first, got: ' + $('.star').length);\ncheck(typeof window.starslib === 'object', 'the library is a global');\nresults","test_kind":"browser"}
{"id":"jcomponent/extensions","section":"jcomponent","kind":"recipe","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/jcomponent/extensions/","markdown_url":"https://forge.totaljs.es/en/jcomponent/extensions.md","tags":["jcomponent","frontend","components"],"license":"CC-BY-4.0","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"0a94048294e6db861f6f27c9390e879de867b0e9a47f8fda2cfd1cd3539b6e64","checked_at":"2026-10-20T12:19:23.375Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"browser","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Extend components and plugins with EXTENSION","description":"Add behavior to an existing component from outside, without editing its source, with EXTENSION().","messages":[{"role":"user","content":"Extend components and plugins with EXTENSION\n\nAdd behavior to an existing component from outside, without editing its source, with EXTENSION()."},{"role":"assistant","content":"`EXTENSION(name, config, callback)` adds code to every instance of the component `name` (or of the plugin `@name`) when it is created. The callback receives the instance and its config, exactly like a declaration, so you can add methods, change options or listen to events. It is how an application customizes third-party components without forking them.\n\n```html public/index.html\n<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<ui-component name=\"label\" config=\"text:Hello\"></ui-component>\n\n\t<script>\n\n\t\tCOMPONENT('label', 'text:', function(self, config) {\n\t\t\tself.make = function() {\n\t\t\t\tself.html('<span></span>');\n\t\t\t\tself.find('span').text(config.text);\n\t\t\t};\n\t\t});\n\n\t\tEXTENSION('label', 'suffix:!', function(self, config) {\n\t\t\tself.shout = function() {\n\t\t\t\tself.find('span').text(config.text.toUpperCase() + config.suffix);\n\t\t\t};\n\t\t\tself.extended = true;\n\t\t});\n\n\t</script>\n\n</body>\n</html>\n```"}],"knowledge":{"content_sha256":"36d8b89a461439666c01affaa521ef83de6b58de563a0bdc49c5d9cc976298f2","source_sha256":"0a94048294e6db861f6f27c9390e879de867b0e9a47f8fda2cfd1cd3539b6e64","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"jcomponent20","url":"https://github.com/totaljs/jComponent","revision":"3fedd009a277f50fddc608b97afbec553b67c51b","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"01ce7c4d3dbd3d14b5a7ca9f44172ed332fa1eed250505811d4f828b38254bf9","license_files":[{"path":"LICENSE","sha256":"abf1260b51729c2fc0da7917e3564878725212bd7859683e7d5af91b6f9192bd","text":true,"characters":1095},{"path":"license.txt","sha256":"7226fe6e0be959f3c5e337bec8ca39ec08e694c8051dd7bc09d3e1a242abdc88","text":true,"characters":1099}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["COMPONENT","DOCTYPE","EXTENSION"],"language":"en"},"files":[{"path":"public/index.html","language":"html","content":"<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<ui-component name=\"label\" config=\"text:Hello\"></ui-component>\n\n\t<script>\n\n\t\tCOMPONENT('label', 'text:', function(self, config) {\n\t\t\tself.make = function() {\n\t\t\t\tself.html('<span></span>');\n\t\t\t\tself.find('span').text(config.text);\n\t\t\t};\n\t\t});\n\n\t\tEXTENSION('label', 'suffix:!', function(self, config) {\n\t\t\tself.shout = function() {\n\t\t\t\tself.find('span').text(config.text.toUpperCase() + config.suffix);\n\t\t\t};\n\t\t\tself.extended = true;\n\t\t});\n\n\t</script>\n\n</body>\n</html>"}],"test":"const results = [];\nconst check = (ok, msg) => ok || results.push(msg);\nconst until = async (fn, ms = 4000) => { const t = Date.now(); while (Date.now() - t < ms) { try { if (fn()) return true; } catch (e) {} await new Promise(r => setTimeout(r, 50)); } return false; };\nawait until(() => $('ui-component span').length === 1);\nconst instance = $('ui-component')[0].$uicomponent;\nawait until(() => instance.extended === true);\ncheck(instance.extended === true, 'the extension ran for the instance');\ncheck(typeof instance.shout === 'function', 'the extension added a method');\ninstance.shout();\ncheck($('ui-component span').text() === 'HELLO!', 'the added method works with the config, got: ' + $('ui-component span').text());\nresults","test_kind":"browser"}
{"id":"jcomponent/ui-bind","section":"jcomponent","kind":"recipe","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/jcomponent/ui-bind/","markdown_url":"https://forge.totaljs.es/en/jcomponent/ui-bind.md","tags":["jcomponent","frontend","binding"],"license":"CC-BY-4.0","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"1b55a9401e68b07080c59732064f73a390e86ee5e61cb0b1dcd4e95ac9f90632","checked_at":"2026-10-20T12:19:38.964Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"browser","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Show, hide and style elements with ui-bind","description":"Use ui-bind commands to toggle visibility, classes and HTML from the state, without writing DOM code.","messages":[{"role":"user","content":"Show, hide and style elements with ui-bind\n\nUse ui-bind commands to toggle visibility, classes and HTML from the state, without writing DOM code."},{"role":"assistant","content":"`<ui-bind path=\"...\" config=\"...\">` applies **commands** to its element whenever the value of the path changes. Commands are separated by semicolons. Each command is `name:expression`, where `value` is the current value of the path:\n\n- `show:value` / `hide:value` shows or hides the element\n- `html:value` / `text:value` replaces the content\n- `.classname:value` adds the class when the expression is truthy\n\n```html public/index.html\n<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n\t<style>.hidden { display: none; } .admin { color: crimson; }</style>\n</head>\n<body>\n\n\t<ui-bind path=\"user.admin\" config=\"show:value\" class=\"hidden\">Admin panel</ui-bind>\n\t<ui-bind path=\"user.admin\" config=\"hide:value\">You are a guest</ui-bind>\n\t<ui-bind path=\"user.name\" config=\".admin:value === 'Peter';text:value\">-</ui-bind>\n\n\t<script>\n\t\tSET('user.admin', false);\n\t\tSET('user.name', 'Louis');\n\t</script>\n\n</body>\n</html>\n```"}],"knowledge":{"content_sha256":"482f5f41510459159e22387a9b057687f79a009fb31c96783b3cff31a0a33fd7","source_sha256":"1b55a9401e68b07080c59732064f73a390e86ee5e61cb0b1dcd4e95ac9f90632","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"jcomponent20","url":"https://github.com/totaljs/jComponent","revision":"3fedd009a277f50fddc608b97afbec553b67c51b","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"01ce7c4d3dbd3d14b5a7ca9f44172ed332fa1eed250505811d4f828b38254bf9","license_files":[{"path":"LICENSE","sha256":"abf1260b51729c2fc0da7917e3564878725212bd7859683e7d5af91b6f9192bd","text":true,"characters":1095},{"path":"license.txt","sha256":"7226fe6e0be959f3c5e337bec8ca39ec08e694c8051dd7bc09d3e1a242abdc88","text":true,"characters":1099}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["DOCTYPE","SET"],"language":"en"},"files":[{"path":"public/index.html","language":"html","content":"<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n\t<style>.hidden { display: none; } .admin { color: crimson; }</style>\n</head>\n<body>\n\n\t<ui-bind path=\"user.admin\" config=\"show:value\" class=\"hidden\">Admin panel</ui-bind>\n\t<ui-bind path=\"user.admin\" config=\"hide:value\">You are a guest</ui-bind>\n\t<ui-bind path=\"user.name\" config=\".admin:value === 'Peter';text:value\">-</ui-bind>\n\n\t<script>\n\t\tSET('user.admin', false);\n\t\tSET('user.name', 'Louis');\n\t</script>\n\n</body>\n</html>"}],"test":"const results = [];\nconst check = (ok, msg) => ok || results.push(msg);\nconst until = async (fn, ms = 4000) => { const t = Date.now(); while (Date.now() - t < ms) { try { if (fn()) return true; } catch (e) {} await new Promise(r => setTimeout(r, 50)); } return false; };\nconst panel = $('ui-bind').eq(0);\nconst guest = $('ui-bind').eq(1);\nconst name = $('ui-bind').eq(2);\nawait until(() => name.text() === 'Louis');\ncheck(panel.is(':hidden'), 'admin panel is hidden for a guest');\ncheck(guest.is(':visible'), 'guest text is visible');\ncheck(name.text() === 'Louis' && !name.hasClass('admin'), 'text is set, class is not: ' + name.text() + ' / ' + name.attr('class'));\nSET('user.admin', true);\nSET('user.name', 'Peter');\nawait until(() => name.text() === 'Peter' && name.hasClass('admin'));\ncheck(panel.is(':visible'), 'admin panel is shown after SET');\ncheck(guest.is(':hidden'), 'guest text is hidden after SET');\ncheck(name.text() === 'Peter' && name.hasClass('admin'), 'class is added when the expression is true: ' + name.text() + ' / ' + name.attr('class'));\nresults","test_kind":"browser"}
{"id":"jcomponent/ui-bind-templates","section":"jcomponent","kind":"recipe","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/jcomponent/ui-bind-templates/","markdown_url":"https://forge.totaljs.es/en/jcomponent/ui-bind-templates.md","tags":["jcomponent","frontend","binding","templates"],"license":"CC-BY-4.0","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"2061760524afd118b609b34bbc8fd10e97359f25a68179bf632a050d4b2a8810","checked_at":"2026-10-20T12:19:38.117Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"browser","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Render lists with a ui-bind template","description":"Put a Tangular template inside ui-bind with the template command to render arrays and objects whenever the data changes.","messages":[{"role":"user","content":"Render lists with a ui-bind template\n\nPut a Tangular template inside ui-bind with the template command to render arrays and objects whenever the data changes."},{"role":"assistant","content":"With the `template` command, `<ui-bind>` renders a **Tangular** template instead of setting one value. The template is the `<script type=\"text/html\">` inside the element; `value` is the data of the path. Every time the path changes the element is rendered again, so lists stay in sync with the state.\n\n```html public/index.html\n<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<ui-bind path=\"products\" config=\"template\">\n\t\t<script type=\"text/html\">\n\t\t\t<ul>\n\t\t\t{{ foreach p in value }}\n\t\t\t\t<li class=\"product\">{{ p.name }} - {{ p.price }}</li>\n\t\t\t{{ end }}\n\t\t\t</ul>\n\t\t</script>\n\t</ui-bind>\n\n\t<script>\n\t\tSET('products', [{ name: 'Book', price: 12 }, { name: 'Pen', price: 3 }]);\n\t</script>\n\n</body>\n</html>\n```"}],"knowledge":{"content_sha256":"7e72862392e0c19c808e973b30a82d3c1551828fea364d0b3e1bb0f65556d668","source_sha256":"2061760524afd118b609b34bbc8fd10e97359f25a68179bf632a050d4b2a8810","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"jcomponent20","url":"https://github.com/totaljs/jComponent","revision":"3fedd009a277f50fddc608b97afbec553b67c51b","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"01ce7c4d3dbd3d14b5a7ca9f44172ed332fa1eed250505811d4f828b38254bf9","license_files":[{"path":"LICENSE","sha256":"abf1260b51729c2fc0da7917e3564878725212bd7859683e7d5af91b6f9192bd","text":true,"characters":1095},{"path":"license.txt","sha256":"7226fe6e0be959f3c5e337bec8ca39ec08e694c8051dd7bc09d3e1a242abdc88","text":true,"characters":1099}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["DOCTYPE","SET"],"language":"en"},"files":[{"path":"public/index.html","language":"html","content":"<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<ui-bind path=\"products\" config=\"template\">\n\t\t<script type=\"text/html\">\n\t\t\t<ul>\n\t\t\t{{ foreach p in value }}\n\t\t\t\t<li class=\"product\">{{ p.name }} - {{ p.price }}</li>\n\t\t\t{{ end }}\n\t\t\t</ul>\n\t\t</script>\n\t</ui-bind>\n\n\t<script>\n\t\tSET('products', [{ name: 'Book', price: 12 }, { name: 'Pen', price: 3 }]);\n\t</script>\n\n</body>\n</html>"}],"test":"const results = [];\nconst check = (ok, msg) => ok || results.push(msg);\nconst until = async (fn, ms = 4000) => { const t = Date.now(); while (Date.now() - t < ms) { try { if (fn()) return true; } catch (e) {} await new Promise(r => setTimeout(r, 50)); } return false; };\nawait until(() => $('li.product').length === 2);\ncheck($('li.product').eq(0).text() === 'Book - 12', 'the template renders the first item, got: ' + $('li.product').eq(0).text());\nPUSH('products', { name: 'Lamp', price: 40 });\nawait until(() => $('li.product').length === 3);\ncheck($('li.product').eq(2).text() === 'Lamp - 40', 'the list is rendered again after PUSH, got: ' + $('li.product').length);\nSET('products', []);\nawait until(() => $('li.product').length === 0);\ncheck($('li.product').length === 0, 'an empty array renders no items');\nresults","test_kind":"browser"}
{"id":"jcomponent/ui-bind-more-commands","section":"jcomponent","kind":"recipe","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/jcomponent/ui-bind-more-commands/","markdown_url":"https://forge.totaljs.es/en/jcomponent/ui-bind-more-commands.md","tags":["jcomponent","frontend","binding"],"license":"CC-BY-4.0","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"9e003fef555efcfde5b0c0b654725d4f671d0380df1d263bcda27119dd3adef4","checked_at":"2026-10-20T12:19:35.210Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"browser","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"More ui-bind commands: attributes, values, state and formatting","description":"Set HTML, attributes, form values, disabled state and a text format from the state with ui-bind commands.","messages":[{"role":"user","content":"More ui-bind commands: attributes, values, state and formatting\n\nSet HTML, attributes, form values, disabled state and a text format from the state with ui-bind commands."},{"role":"assistant","content":"Besides `text`, `show` and `hide`, `ui-bind` has commands for the common DOM changes. Each command is `name:expression` (or `name selector:expression` to apply it to an inner element), and `value` is the value of the path:\n\n| Command | Does |\n|---|---|\n| `html:expr` | sets the inner HTML (escape it yourself!) |\n| `title:expr`, `href:expr`, `src:expr` | sets that attribute |\n| `attr name:expr` | sets any attribute |\n| `value:expr` | sets the value of a form control |\n| `disabled:expr` / `enabled:expr` | disables or enables the controls inside |\n| `checked:expr` | checks the checkbox inside |\n| `.class:expr` | adds or removes the class |\n\nOptions of the whole element: `format:yyyy-MM-dd` formats a date value before it is printed (for numbers call `format()` in the expression: `text:value.format(2)`), `empty:none` is shown when the value is empty, `changes` skips updates when the value did not change, and `delay:200` waits that many milliseconds before rendering.\n\n```html public/index.html\n<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<ui-bind path=\"user\" config=\"attr data-id:value.id;title:value.name;text:value.name\" class=\"greeting\"></ui-bind>\n\t<ui-bind path=\"product.price\" config=\"text:value.format(2)\" class=\"price\"></ui-bind>\n\t<ui-bind path=\"product.date\" config=\"text:value;format:yyyy-MM-dd\" class=\"date\"></ui-bind>\n\t<ui-bind path=\"user.admin\" config=\"disabled:!value\"><button type=\"button\" id=\"delete\">Delete</button></ui-bind>\n\t<ui-bind path=\"user.nick\" config=\"value input:value;empty:anonymous\"><input type=\"text\" /></ui-bind>\n\t<ui-bind path=\"user.nick\" config=\"text:value;empty:anonymous\" class=\"nick\"></ui-bind>\n\n\t<script>\n\t\tSET('user', { id: 7, name: 'Peter', admin: false, nick: '' });\n\t\tSET('product', { price: 1234.5, date: new Date(2026, 9, 16) });\n\t</script>\n\n</body>\n</html>\n```"}],"knowledge":{"content_sha256":"d2eab2bb102a37f53d6827705cb5c632ede7dd263e6cc9a51ba6b2b0ba931523","source_sha256":"9e003fef555efcfde5b0c0b654725d4f671d0380df1d263bcda27119dd3adef4","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"jcomponent20","url":"https://github.com/totaljs/jComponent","revision":"3fedd009a277f50fddc608b97afbec553b67c51b","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"01ce7c4d3dbd3d14b5a7ca9f44172ed332fa1eed250505811d4f828b38254bf9","license_files":[{"path":"LICENSE","sha256":"abf1260b51729c2fc0da7917e3564878725212bd7859683e7d5af91b6f9192bd","text":true,"characters":1095},{"path":"license.txt","sha256":"7226fe6e0be959f3c5e337bec8ca39ec08e694c8051dd7bc09d3e1a242abdc88","text":true,"characters":1099}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["DOCTYPE","DOM","HTML","SET"],"language":"en"},"files":[{"path":"public/index.html","language":"html","content":"<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<ui-bind path=\"user\" config=\"attr data-id:value.id;title:value.name;text:value.name\" class=\"greeting\"></ui-bind>\n\t<ui-bind path=\"product.price\" config=\"text:value.format(2)\" class=\"price\"></ui-bind>\n\t<ui-bind path=\"product.date\" config=\"text:value;format:yyyy-MM-dd\" class=\"date\"></ui-bind>\n\t<ui-bind path=\"user.admin\" config=\"disabled:!value\"><button type=\"button\" id=\"delete\">Delete</button></ui-bind>\n\t<ui-bind path=\"user.nick\" config=\"value input:value;empty:anonymous\"><input type=\"text\" /></ui-bind>\n\t<ui-bind path=\"user.nick\" config=\"text:value;empty:anonymous\" class=\"nick\"></ui-bind>\n\n\t<script>\n\t\tSET('user', { id: 7, name: 'Peter', admin: false, nick: '' });\n\t\tSET('product', { price: 1234.5, date: new Date(2026, 9, 16) });\n\t</script>\n\n</body>\n</html>"}],"test":"const results = [];\nconst check = (ok, msg) => ok || results.push(msg);\nconst until = async (fn, ms = 4000) => { const t = Date.now(); while (Date.now() - t < ms) { try { if (fn()) return true; } catch (e) {} await new Promise(r => setTimeout(r, 50)); } return false; };\nawait until(() => $('.greeting').text() === 'Peter' && $('.date').text());\nconst greeting = $('.greeting');\ncheck(greeting.text() === 'Peter', 'text shows the name, got: ' + greeting.text());\ncheck(/^1\\s?234\\.50$/.test($('.price').text()), 'value.format(2) gives two decimals, got: ' + $('.price').text());\ncheck($('.date').text() === '2026-10-16', 'format:yyyy-MM-dd formats a date, got: ' + $('.date').text());\ncheck(greeting.attr('data-id') === '7', 'attr sets an attribute, got: ' + greeting.attr('data-id'));\ncheck(greeting.attr('title') === 'Peter', 'title sets the title attribute');\ncheck($('#delete').prop('disabled') === true, 'disabled:!value disables the button for a non-admin');\ncheck($('.nick').text() === 'anonymous', 'empty is shown for an empty value, got: ' + $('.nick').text());\nSET('user.admin', true);\nSET('user.nick', 'pete');\nawait until(() => $('#delete').prop('disabled') === false && $('.nick').text() === 'pete');\ncheck($('#delete').prop('disabled') === false, 'the button is enabled after SET');\ncheck($('.nick').text() === 'pete', 'text follows the state');\ncheck($('input').val() === 'pete', 'value sets the control inside, got: ' + $('input').val());\nresults","test_kind":"browser"}
{"id":"jcomponent/ui-bind-reference","section":"jcomponent","kind":"recipe","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/jcomponent/ui-bind-reference/","markdown_url":"https://forge.totaljs.es/en/jcomponent/ui-bind-reference.md","tags":["jcomponent","frontend","binding","reference"],"license":"CC-BY-4.0","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"f51f8861531efd692c6048dd7cfdb69207f6277b0998247fd17375d0ed68ee4e","checked_at":"2026-10-20T12:19:37.275Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"browser","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"ui-bind reference: every command, option and attribute","description":"All the commands of ui-bind with what each one does, the options that control when it runs, and the attributes that choose the target element.","messages":[{"role":"user","content":"ui-bind reference: every command, option and attribute\n\nAll the commands of ui-bind with what each one does, the options that control when it runs, and the attributes that choose the target element."},{"role":"assistant","content":"`<ui-bind>` is the element that applies **commands** to the DOM when the value of its path changes. This page lists what each command does, the options that decide *when* it runs and the attributes that choose *which element* it changes.\n\nThe format is `config=\"command:expression;command:expression\"`. In an expression `value` is the value of the path and `element` (also `el`) is the element that is changed. A command can have a selector (`text .name:value` applies it to the elements `.name` inside) and `attr` takes the attribute name first (`attr title:value`).\n\n## Commands that change the element\n\n| Command | What it does |\n|---|---|\n| `text:expr` | sets the text; an empty value shows the `empty` text |\n| `html:expr` | sets the inner HTML (escape it yourself) |\n| `value:expr` | sets the value of a form control |\n| `title:expr`, `href:expr`, `src:expr` | sets that attribute |\n| `attr name:expr` | sets any attribute |\n| `.class:expr` | adds the class when the expression is true and removes it when false |\n| `show:expr` / `hide:expr` | adds or removes the class `hidden` |\n| `visible:expr` / `invisible:expr` | adds or removes the class `invisible` (the element keeps its space) |\n| `checked:expr` | checks or unchecks the `input` inside |\n| `disabled:expr` / `enabled:expr` | disables or enables the controls inside (`input`, `select`, `textarea`, `button`) and the `ui-component` elements inside |\n| `required:expr` | sets the `required` option of the `ui-component` elements inside (it does not touch a plain `input`) |\n| `template` | renders the Tangular template of the element (see the template recipe) |\n| `exec:path` / `refresh:path` | calls the function at that path with the value, the path and the element, for every `SET` of the path of the binder |\n\n`show` and `hide` run first, then `visible` and `invisible`, and `text`, `html` and `template` run last, so that the visibility of the element is already known when the content is written. `text`, `html` and `template` are only written while the element is visible, unless `forcerender:true` is in the same config: then they are written even when the element is hidden.\n\n## Options: when the commands run\n\n| Option | What it does |\n|---|---|\n| `changes` | skips the update when the value is the same as the last one |\n| `strict` | only runs when the path of the binder itself changed, not when a child path changed (`user.name` does not run a binder of `user`) |\n| `track:a,b` | only runs when one of those paths changed (a binder of `user` that tracks `user.name` ignores `user.age`); it runs again for every `SET` of a tracked path, even with the same value |\n| `once` | runs once and then the binder is removed |\n| `check:path` | calls the function at that path with `(value, path, element)`; the commands only run when it returns true |\n| `delay:ms` | waits that many milliseconds and renders once, even if the value changed several times meanwhile (100 when no value is given) |\n| `empty:text` | the text shown instead of an empty value (`---` when no text is given) |\n| `format:pattern` | formats the value of `text`, `html`, `value`, `attr`, `title`, `href` and `src` with `String.format` |\n| `class:name` | adds that class to the `ui-bind` element once, after the first render |\n| `assign:path` | stores the binder instance at that path |\n| `helpers:path` | the object at that path provides the helpers of the template: with `helpers:hh` and `hh = { shout: fn }`, the template can use `{{ value | shout() }}` |\n\n## Attributes: which element changes\n\nBy default the commands change the `ui-bind` element. These attributes pick another one:\n\n- `element=\"#target\"` any element of the page (a jQuery selector)\n- `child=\".inner\"` an element inside the `ui-bind`\n- `parent=\".card\"` the closest ancestor that matches\n\nThe path can start with `!` (`path=\"!user.nick\"`): the binder then ignores the moments when the value is `null` or `undefined`. A command name can also start with `!` or `~`: the prefix is accepted and ignored, the command works as without it.\n\n## Attribute commands\n\n- **Use one attribute per command: write `title:value` and `alt:value` as two commands.**\n\n```html public/index.html\n<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<ui-bind path=\"user.age\" config=\"visible:value > 17\" id=\"visible\"><span>adult</span></ui-bind>\n\t<ui-bind path=\"user.age\" config=\"invisible:value > 17\" id=\"invisible\"><span>minor</span></ui-bind>\n\t<ui-bind path=\"user.name\" config=\"text:value;class:marked\" id=\"classonce\"></ui-bind>\n\t<ui-bind path=\"user.name\" config=\"text:(window.counter_changes = (window.counter_changes || 0) + 1) && value;changes\" id=\"changes\"></ui-bind>\n\t<ui-bind path=\"user\" config=\"text:(window.counter_strict = (window.counter_strict || 0) + 1);strict\" id=\"strict\"></ui-bind>\n\t<ui-bind path=\"user\" config=\"text:value.name;track:user.name\" id=\"track\"></ui-bind>\n\t<ui-bind path=\"user.name\" config=\"text:value;once\" id=\"once\"></ui-bind>\n\t<ui-bind path=\"user.name\" config=\"text:value;check:isready\" id=\"check\"></ui-bind>\n\t<ui-bind path=\"user.name\" config=\"exec:onname\" id=\"exec\"></ui-bind>\n\t<ui-bind path=\"user.name\" config=\"text:value;assign:binders.main\" id=\"assign\"></ui-bind>\n\t<ui-bind path=\"!user.nick\" config=\"text:value\" id=\"notnull\"></ui-bind>\n\t<ui-bind path=\"user.name\" config=\"text:value\" element=\"#target\" id=\"element\"></ui-bind>\n\t<div id=\"target\"></div>\n\t<ui-bind path=\"user.name\" config=\"text:value\" child=\".inner\" id=\"child\"><b class=\"inner\"></b></ui-bind>\n\t<ui-bind path=\"user.name\" config=\"text .a:value;text .b:value.toUpperCase()\" id=\"selector\"><i class=\"a\"></i><i class=\"b\"></i></ui-bind>\n\t<ui-bind path=\"user.name\" config=\"show:false;text:value;forcerender:true\" id=\"force\"></ui-bind>\n\t<ui-bind path=\"user.name\" config=\"show:false;text:value\" id=\"noforce\"></ui-bind>\n\t<ui-bind path=\"user.admin\" config=\"required:value;disabled:!value\" id=\"flags\"><input type=\"text\" /></ui-bind>\n\t<ui-bind path=\"user.admin\" config=\"checked:value\" id=\"checked\"><input type=\"checkbox\" /></ui-bind>\n\n\t<ui-bind path=\"user.name\" config=\"title+alt:value\" id=\"clone\"></ui-bind>\n\t<ui-bind path=\"user\" config=\"text:(window.counter_track = (window.counter_track || 0) + 1);track:user.name\" id=\"track2\"></ui-bind>\n\t<ui-bind path=\"user.name\" config=\"template;helpers:hh\" id=\"helpers\"><script type=\"text/html\"><b>{{ value | shout() }}</b></script></ui-bind>\n\t<ui-bind path=\"user.none\" config=\"text:value;empty\" id=\"emptydef\"></ui-bind>\n\t<ui-bind path=\"user.name\" config=\"focus\"><input type=\"text\" id=\"f1\" /></ui-bind>\n\t<ui-bind path=\"user.name\" config=\"!text:value\" id=\"bang\"></ui-bind>\n\t<ui-bind path=\"user.name\" config=\"~text:value\" id=\"tilde\"></ui-bind>\n\t<ui-bind path=\"user.name\" config=\"text:el.attr('id') + '/' + element.attr('id')\" id=\"elvar\"></ui-bind>\n\t<div class=\"card\"><ui-bind path=\"user.name\" config=\"attr data-n:value\" parent=\".card\" id=\"parentbind\"></ui-bind></div>\n\n\t<script>\n\t\twindow.hh = { shout: s => String(s).toUpperCase() };\n\t\twindow.isready = function(value, path, element) {\n\t\t\treturn value !== 'blocked';\n\t\t};\n\t\twindow.exec_log = [];\n\t\twindow.onname = function() {\n\t\t\twindow.exec_log.push(Array.prototype.slice.call(arguments).map(a => typeof a === 'object' ? (a && a.nodeType ? 'element' : a && a.path ? 'path' : 'object') : String(a)).join('|'));\n\t\t};\n\t\tSET('user', { name: 'Ann', age: 30, admin: false });\n\t</script>\n\n</body>\n</html>\n```"}],"knowledge":{"content_sha256":"d7dd2942e0d87bb5a180224f41aef86edbd3df78da67e6f6a52dc048d81fa14a","source_sha256":"f51f8861531efd692c6048dd7cfdb69207f6277b0998247fd17375d0ed68ee4e","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"jcomponent20","url":"https://github.com/totaljs/jComponent","revision":"3fedd009a277f50fddc608b97afbec553b67c51b","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"01ce7c4d3dbd3d14b5a7ca9f44172ed332fa1eed250505811d4f828b38254bf9","license_files":[{"path":"LICENSE","sha256":"abf1260b51729c2fc0da7917e3564878725212bd7859683e7d5af91b6f9192bd","text":true,"characters":1095},{"path":"license.txt","sha256":"7226fe6e0be959f3c5e337bec8ca39ec08e694c8051dd7bc09d3e1a242abdc88","text":true,"characters":1099}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["Array.prototype.slice.call","DOCTYPE","DOM","HTML","SET","String.format"],"language":"en"},"files":[{"path":"public/index.html","language":"html","content":"<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<ui-bind path=\"user.age\" config=\"visible:value > 17\" id=\"visible\"><span>adult</span></ui-bind>\n\t<ui-bind path=\"user.age\" config=\"invisible:value > 17\" id=\"invisible\"><span>minor</span></ui-bind>\n\t<ui-bind path=\"user.name\" config=\"text:value;class:marked\" id=\"classonce\"></ui-bind>\n\t<ui-bind path=\"user.name\" config=\"text:(window.counter_changes = (window.counter_changes || 0) + 1) && value;changes\" id=\"changes\"></ui-bind>\n\t<ui-bind path=\"user\" config=\"text:(window.counter_strict = (window.counter_strict || 0) + 1);strict\" id=\"strict\"></ui-bind>\n\t<ui-bind path=\"user\" config=\"text:value.name;track:user.name\" id=\"track\"></ui-bind>\n\t<ui-bind path=\"user.name\" config=\"text:value;once\" id=\"once\"></ui-bind>\n\t<ui-bind path=\"user.name\" config=\"text:value;check:isready\" id=\"check\"></ui-bind>\n\t<ui-bind path=\"user.name\" config=\"exec:onname\" id=\"exec\"></ui-bind>\n\t<ui-bind path=\"user.name\" config=\"text:value;assign:binders.main\" id=\"assign\"></ui-bind>\n\t<ui-bind path=\"!user.nick\" config=\"text:value\" id=\"notnull\"></ui-bind>\n\t<ui-bind path=\"user.name\" config=\"text:value\" element=\"#target\" id=\"element\"></ui-bind>\n\t<div id=\"target\"></div>\n\t<ui-bind path=\"user.name\" config=\"text:value\" child=\".inner\" id=\"child\"><b class=\"inner\"></b></ui-bind>\n\t<ui-bind path=\"user.name\" config=\"text .a:value;text .b:value.toUpperCase()\" id=\"selector\"><i class=\"a\"></i><i class=\"b\"></i></ui-bind>\n\t<ui-bind path=\"user.name\" config=\"show:false;text:value;forcerender:true\" id=\"force\"></ui-bind>\n\t<ui-bind path=\"user.name\" config=\"show:false;text:value\" id=\"noforce\"></ui-bind>\n\t<ui-bind path=\"user.admin\" config=\"required:value;disabled:!value\" id=\"flags\"><input type=\"text\" /></ui-bind>\n\t<ui-bind path=\"user.admin\" config=\"checked:value\" id=\"checked\"><input type=\"checkbox\" /></ui-bind>\n\n\t<ui-bind path=\"user.name\" config=\"title+alt:value\" id=\"clone\"></ui-bind>\n\t<ui-bind path=\"user\" config=\"text:(window.counter_track = (window.counter_track || 0) + 1);track:user.name\" id=\"track2\"></ui-bind>\n\t<ui-bind path=\"user.name\" config=\"template;helpers:hh\" id=\"helpers\"><script type=\"text/html\"><b>{{ value | shout() }}</b></script></ui-bind>\n\t<ui-bind path=\"user.none\" config=\"text:value;empty\" id=\"emptydef\"></ui-bind>\n\t<ui-bind path=\"user.name\" config=\"focus\"><input type=\"text\" id=\"f1\" /></ui-bind>\n\t<ui-bind path=\"user.name\" config=\"!text:value\" id=\"bang\"></ui-bind>\n\t<ui-bind path=\"user.name\" config=\"~text:value\" id=\"tilde\"></ui-bind>\n\t<ui-bind path=\"user.name\" config=\"text:el.attr('id') + '/' + element.attr('id')\" id=\"elvar\"></ui-bind>\n\t<div class=\"card\"><ui-bind path=\"user.name\" config=\"attr data-n:value\" parent=\".card\" id=\"parentbind\"></ui-bind></div>\n\n\t<script>\n\t\twindow.hh = { shout: s => String(s).toUpperCase() };\n\t\twindow.isready = function(value, path, element) {\n\t\t\treturn value !== 'blocked';\n\t\t};\n\t\twindow.exec_log = [];\n\t\twindow.onname = function() {\n\t\t\twindow.exec_log.push(Array.prototype.slice.call(arguments).map(a => typeof a === 'object' ? (a && a.nodeType ? 'element' : a && a.path ? 'path' : 'object') : String(a)).join('|'));\n\t\t};\n\t\tSET('user', { name: 'Ann', age: 30, admin: false });\n\t</script>\n\n</body>\n</html>"}],"test":"const results = [];\nconst check = (ok, msg) => ok || results.push(msg);\nconst until = async (fn, ms = 4000) => { const t = Date.now(); while (Date.now() - t < ms) { try { if (fn()) return true; } catch (e) {} await new Promise(r => setTimeout(r, 50)); } return false; };\nconst wait = ms => new Promise(r => setTimeout(r, ms));\nconst text = id => $('#' + id).text();\nawait until(() => text('track') === 'Ann');\nawait wait(300);\n\n// the commands\ncheck(!$('#visible').hasClass('invisible') && $('#invisible').hasClass('invisible'), 'visible and invisible add the class invisible to the right element');\ncheck($('#classonce').hasClass('marked'), 'class:marked is added to the ui-bind element');\ncheck(text('selector') === 'AnnANN' && $('#selector .a').text() === 'Ann' && $('#selector .b').text() === 'ANN', 'a command with a selector changes the inner element');\ncheck($('#flags input').prop('disabled') === true, 'disabled:!value disables the control for a false value');\ncheck($('#flags input').prop('required') === false, 'required does not touch a plain input');\ncheck($('#checked input').prop('checked') === false, 'checked:value is unchecked for false');\n\n// the target\ncheck(text('element') === '' && $('#target').text() === 'Ann', 'element=\"#target\" changes another element, got: ' + $('#target').text());\ncheck($('.inner').text() === 'Ann', 'child changes the inner element');\ncheck($('.card').attr('data-n') === 'Ann', 'parent changes the closest ancestor, got: ' + $('.card').attr('data-n'));\ncheck(text('notnull') === '', 'the ! prefix of the path skips an undefined value');\ncheck(text('elvar') === 'elvar/elvar', 'el and element are the target element, got: ' + text('elvar'));\ncheck(text('bang') === 'Ann' && text('tilde') === 'Ann', 'the ! and ~ prefixes of a command are ignored');\ncheck($('#clone').attr('title') === 'Ann' && $('#clone').attr('alt') == null, 'a+b sets only the first attribute');\n\n// forcerender and empty\ncheck(text('force') === 'Ann' && $('#force').hasClass('hidden'), 'forcerender writes the text of a hidden element');\ncheck(text('noforce') === '' && $('#noforce').hasClass('hidden'), 'a hidden element does not get its text without forcerender');\ncheck(text('emptydef') === '---', 'empty without a value shows ---, got: ' + text('emptydef'));\n\n// assign, exec and helpers\ncheck(typeof GET('binders.main') === 'object' && GET('binders.main') !== null, 'assign stores the binder instance');\ncheck(window.exec_log[0].indexOf('Ann|path|object') === 0, 'exec receives the value, the path and the element, got: ' + window.exec_log[0]);\ncheck($('#helpers b').text() === 'ANN', 'helpers: the template can call {{ value | shout() }}, got: ' + $('#helpers').html());\n\n// options when the commands run\nconst track0 = window.counter_track;\nconst strict0 = window.counter_strict;\nconst changes0 = window.counter_changes;\nSET('user.age', 10);\nSET('user.admin', true);\nSET('user.name', 'Bob');\nSET('user.name', 'Bob');\nawait wait(500);\ncheck(window.counter_track === track0 + 2, 'track:user.name ran for the two SET of the name and not for age or admin, ran ' + (window.counter_track - track0));\ncheck(window.counter_strict === strict0, 'strict does not run for a child path');\ncheck(window.counter_changes === changes0 + 1, 'changes skips the repeated value, ran ' + (window.counter_changes - changes0));\ncheck(text('once') === 'Ann', 'once renders once and stays, got: ' + text('once'));\ncheck(text('check') === 'Bob', 'check lets the update pass');\ncheck(window.exec_log.length === 3 && window.exec_log[1].indexOf('Bob') === 0, 'exec ran for every SET of its path (also the repeated one), got: ' + window.exec_log.join(' / '));\ncheck($('#visible').hasClass('invisible') && !$('#invisible').hasClass('invisible'), 'visible and invisible follow the value');\ncheck($('#flags input').prop('disabled') === false, 'disabled:!value enables the control for true');\ncheck($('#checked input').prop('checked') === true, 'checked:value checks the control for true');\n\nSET('user.name', 'blocked');\nSET('user.nick', 'pete');\nawait wait(400);\ncheck(text('check') === 'Bob', 'check returned false: the commands did not run, got: ' + text('check'));\ncheck(text('notnull') === 'pete', 'the ! path runs for a value, got: ' + text('notnull'));\nresults","test_kind":"browser"}
{"id":"jcomponent/component-instance-reference","section":"jcomponent","kind":"recipe","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/jcomponent/component-instance-reference/","markdown_url":"https://forge.totaljs.es/en/jcomponent/component-instance-reference.md","tags":["jcomponent","frontend","components","reference"],"license":"CC-BY-4.0","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"04cb402cbe5e127f7c116ca75ee25f419a99a607cf9b1e5263acaac9d1fbc553","checked_at":"2026-10-20T12:19:15.829Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"browser","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Component instance reference: properties and methods of self","description":"The declaration of a jComponent 20 component and what its instance (self) offers: properties, value methods, DOM helpers, events, watchers and commands.","messages":[{"role":"user","content":"Component instance reference: properties and methods of self\n\nThe declaration of a jComponent 20 component and what its instance (self) offers: properties, value methods, DOM helpers, events, watchers and commands."},{"role":"assistant","content":"A component is declared with `COMPONENT(name, defaultConfig, function(self, config, cls) { ... })`. The declaration runs once for every `<ui-component name=\"...\">` of the page and receives the instance (`self`), its configuration (`config`, the same object as `self.config`) and the class prefix (`cls`). This page lists what `self` has.\n\n## Hooks you define\n\n| Member | When it runs |\n|---|---|\n| `self.make` | once, when the element is ready: build the DOM here |\n| `self.setter(value, path, flags)` | every time the value of the path changes, and when the component starts; `flags` is an object |\n| `self.destroy` | after the element was removed (see the lifecycle recipe) |\n\n## Properties\n\n| Property | Value |\n|---|---|\n| `self.name` | the name of the component (`probe`) |\n| `self.path.path` | the path as a string (`form.name`) |\n| `self.element` | the `<ui-component>` element as a jQuery collection |\n| `self.dom` | the same element as a DOM node |\n| `self.config` | the configuration: the defaults of the declaration, then the `config` attribute of the element. It also holds the state flags: `invalid` from the start, `modified` and `touched` once the value is modified |\n| `self.id` | a unique id of the instance |\n| `self.ready` | `true` after `make` |\n\n## The value\n\n| Method | What it does |\n|---|---|\n| `self.get()` | reads the value of the path |\n| `self.set(value)` | writes the value and runs the `setter` of every component of that path, this one too |\n| `self.update()` | runs the `setter` again with the current value, without changing it |\n| `self.rewrite(value)` | writes the value **without** running the `setter` of this component (the others are notified) |\n| `self.modify(value)` | writes the value and marks the component as modified and touched: `config.modified` and `config.touched` become 1 |\n| `self.reset()` | clears those flags: `config.modified` and `config.touched` go back to 0 |\n| `self.GET(path)`, `self.SET(path, value)` | read and write any path of the state |\n| `self.EXEC(name, a)` | calls the global function `name` |\n\n## The DOM of the component\n\n`self.html(html)`, `self.text(text)`, `self.append(html)` and `self.empty()` change the content. `self.find(selector)` returns the elements inside as jQuery. `self.css(name, value)` sets a style, `self.attr(name, value)` an attribute and `self.attrd(name, value)` a `data-` attribute (`self.attrd('x')` reads `data-x`). The classes: `self.aclass(name)` adds, `self.rclass(name)` removes, `self.hclass(name)` tells whether it has it, and `self.tclass(name, [state])` toggles it or sets it to `state`. `self.width()` and `self.height()` return numbers.\n\n## Events, watchers and commands\n\n| Method | What it does |\n|---|---|\n| `self.on(name, fn)` / `self.emit(name, a)` | listens to and sends a named event of the page |\n| `self.watch(path, fn, [autoinit])` | calls `fn(value, flags, path)` when that path changes; with `autoinit` also once at the start. `+` joins several paths |\n| `self.reconfigure('size:9')` | changes the configuration at runtime: `self.config.size` is 9 |\n| `self.command(name, fn)` / `self.CMD(name, a)` | registers a command of this instance and calls it |\n\n## Do not rely on these\n\n- **`self.unwatch(path, [fn])` does not remove the watcher** in jComponent 20: it compares the parsed path of the watcher with a string, so nothing matches. The watcher keeps running after `unwatch`. If you need to stop a watcher, check a flag inside `fn`.\n- **The global `CMD(name)` did not call a command registered with `self.command`**: only `self.CMD(name)` did.\n\n```html public/index.html\n<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<ui-component name=\"probe\" path=\"form.name\" config=\"size:3;title:Hello\" id=\"probe\"></ui-component>\n\t<script>\n\t\twindow.log = {};\n\t\twindow.events = [];\n\t\tCOMPONENT('probe', 'size:1;flag:false', function(self, config, cls) {\n\t\t\twindow.inst = self;\n\t\t\tself.make = function() {\n\t\t\t\tself.html('<p class=\"a\">one</p><p class=\"b\">two</p>');\n\t\t\t\tself.event('click', '.a', function() { window.events.push('click'); });\n\t\t\t};\n\t\t\tself.setter = function(value, path, type) {\n\t\t\t\twindow.events.push('setter:' + value + ':' + path + ':' + type);\n\t\t\t};\n\t\t});\n\t\tSET('form.name', 'Ann');\n\t</script>\n</body>\n</html>\n```"}],"knowledge":{"content_sha256":"33a85f3da8d2937168f5392eef1f0018b207e7f63c15b0a4e71d55753f94e6b8","source_sha256":"04cb402cbe5e127f7c116ca75ee25f419a99a607cf9b1e5263acaac9d1fbc553","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"jcomponent20","url":"https://github.com/totaljs/jComponent","revision":"3fedd009a277f50fddc608b97afbec553b67c51b","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"01ce7c4d3dbd3d14b5a7ca9f44172ed332fa1eed250505811d4f828b38254bf9","license_files":[{"path":"LICENSE","sha256":"abf1260b51729c2fc0da7917e3564878725212bd7859683e7d5af91b6f9192bd","text":true,"characters":1095},{"path":"license.txt","sha256":"7226fe6e0be959f3c5e337bec8ca39ec08e694c8051dd7bc09d3e1a242abdc88","text":true,"characters":1099}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CMD","COMPONENT","DOCTYPE","DOM","EXEC","GET","SET"],"language":"en"},"files":[{"path":"public/index.html","language":"html","content":"<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<ui-component name=\"probe\" path=\"form.name\" config=\"size:3;title:Hello\" id=\"probe\"></ui-component>\n\t<script>\n\t\twindow.log = {};\n\t\twindow.events = [];\n\t\tCOMPONENT('probe', 'size:1;flag:false', function(self, config, cls) {\n\t\t\twindow.inst = self;\n\t\t\tself.make = function() {\n\t\t\t\tself.html('<p class=\"a\">one</p><p class=\"b\">two</p>');\n\t\t\t\tself.event('click', '.a', function() { window.events.push('click'); });\n\t\t\t};\n\t\t\tself.setter = function(value, path, type) {\n\t\t\t\twindow.events.push('setter:' + value + ':' + path + ':' + type);\n\t\t\t};\n\t\t});\n\t\tSET('form.name', 'Ann');\n\t</script>\n</body>\n</html>"}],"test":"const results = [];\nconst check = (ok, msg) => ok || results.push(msg);\nconst wait = ms => new Promise(r => setTimeout(r, ms));\nawait wait(800);\nconst s = window.inst;\nconst last = () => window.events[window.events.length - 1];\n\n// declaration and properties\ncheck(s.name === 'probe' && s.path.path === 'form.name', 'name and path, got: ' + s.name + ' ' + s.path.path);\ncheck(s.element.length === 1 && s.dom.tagName === 'UI-COMPONENT', 'element and dom');\ncheck(s.config.title === 'Hello' && s.config.flag === false && s.config.size === 3, 'config merges the defaults and the attribute, got: ' + JSON.stringify(s.config));\ncheck(s.config.invalid === false, 'invalid is false at the start, got: ' + JSON.stringify(s.config));\ncheck(typeof s.id === 'string' && s.id.length > 3 && s.ready === true, 'id and ready');\ncheck(window.events[0] === 'setter:Ann:form.name:[object Object]', 'the setter receives value, path and a flags object, got: ' + window.events[0]);\ncheck(s.find('.a').length === 1, 'make built the DOM');\n\n// the value\ncheck(s.get() === 'Ann', 'get');\ns.set('Bob'); await wait(200);\ncheck(s.get() === 'Bob' && last() === 'setter:Bob:form.name:[object Object]', 'set runs the setter, got: ' + last());\ns.rewrite('Cy'); await wait(200);\ncheck(GET('form.name') === 'Cy' && last() === 'setter:Bob:form.name:[object Object]', 'rewrite changes the value without the setter of this component, got: ' + last());\ns.update(); await wait(200);\ncheck(last() === 'setter:Cy:form.name:[object Object]', 'update runs the setter with the current value, got: ' + last());\ns.modify('Dee'); await wait(200);\ncheck(GET('form.name') === 'Dee' && s.config.modified === 1 && s.config.touched === 1, 'modify sets the value and the flags, got: ' + JSON.stringify(s.config));\ns.reset(); await wait(200);\ncheck(s.config.modified === 0 && s.config.touched === 0, 'reset clears the flags');\ns.SET('x.y', 5);\ncheck(s.GET('x.y') === 5, 'SET and GET');\nwindow.hi = function(a) { window.hival = a; };\ns.EXEC('hi', 'zz');\ncheck(window.hival === 'zz', 'EXEC calls a global function');\n\n// the DOM\ncheck(s.attr('id') === 'probe', 'attr reads');\ns.attr('data-x', '5');\ncheck(s.attr('data-x') === '5' && s.attrd('x') === '5', 'attr and attrd');\ns.aclass('k1');\ncheck(s.hclass('k1') === true, 'aclass and hclass');\ns.rclass('k1');\ncheck(s.hclass('k1') === false, 'rclass');\ns.tclass('k2');\ncheck(s.hclass('k2') === true, 'tclass toggles on');\ns.tclass('k2', false);\ncheck(s.hclass('k2') === false, 'tclass with a state');\ncheck(s.find('.a').text() === 'one' && s.find('p').length === 2, 'find');\ns.css('color', 'red');\ncheck(s.element.css('color') === 'rgb(255, 0, 0)', 'css');\ns.text('t');\ncheck(s.element.text() === 't', 'text');\ns.html('<p class=\"a\">one</p>');\ns.append('<i>x</i>');\ncheck(s.element.find('i').length === 1 && s.element.find('.a').length === 1, 'html and append');\ns.empty();\ncheck(s.element.html() === '', 'empty');\ncheck(typeof s.width() === 'number' && typeof s.height() === 'number', 'width and height are numbers');\n\n// events, watchers, config, commands\nconst got = [];\ns.on('myevent', function(a) { got.push(a); });\ns.emit('myevent', 7);\ncheck(got.length === 1 && got[0] === 7, 'on and emit, got: ' + got);\nlet count = 0;\nconst fn = function(value, flags, path) { count++; };\ns.watch('o.x', fn);\nSET('o.x', 1); await wait(150);\ncheck(count === 1, 'watch calls the function, got: ' + count);\ns.unwatch('o.x', fn);\nSET('o.x', 2); await wait(150);\ncheck(count === 2, 'unwatch does not remove the watcher in this version, got: ' + count);\ns.watch('o.z', fn, true); await wait(150);\ncheck(count === 3, 'watch with autoinit calls the function once at the start, got: ' + count);\ns.reconfigure('size:9');\ncheck(s.config.size === 9, 'reconfigure changes the configuration');\nconst cmd = [];\ns.command('hello', function(a) { cmd.push(a); });\nCMD('hello', 'A');\ns.CMD('hello', 'B');\ncheck(cmd.length === 1 && cmd[0] === 'B', 'only self.CMD calls a command of the instance, got: ' + cmd);\nresults","test_kind":"browser"}
{"id":"jcomponent/plugin","section":"jcomponent","kind":"recipe","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/jcomponent/plugin/","markdown_url":"https://forge.totaljs.es/en/jcomponent/plugin.md","tags":["jcomponent","frontend","plugins"],"license":"CC-BY-4.0","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"8247de68987a91e30dbe946d236f215f9dc45cc5715bffdeee4c7cc7839f80bf","checked_at":"2026-10-20T12:19:27.206Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"browser","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Group state and logic in a jComponent plugin","description":"Declare a plugin with PLUGIN() and attach it to a part of the page with ui-plugin, using ? as its own path.","messages":[{"role":"user","content":"Group state and logic in a jComponent plugin\n\nDeclare a plugin with PLUGIN() and attach it to a part of the page with ui-plugin, using ? as its own path."},{"role":"assistant","content":"A plugin is the controller of a section of the page. `PLUGIN(name, declaration)` registers it and `<ui-plugin path=\"name\">` attaches it to an element. Inside the section, `?` in a path means \"the path of the plugin\", so the markup does not depend on where the plugin lives in the state.\n\nThe declaration receives `self` (the plugin instance) and `config`. `self.get()` and `self.set()` work relative to the plugin path, and `self.element` is the jQuery element of the `<ui-plugin>`. Implement `self.make` to run code once the plugin is ready. Use `self.element` for DOM events: `self.on()` is for jComponent events, not for clicks.\n\n```html public/index.html\n<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\n\t<ui-plugin path=\"cart\">\n\t\t<p>Items: <ui-bind path=\"?.items\" config=\"text:value || 0\"></ui-bind></p>\n\t\t<button type=\"button\" id=\"add\">Add item</button>\n\t</ui-plugin>\n\n\t<script>\n\n\t\tPLUGIN('cart', function(self) {\n\n\t\t\tself.add = function() {\n\t\t\t\tself.set('items', (self.get('items') || 0) + 1);\n\t\t\t};\n\n\t\t\t// make() runs when the plugin is ready: initialize the state and the events here\n\t\t\tself.make = function() {\n\t\t\t\tself.set('items', 0);\n\t\t\t\tself.element.on('click', '#add', self.add);\n\t\t\t};\n\n\t\t});\n\n\t</script>\n\n</body>\n</html>\n```"}],"knowledge":{"content_sha256":"918277cc4bfc0f66aebf54064e58421a46ea131fb2fb2ec1acefc647442b9b4b","source_sha256":"8247de68987a91e30dbe946d236f215f9dc45cc5715bffdeee4c7cc7839f80bf","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"jcomponent20","url":"https://github.com/totaljs/jComponent","revision":"3fedd009a277f50fddc608b97afbec553b67c51b","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"01ce7c4d3dbd3d14b5a7ca9f44172ed332fa1eed250505811d4f828b38254bf9","license_files":[{"path":"LICENSE","sha256":"abf1260b51729c2fc0da7917e3564878725212bd7859683e7d5af91b6f9192bd","text":true,"characters":1095},{"path":"license.txt","sha256":"7226fe6e0be959f3c5e337bec8ca39ec08e694c8051dd7bc09d3e1a242abdc88","text":true,"characters":1099}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["DOCTYPE","DOM","PLUGIN"],"language":"en"},"files":[{"path":"public/index.html","language":"html","content":"<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\n\t<ui-plugin path=\"cart\">\n\t\t<p>Items: <ui-bind path=\"?.items\" config=\"text:value || 0\"></ui-bind></p>\n\t\t<button type=\"button\" id=\"add\">Add item</button>\n\t</ui-plugin>\n\n\t<script>\n\n\t\tPLUGIN('cart', function(self) {\n\n\t\t\tself.add = function() {\n\t\t\t\tself.set('items', (self.get('items') || 0) + 1);\n\t\t\t};\n\n\t\t\t// make() runs when the plugin is ready: initialize the state and the events here\n\t\t\tself.make = function() {\n\t\t\t\tself.set('items', 0);\n\t\t\t\tself.element.on('click', '#add', self.add);\n\t\t\t};\n\n\t\t});\n\n\t</script>\n\n</body>\n</html>"}],"test":"const results = [];\nconst check = (ok, msg) => ok || results.push(msg);\nconst until = async (fn, ms = 4000) => { const t = Date.now(); while (Date.now() - t < ms) { try { if (fn()) return true; } catch (e) {} await new Promise(r => setTimeout(r, 50)); } return false; };\nawait until(() => GET('cart.items') === 0);\ncheck(GET('cart.items') === 0, 'plugin initializes its own path, got: ' + GET('cart.items'));\n$('#add').trigger('click');\n$('#add').trigger('click');\nawait until(() => GET('cart.items') === 2 && $('ui-bind').text() === '2');\ncheck(GET('cart.items') === 2, 'plugin method writes to its path, got: ' + GET('cart.items'));\ncheck($('ui-bind').text() === '2', 'ui-bind with ? follows the plugin path, got: ' + $('ui-bind').text());\nresults","test_kind":"browser"}
{"id":"jcomponent/spa-routing","section":"jcomponent","kind":"recipe","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/jcomponent/spa-routing/","markdown_url":"https://forge.totaljs.es/en/jcomponent/spa-routing.md","tags":["jcomponent","frontend","routing","spa"],"license":"CC-BY-4.0","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"d6d0eeffce4e556b2ffd06280fb5168840bb38d6dd2a7a13c53632bd790e6a43","checked_at":"2026-10-20T12:19:27.997Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"browser","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Route pages in the browser without reloading","description":"Register client-side routes with ROUTE(), navigate with REDIRECT(), read the URL parameters and the query string from NAV, and go back in the history.","messages":[{"role":"user","content":"Route pages in the browser without reloading\n\nRegister client-side routes with ROUTE(), navigate with REDIRECT(), read the URL parameters and the query string from NAV, and go back in the history."},{"role":"assistant","content":"jComponent has its own router for single page applications. `ROUTE(url, fn)` registers a route: `{name}` segments are passed to `fn` as arguments in order. `REDIRECT(url)` changes the address with the History API (no page load) and runs the route that matches. Routes are compared segment by segment: a route and a URL must have the same number of segments, and `*` matches any value at its position. `NAV` describes the current location: `NAV.url` is the path, `NAV.query` the parsed query string and `NAV.params` the route arguments. `NAV.back()` and `NAV.forward()` move through the history that jComponent keeps.\n\nFor the server this is still one page, so every URL below your app has to return the same HTML (a `ROUTE('GET /*')` that renders the shell), otherwise a reload on `/users/5/` would give a 404.\n\n```html public/index.html\n<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\n\t<h1 id=\"title\"></h1>\n\n\t<script>\n\n\t\tROUTE('/', function() {\n\t\t\t$('#title').text('Home');\n\t\t});\n\n\t\tROUTE('/users/{id}/', function(id) {\n\t\t\t$('#title').text('User ' + id);\n\t\t});\n\n\t\tROUTE('/files/*', function() {\n\t\t\t$('#title').text('Files');\n\t\t});\n\n\t</script>\n\n</body>\n</html>\n```"}],"knowledge":{"content_sha256":"0653d2df8ce5713c551c0651206dcb143244d26558dd299359751f41b40ae700","source_sha256":"d6d0eeffce4e556b2ffd06280fb5168840bb38d6dd2a7a13c53632bd790e6a43","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"jcomponent20","url":"https://github.com/totaljs/jComponent","revision":"3fedd009a277f50fddc608b97afbec553b67c51b","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"01ce7c4d3dbd3d14b5a7ca9f44172ed332fa1eed250505811d4f828b38254bf9","license_files":[{"path":"LICENSE","sha256":"abf1260b51729c2fc0da7917e3564878725212bd7859683e7d5af91b6f9192bd","text":true,"characters":1095},{"path":"license.txt","sha256":"7226fe6e0be959f3c5e337bec8ca39ec08e694c8051dd7bc09d3e1a242abdc88","text":true,"characters":1099}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["API","DOCTYPE","GET","HTML","NAV","REDIRECT","ROUTE","URL"],"language":"en"},"files":[{"path":"public/index.html","language":"html","content":"<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\n\t<h1 id=\"title\"></h1>\n\n\t<script>\n\n\t\tROUTE('/', function() {\n\t\t\t$('#title').text('Home');\n\t\t});\n\n\t\tROUTE('/users/{id}/', function(id) {\n\t\t\t$('#title').text('User ' + id);\n\t\t});\n\n\t\tROUTE('/files/*', function() {\n\t\t\t$('#title').text('Files');\n\t\t});\n\n\t</script>\n\n</body>\n</html>"}],"test":"const results = [];\nconst check = (ok, msg) => ok || results.push(msg);\nconst until = async (fn, ms = 4000) => { const t = Date.now(); while (Date.now() - t < ms) { try { if (fn()) return true; } catch (e) {} await new Promise(r => setTimeout(r, 50)); } return false; };\n\nREDIRECT('/');\nawait until(() => $('#title').text() === 'Home');\ncheck($('#title').text() === 'Home', 'REDIRECT() runs the matching route, got: ' + $('#title').text());\n\nREDIRECT('/users/42/?tab=info');\nawait until(() => $('#title').text() === 'User 42');\ncheck($('#title').text() === 'User 42', 'a {param} is passed to the route, got: ' + $('#title').text());\ncheck(location.pathname === '/users/42/', 'the address changes without a reload, got: ' + location.pathname);\ncheck(NAV.url === '/users/42/', 'NAV.url, got: ' + NAV.url);\ncheck(NAV.query && NAV.query.tab === 'info', 'NAV.query, got: ' + JSON.stringify(NAV.query));\n\nREDIRECT('/files/readme/');\nawait until(() => $('#title').text() === 'Files');\ncheck($('#title').text() === 'Files', 'a * route matches everything below, got: ' + $('#title').text());\n\nNAV.back();\nawait until(() => $('#title').text() === 'User 42');\ncheck($('#title').text() === 'User 42', 'NAV.back() returns to the previous route, got: ' + $('#title').text());\n\nresults","test_kind":"browser"}
{"id":"jcomponent/ui-import","section":"jcomponent","kind":"recipe","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/jcomponent/ui-import/","markdown_url":"https://forge.totaljs.es/en/jcomponent/ui-import.md","tags":["jcomponent","frontend","import"],"license":"CC-BY-4.0","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"d5d606d391d7e567e57b3196c23f1aa2c88fef27be76dced05890c90c309cfc8","checked_at":"2026-10-20T12:19:39.911Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"browser","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Load parts of a page with ui-import","description":"Fetch an HTML part from the server into an element with ui-import or IMPORT(), and let the part carry its own components.","messages":[{"role":"user","content":"Load parts of a page with ui-import\n\nFetch an HTML part from the server into an element with ui-import or IMPORT(), and let the part carry its own components."},{"role":"assistant","content":"`<ui-import>` downloads an HTML file and puts it into the page, so a big screen can be split into parts that are loaded when needed. The address goes in the `config` attribute (`url:/parts/card.html`); `target` can name another element to fill instead of the `ui-import` itself.\n\nIn code use `IMPORT(url, target, callback)`: the target is a selector or element, and the callback runs when the part is in the page. Each address is downloaded once. The same function loads scripts (`.js`) and styles (`.css`). Components and bindings inside the loaded part start working automatically.\n\n```html public/parts/card.html\n<div class=\"card\">Hello <ui-bind path=\"user.name\" config=\"text:value\" class=\"who\"></ui-bind></div>\n```\n\n```html public/parts/note.html\n<div class=\"note\">A note</div>\n```\n\n```html public/index.html\n<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<ui-import config=\"url:/parts/card.html\"></ui-import>\n\t<div id=\"slot\"></div>\n\n\t<script>\n\t\tvar done = [];\n\t\tIMPORT('/parts/note.html', '#slot', function() { done.push('slot'); });\n\t\tSET('user.name', 'Peter');\n\t</script>\n\n</body>\n</html>\n```"}],"knowledge":{"content_sha256":"7ac2231a7f413cd4d9e85011909290452b95eae379496c91fab2f1b18347f18f","source_sha256":"d5d606d391d7e567e57b3196c23f1aa2c88fef27be76dced05890c90c309cfc8","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"jcomponent20","url":"https://github.com/totaljs/jComponent","revision":"3fedd009a277f50fddc608b97afbec553b67c51b","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"01ce7c4d3dbd3d14b5a7ca9f44172ed332fa1eed250505811d4f828b38254bf9","license_files":[{"path":"LICENSE","sha256":"abf1260b51729c2fc0da7917e3564878725212bd7859683e7d5af91b6f9192bd","text":true,"characters":1095},{"path":"license.txt","sha256":"7226fe6e0be959f3c5e337bec8ca39ec08e694c8051dd7bc09d3e1a242abdc88","text":true,"characters":1099}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["DOCTYPE","HTML","IMPORT","SET"],"language":"en"},"files":[{"path":"public/parts/card.html","language":"html","content":"<div class=\"card\">Hello <ui-bind path=\"user.name\" config=\"text:value\" class=\"who\"></ui-bind></div>"},{"path":"public/parts/note.html","language":"html","content":"<div class=\"note\">A note</div>"},{"path":"public/index.html","language":"html","content":"<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<ui-import config=\"url:/parts/card.html\"></ui-import>\n\t<div id=\"slot\"></div>\n\n\t<script>\n\t\tvar done = [];\n\t\tIMPORT('/parts/note.html', '#slot', function() { done.push('slot'); });\n\t\tSET('user.name', 'Peter');\n\t</script>\n\n</body>\n</html>"}],"test":"const results = [];\nconst check = (ok, msg) => ok || results.push(msg);\nconst until = async (fn, ms = 4000) => { const t = Date.now(); while (Date.now() - t < ms) { try { if (fn()) return true; } catch (e) {} await new Promise(r => setTimeout(r, 50)); } return false; };\nawait until(() => $('.card').length === 1 && $('#slot .note').length === 1);\ncheck($('.card').length === 1, 'ui-import loads the part, got: ' + $('.card').length);\ncheck($('#slot .note').length === 1, 'IMPORT() fills the target element');\nawait until(() => done.length === 1);\ncheck(done.join() === 'slot', 'the callback runs after the part is in the page, got: ' + done.join());\nawait until(() => $('.card .who').first().text() === 'Peter');\ncheck($('.card .who').first().text() === 'Peter', 'ui-bind inside the loaded part works, got: ' + $('.card .who').first().text());\nresults","test_kind":"browser"}
{"id":"jcomponent/commands","section":"jcomponent","kind":"recipe","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/jcomponent/commands/","markdown_url":"https://forge.totaljs.es/en/jcomponent/commands.md","tags":["jcomponent","frontend","commands"],"license":"CC-BY-4.0","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"ca64807c91bea85331d7f2f118bc10964f13e4a93dd8d2cc0b0548faba91aedc","checked_at":"2026-10-20T12:19:11.077Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"browser","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Call code with commands and EXEC","description":"Register commands with component.command() and call them with CMD(), or call a plugin method with EXEC('plugin/method').","messages":[{"role":"user","content":"Call code with commands and EXEC\n\nRegister commands with component.command() and call them with CMD(), or call a plugin method with EXEC('plugin/method')."},{"role":"assistant","content":"Two ways to trigger code without a direct reference to the object that owns it:\n\n- A component registers a **command** with `self.command(name, fn)`, and the code that holds the component instance runs it with `instance.CMD(name, a, b, c)` (`element[0].$uicomponent` is the instance of a `<ui-component>`). Use it for actions such as \"open this window\" or \"refresh that list\". The global `CMD(name)` is declared to reach every component, but in jComponent 20.014 it cuts a name that has no space, so it does nothing: use the instance, or `EXEC()`.\n- `EXEC('plugin/method', a, b)` calls the method `method` of the plugin that lives on the path `plugin`; a plain function name (`EXEC('myfunction')`) calls a global function. The `exec` command of `ui-bind` and the `exec` option of many components take the same names.\n\n```html public/index.html\n<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<ui-plugin path=\"cart\">\n\t\t<p>Items: <ui-bind path=\"?.count\" config=\"text:value\"></ui-bind></p>\n\t</ui-plugin>\n\t<ui-component name=\"notice\" id=\"notice\"></ui-component>\n\n\t<script>\n\n\t\tPLUGIN('cart', function(self) {\n\t\t\tself.add = function(n) {\n\t\t\t\tself.set('count', (self.get('count') || 0) + n);\n\t\t\t};\n\t\t\tself.make = function() {\n\t\t\t\tself.set('count', 0);\n\t\t\t};\n\t\t});\n\n\t\tCOMPONENT('notice', function(self) {\n\t\t\tself.make = function() {\n\t\t\t\tself.html('<i class=\"text\"></i>');\n\t\t\t\tself.command('say', function(text) {\n\t\t\t\t\tself.find('.text').text(text);\n\t\t\t\t});\n\t\t\t};\n\t\t});\n\n\t\tfunction logout(arg) {\n\t\t\twindow.loggedout = arg;\n\t\t}\n\n\t</script>\n\n</body>\n</html>\n```"}],"knowledge":{"content_sha256":"39dc5c5a78e25e6e23174948c2caa281a46d2d0b99027a9540e82ac791c548fa","source_sha256":"ca64807c91bea85331d7f2f118bc10964f13e4a93dd8d2cc0b0548faba91aedc","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"jcomponent20","url":"https://github.com/totaljs/jComponent","revision":"3fedd009a277f50fddc608b97afbec553b67c51b","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"01ce7c4d3dbd3d14b5a7ca9f44172ed332fa1eed250505811d4f828b38254bf9","license_files":[{"path":"LICENSE","sha256":"abf1260b51729c2fc0da7917e3564878725212bd7859683e7d5af91b6f9192bd","text":true,"characters":1095},{"path":"license.txt","sha256":"7226fe6e0be959f3c5e337bec8ca39ec08e694c8051dd7bc09d3e1a242abdc88","text":true,"characters":1099}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CMD","COMPONENT","DOCTYPE","EXEC","PLUGIN"],"language":"en"},"files":[{"path":"public/index.html","language":"html","content":"<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<ui-plugin path=\"cart\">\n\t\t<p>Items: <ui-bind path=\"?.count\" config=\"text:value\"></ui-bind></p>\n\t</ui-plugin>\n\t<ui-component name=\"notice\" id=\"notice\"></ui-component>\n\n\t<script>\n\n\t\tPLUGIN('cart', function(self) {\n\t\t\tself.add = function(n) {\n\t\t\t\tself.set('count', (self.get('count') || 0) + n);\n\t\t\t};\n\t\t\tself.make = function() {\n\t\t\t\tself.set('count', 0);\n\t\t\t};\n\t\t});\n\n\t\tCOMPONENT('notice', function(self) {\n\t\t\tself.make = function() {\n\t\t\t\tself.html('<i class=\"text\"></i>');\n\t\t\t\tself.command('say', function(text) {\n\t\t\t\t\tself.find('.text').text(text);\n\t\t\t\t});\n\t\t\t};\n\t\t});\n\n\t\tfunction logout(arg) {\n\t\t\twindow.loggedout = arg;\n\t\t}\n\n\t</script>\n\n</body>\n</html>"}],"test":"const results = [];\nconst check = (ok, msg) => ok || results.push(msg);\nconst until = async (fn, ms = 4000) => { const t = Date.now(); while (Date.now() - t < ms) { try { if (fn()) return true; } catch (e) {} await new Promise(r => setTimeout(r, 50)); } return false; };\nawait until(() => GET('cart.count') === 0 && $('.text').length);\nEXEC('cart/add', 3);\nawait until(() => GET('cart.count') === 3);\ncheck(GET('cart.count') === 3, 'EXEC calls the method of the plugin, got: ' + GET('cart.count'));\nawait until(() => $('ui-bind').text() === '3');\ncheck($('ui-bind').text() === '3', 'the binding follows, got: ' + $('ui-bind').text());\nCMD('say', 'global');\nawait new Promise(r => setTimeout(r, 300));\ncheck($('.text').text() === '', 'the global CMD() with a plain name does nothing in 20.014, got: ' + $('.text').text());\n$('#notice')[0].$uicomponent.CMD('say', 'Saved!');\nawait until(() => $('.text').text() === 'Saved!');\ncheck($('.text').text() === 'Saved!', 'instance.CMD() runs the command registered by the component, got: ' + $('.text').text());\nEXEC('logout', 'bye');\ncheck(window.loggedout === 'bye', 'EXEC calls a global function');\nresults","test_kind":"browser"}
{"id":"jcomponent/events","section":"jcomponent","kind":"recipe","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/jcomponent/events/","markdown_url":"https://forge.totaljs.es/en/jcomponent/events.md","tags":["jcomponent","frontend","events"],"license":"CC-BY-4.0","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"096933b231d3325c21e7fedbf2c2766484558d909af2b96cf665fadfe23eceae","checked_at":"2026-10-20T12:19:22.532Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"browser","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Send events between components with ON and EMIT","description":"Decouple parts of the page with global events: EMIT() announces something, ON() listens, OFF() stops listening.","messages":[{"role":"user","content":"Send events between components with ON and EMIT\n\nDecouple parts of the page with global events: EMIT() announces something, ON() listens, OFF() stops listening."},{"role":"assistant","content":"Global events let independent parts of a page talk without knowing each other. `ON(name, callback)` registers a listener, `EMIT(name, a, b, c, d)` calls every listener with up to four arguments, and `OFF(name, [callback])` removes one listener or all the listeners of the name. Components and plugins have the same functions on `self` (`self.on()`, `self.emit()`), and the listeners they register are removed together with the component.\n\n```\nEMIT('cart.changed', 3)   ->   ON('cart.changed', function(count) { ... })\n```\n\n```html public/index.html\n<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<ui-component name=\"counter\"></ui-component>\n\t<p class=\"shown\"></p>\n\n\t<script>\n\n\t\tvar received = [];\n\n\t\tCOMPONENT('counter', function(self) {\n\t\t\tself.make = function() {\n\t\t\t\tself.html('<button type=\"button\">+1</button>');\n\t\t\t\tvar count = 0;\n\t\t\t\tself.find('button').on('click', function() {\n\t\t\t\t\tEMIT('cart.changed', ++count, 'button');\n\t\t\t\t});\n\t\t\t};\n\t\t});\n\n\t\tfunction listener(count, source) {\n\t\t\treceived.push(count + ':' + source);\n\t\t\t$('.shown').text('Count: ' + count);\n\t\t}\n\n\t\tON('cart.changed', listener);\n\n\t</script>\n\n</body>\n</html>\n```"}],"knowledge":{"content_sha256":"e7d942ca05d5a424fe982d323480c094f46cf37282715241724cac08fd6952b7","source_sha256":"096933b231d3325c21e7fedbf2c2766484558d909af2b96cf665fadfe23eceae","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"jcomponent20","url":"https://github.com/totaljs/jComponent","revision":"3fedd009a277f50fddc608b97afbec553b67c51b","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"01ce7c4d3dbd3d14b5a7ca9f44172ed332fa1eed250505811d4f828b38254bf9","license_files":[{"path":"LICENSE","sha256":"abf1260b51729c2fc0da7917e3564878725212bd7859683e7d5af91b6f9192bd","text":true,"characters":1095},{"path":"license.txt","sha256":"7226fe6e0be959f3c5e337bec8ca39ec08e694c8051dd7bc09d3e1a242abdc88","text":true,"characters":1099}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["COMPONENT","DOCTYPE","EMIT","OFF"],"language":"en"},"files":[{"path":"public/index.html","language":"html","content":"<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<ui-component name=\"counter\"></ui-component>\n\t<p class=\"shown\"></p>\n\n\t<script>\n\n\t\tvar received = [];\n\n\t\tCOMPONENT('counter', function(self) {\n\t\t\tself.make = function() {\n\t\t\t\tself.html('<button type=\"button\">+1</button>');\n\t\t\t\tvar count = 0;\n\t\t\t\tself.find('button').on('click', function() {\n\t\t\t\t\tEMIT('cart.changed', ++count, 'button');\n\t\t\t\t});\n\t\t\t};\n\t\t});\n\n\t\tfunction listener(count, source) {\n\t\t\treceived.push(count + ':' + source);\n\t\t\t$('.shown').text('Count: ' + count);\n\t\t}\n\n\t\tON('cart.changed', listener);\n\n\t</script>\n\n</body>\n</html>"}],"test":"const results = [];\nconst check = (ok, msg) => ok || results.push(msg);\nconst until = async (fn, ms = 4000) => { const t = Date.now(); while (Date.now() - t < ms) { try { if (fn()) return true; } catch (e) {} await new Promise(r => setTimeout(r, 50)); } return false; };\nawait until(() => $('button').length);\n$('button').trigger('click');\n$('button').trigger('click');\nawait until(() => received.length === 2);\ncheck(received.join() === '1:button,2:button', 'the listener receives the arguments of EMIT, got: ' + received.join());\ncheck($('.shown').text() === 'Count: 2', 'the page reacts, got: ' + $('.shown').text());\nOFF('cart.changed', listener);\n$('button').trigger('click');\nawait new Promise(r => setTimeout(r, 300));\ncheck(received.length === 2, 'after OFF the listener is not called, got: ' + received.join());\nresults","test_kind":"browser"}
{"id":"jcomponent/storage-cache-pref-cookies","section":"jcomponent","kind":"recipe","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/jcomponent/storage-cache-pref-cookies/","markdown_url":"https://forge.totaljs.es/en/jcomponent/storage-cache-pref-cookies.md","tags":["jcomponent","frontend","storage"],"license":"CC-BY-4.0","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"1232608cd34163075d7cab5931686c60766ca258b3a94aa5e6151cc7218c49aa","checked_at":"2026-10-20T12:19:34.352Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"browser","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Store data in the browser with CACHE, PREF and COOKIES","description":"Keep values between visits with CACHE (expiring), PREF (user preferences) and COOKIES.","messages":[{"role":"user","content":"Store data in the browser with CACHE, PREF and COOKIES\n\nKeep values between visits with CACHE (expiring), PREF (user preferences) and COOKIES."},{"role":"assistant","content":"Three helpers store data in the browser:\n\n- `CACHE(key, value, expiration)` stores any value for a period (`'10 minutes'`, `'1 day'`) and `CACHE(key)` reads it, giving `null` once it expired. The data lives in the browser storage, so it survives a reload.\n- `PREF.set(name, value)` stores a user preference for a year; read it back as `PREF.name`.\n- `COOKIES.set(name, value, expiration)`, `COOKIES.get(name)` and `COOKIES.rem(name)` work with real cookies, which the server receives on every request.\n\n```html public/index.html\n<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<p class=\"theme\"></p>\n\n\t<script>\n\n\t\tCACHE('profile', { name: 'Peter', age: 42 }, '1 hour');\n\t\tCACHE('short', 'value', '1 second');\n\n\t\tPREF.set('theme', 'dark');\n\t\t$('.theme').text('Theme: ' + PREF.theme);\n\n\t\tCOOKIES.set('visited', 'yes', '1 day');\n\n\t</script>\n\n</body>\n</html>\n```"}],"knowledge":{"content_sha256":"bdb8f20a9c8cdbd741b0e9c31c4c330560cfbbead4075c9e1a7b00df8746650a","source_sha256":"1232608cd34163075d7cab5931686c60766ca258b3a94aa5e6151cc7218c49aa","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"jcomponent20","url":"https://github.com/totaljs/jComponent","revision":"3fedd009a277f50fddc608b97afbec553b67c51b","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"01ce7c4d3dbd3d14b5a7ca9f44172ed332fa1eed250505811d4f828b38254bf9","license_files":[{"path":"LICENSE","sha256":"abf1260b51729c2fc0da7917e3564878725212bd7859683e7d5af91b6f9192bd","text":true,"characters":1095},{"path":"license.txt","sha256":"7226fe6e0be959f3c5e337bec8ca39ec08e694c8051dd7bc09d3e1a242abdc88","text":true,"characters":1099}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CACHE","COOKIES","DOCTYPE","PREF"],"language":"en"},"files":[{"path":"public/index.html","language":"html","content":"<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<p class=\"theme\"></p>\n\n\t<script>\n\n\t\tCACHE('profile', { name: 'Peter', age: 42 }, '1 hour');\n\t\tCACHE('short', 'value', '1 second');\n\n\t\tPREF.set('theme', 'dark');\n\t\t$('.theme').text('Theme: ' + PREF.theme);\n\n\t\tCOOKIES.set('visited', 'yes', '1 day');\n\n\t</script>\n\n</body>\n</html>"}],"test":"const results = [];\nconst check = (ok, msg) => ok || results.push(msg);\nconst until = async (fn, ms = 4000) => { const t = Date.now(); while (Date.now() - t < ms) { try { if (fn()) return true; } catch (e) {} await new Promise(r => setTimeout(r, 50)); } return false; };\nawait until(() => $('.theme').text() === 'Theme: dark');\nconst profile = CACHE('profile');\ncheck(profile && profile.name === 'Peter' && profile.age === 42, 'CACHE stores and returns an object, got: ' + JSON.stringify(profile));\ncheck(CACHE('short') === 'value', 'a value is available before it expires');\ncheck(PREF.theme === 'dark' && PREF.get('theme') === 'dark', 'PREF returns the preference');\ncheck(COOKIES.get('visited') === 'yes', 'COOKIES.get reads the cookie, got: ' + COOKIES.get('visited'));\nCOOKIES.rem('visited');\ncheck(COOKIES.get('visited') === '', 'COOKIES.rem removes the cookie, got: ' + COOKIES.get('visited'));\nawait new Promise(r => setTimeout(r, 1500));\ncheck(CACHE('short') == null, 'an expired value is gone, got: ' + CACHE('short'));\nresults","test_kind":"browser"}
{"id":"jcomponent/environment-variables","section":"jcomponent","kind":"recipe","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/jcomponent/environment-variables/","markdown_url":"https://forge.totaljs.es/en/jcomponent/environment-variables.md","tags":["jcomponent","frontend","config"],"license":"CC-BY-4.0","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"b56a1206a13ddaf2f5b14cfabbd7e80c156e70db5699468bddf66e55aab4d6d2","checked_at":"2026-10-20T12:19:21.438Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"browser","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Share settings with ENV and the [key] syntax","description":"Register values with ENV() and use them in URLs and component configuration with the [key] placeholder.","messages":[{"role":"user","content":"Share settings with ENV and the [key] syntax\n\nRegister values with ENV() and use them in URLs and component configuration with the [key] placeholder."},{"role":"assistant","content":"`ENV(name, value)` registers a value for the whole page and `ENV(name)` reads it. Wherever jComponent accepts a text that may contain `[name]` (for example the URL of `AJAX()` and `IMPORT()`), the placeholder is replaced with the value, so you set the address of your API in one place. `'[name]'.env()` does the same replacement on any string.\n\n```html public/index.html\n<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<p class=\"out\"></p>\n\n\t<script>\n\n\t\tENV('api', '/api/v1');\n\t\tENV('version', '20');\n\n\t\t$('.out').text('[api]/users?v=[version]'.env());\n\n\t</script>\n\n</body>\n</html>\n```"}],"knowledge":{"content_sha256":"bc463ba39c988765498f2434cfafd091e63bc7d3b4acc44df26da46b70c5b105","source_sha256":"b56a1206a13ddaf2f5b14cfabbd7e80c156e70db5699468bddf66e55aab4d6d2","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"jcomponent20","url":"https://github.com/totaljs/jComponent","revision":"3fedd009a277f50fddc608b97afbec553b67c51b","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"01ce7c4d3dbd3d14b5a7ca9f44172ed332fa1eed250505811d4f828b38254bf9","license_files":[{"path":"LICENSE","sha256":"abf1260b51729c2fc0da7917e3564878725212bd7859683e7d5af91b6f9192bd","text":true,"characters":1095},{"path":"license.txt","sha256":"7226fe6e0be959f3c5e337bec8ca39ec08e694c8051dd7bc09d3e1a242abdc88","text":true,"characters":1099}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["AJAX","API","DOCTYPE","ENV","IMPORT","URL"],"language":"en"},"files":[{"path":"public/index.html","language":"html","content":"<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<p class=\"out\"></p>\n\n\t<script>\n\n\t\tENV('api', '/api/v1');\n\t\tENV('version', '20');\n\n\t\t$('.out').text('[api]/users?v=[version]'.env());\n\n\t</script>\n\n</body>\n</html>"}],"test":"const results = [];\nconst check = (ok, msg) => ok || results.push(msg);\nconst until = async (fn, ms = 4000) => { const t = Date.now(); while (Date.now() - t < ms) { try { if (fn()) return true; } catch (e) {} await new Promise(r => setTimeout(r, 50)); } return false; };\nawait until(() => $('.out').text());\ncheck(ENV('api') === '/api/v1', 'ENV reads the value, got: ' + ENV('api'));\ncheck($('.out').text() === '/api/v1/users?v=20', 'the [key] placeholders are replaced, got: ' + $('.out').text());\nENV('api', '/api/v2');\ncheck('[api]/users'.env() === '/api/v2/users', 'a new value is used from then on');\nresults","test_kind":"browser"}
{"id":"jcomponent/ajax","section":"jcomponent","kind":"recipe","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/jcomponent/ajax/","markdown_url":"https://forge.totaljs.es/en/jcomponent/ajax.md","tags":["jcomponent","frontend","ajax","routing"],"license":"CC-BY-4.0","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"1baf4333927d61f1870342c17f6a8367a43b61136ac989a4ca3118b0a3f5ae3e","checked_at":"2026-10-20T12:19:09.090Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"http","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Load data from a Total.js route with AJAX","description":"Call a Total.js route from the page with AJAX() and put the response into the jComponent state.","messages":[{"role":"user","content":"Load data from a Total.js route with AJAX\n\nCall a Total.js route from the page with AJAX() and put the response into the jComponent state."},{"role":"assistant","content":"`AJAX('METHOD /url/', [data], callback)` sends a request and calls the callback with the response. JSON responses are parsed for you. Combine it with `SET()` and any `ui-bind` or component bound to the path updates by itself.\n\n```js controllers/api.js\nexports.install = function() {\n\tROUTE('GET /api/hello/', hello);\n};\n\nfunction hello($) {\n\t$.json({ message: 'Hello from Total.js' });\n}\n```\n\n```html public/index.html\n<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\n\t<p><ui-bind path=\"greeting\" config=\"text:value\">Loading...</ui-bind></p>\n\n\t<script>\n\t\tAJAX('GET /api/hello/', function(response) {\n\t\t\tSET('greeting', response.message);\n\t\t});\n\t</script>\n\n</body>\n</html>\n```"}],"knowledge":{"content_sha256":"72d9f15885081fa9a0eb755ef2c0e76646960a862b815ae9cf970b6cdb2de691","source_sha256":"1baf4333927d61f1870342c17f6a8367a43b61136ac989a4ca3118b0a3f5ae3e","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"jcomponent20","url":"https://github.com/totaljs/jComponent","revision":"3fedd009a277f50fddc608b97afbec553b67c51b","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"01ce7c4d3dbd3d14b5a7ca9f44172ed332fa1eed250505811d4f828b38254bf9","license_files":[{"path":"LICENSE","sha256":"abf1260b51729c2fc0da7917e3564878725212bd7859683e7d5af91b6f9192bd","text":true,"characters":1095},{"path":"license.txt","sha256":"7226fe6e0be959f3c5e337bec8ca39ec08e694c8051dd7bc09d3e1a242abdc88","text":true,"characters":1099}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["AJAX","DOCTYPE","GET","JSON","METHOD","ROUTE","SET","Total.js"],"language":"en"},"files":[{"path":"controllers/api.js","language":"js","content":"exports.install = function() {\n\tROUTE('GET /api/hello/', hello);\n};\n\nfunction hello($) {\n\t$.json({ message: 'Hello from Total.js' });\n}"},{"path":"public/index.html","language":"html","content":"<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\n\t<p><ui-bind path=\"greeting\" config=\"text:value\">Loading...</ui-bind></p>\n\n\t<script>\n\t\tAJAX('GET /api/hello/', function(response) {\n\t\t\tSET('greeting', response.message);\n\t\t});\n\t</script>\n\n</body>\n</html>"}],"test":"const res = await get('/api/hello/');\nassert(res.status === 200 && res.json.message === 'Hello from Total.js', 'the route answers with JSON');","test_kind":"http"}
{"id":"jcomponent/api-calls","section":"jcomponent","kind":"recipe","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/jcomponent/api-calls/","markdown_url":"https://forge.totaljs.es/en/jcomponent/api-calls.md","tags":["jcomponent","frontend","ajax","api"],"license":"CC-BY-4.0","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"9b176b0c79cb3033c3f2af8f21f0f9329e85139ea9a0da7c0bdc89f94fe534e3","checked_at":"2026-10-20T12:19:09.927Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"browser","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Call Total.js API routes with API()","description":"Use API(url, name, data, callback) to call the actions of a ROUTE('API ...') endpoint, validation errors included.","messages":[{"role":"user","content":"Call Total.js API routes with API()\n\nUse API(url, name, data, callback) to call the actions of a ROUTE('API ...') endpoint, validation errors included."},{"role":"assistant","content":"`API(url, name, data, callback)` is the client side of the Total.js `ROUTE('API ...')` endpoints. It sends `POST url` with the body `{ schema: name, data: data }` and calls the callback with the response. When the server answers with an error (for example a validation error) the callback receives an array of errors: use the `ERROR(response, success, error)` helper to handle both cases in one place.\n\nPut the address of the endpoint in `ENV()` once and reuse it.\n\n```js definitions/math.js\nNEWACTION('Math/add', {\n\tname: 'Add two numbers',\n\tinput: '*a:Number, *b:Number',\n\taction: function($, model) {\n\t\t$.callback({ sum: model.a + model.b });\n\t}\n});\n```\n\n```js controllers/api.js\nexports.install = function() {\n\tROUTE('API /api/ +math_add --> Math/add');\n};\n```\n\n```html public/index.html\n<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<p class=\"sum\"></p>\n\t<p class=\"error\"></p>\n\n\t<script>\n\n\t\tENV('api', '/api/');\n\n\t\tAPI('[api]', 'math_add', { a: 20, b: 22 }, function(response) {\n\t\t\tSET('sum', response.sum);\n\t\t});\n\n\t\tAPI('/api/', 'math_add', { a: 'x' }, function(response) {\n\t\t\tERROR(response, function(value) {\n\t\t\t\tSET('failed', 'unexpected success');\n\t\t\t}, function(errors) {\n\t\t\t\tSET('failed', errors[0].name);\n\t\t\t});\n\t\t});\n\n\t</script>\n\n</body>\n</html>\n```"}],"knowledge":{"content_sha256":"7d1c02bbcfea4e2f336b0f7c326061679b2258d315e46891c6b4e67046dc2b94","source_sha256":"9b176b0c79cb3033c3f2af8f21f0f9329e85139ea9a0da7c0bdc89f94fe534e3","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"jcomponent20","url":"https://github.com/totaljs/jComponent","revision":"3fedd009a277f50fddc608b97afbec553b67c51b","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"01ce7c4d3dbd3d14b5a7ca9f44172ed332fa1eed250505811d4f828b38254bf9","license_files":[{"path":"LICENSE","sha256":"abf1260b51729c2fc0da7917e3564878725212bd7859683e7d5af91b6f9192bd","text":true,"characters":1095},{"path":"license.txt","sha256":"7226fe6e0be959f3c5e337bec8ca39ec08e694c8051dd7bc09d3e1a242abdc88","text":true,"characters":1099}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["API","DOCTYPE","ENV","ERROR","NEWACTION","POST","ROUTE","SET","Total.js"],"language":"en"},"files":[{"path":"definitions/math.js","language":"js","content":"NEWACTION('Math/add', {\n\tname: 'Add two numbers',\n\tinput: '*a:Number, *b:Number',\n\taction: function($, model) {\n\t\t$.callback({ sum: model.a + model.b });\n\t}\n});"},{"path":"controllers/api.js","language":"js","content":"exports.install = function() {\n\tROUTE('API /api/ +math_add --> Math/add');\n};"},{"path":"public/index.html","language":"html","content":"<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<p class=\"sum\"></p>\n\t<p class=\"error\"></p>\n\n\t<script>\n\n\t\tENV('api', '/api/');\n\n\t\tAPI('[api]', 'math_add', { a: 20, b: 22 }, function(response) {\n\t\t\tSET('sum', response.sum);\n\t\t});\n\n\t\tAPI('/api/', 'math_add', { a: 'x' }, function(response) {\n\t\t\tERROR(response, function(value) {\n\t\t\t\tSET('failed', 'unexpected success');\n\t\t\t}, function(errors) {\n\t\t\t\tSET('failed', errors[0].name);\n\t\t\t});\n\t\t});\n\n\t</script>\n\n</body>\n</html>"}],"test":"const results = [];\nconst check = (ok, msg) => ok || results.push(msg);\nconst until = async (fn, ms = 4000) => { const t = Date.now(); while (Date.now() - t < ms) { try { if (fn()) return true; } catch (e) {} await new Promise(r => setTimeout(r, 50)); } return false; };\nawait until(() => GET('sum') === 42 && GET('failed'));\ncheck(GET('sum') === 42, 'API sends the data and returns the response, got: ' + GET('sum'));\ncheck(GET('failed') === 'a', 'a validation error arrives as an array with the field name, got: ' + GET('failed'));\nresults","test_kind":"browser"}
{"id":"jcomponent/upload","section":"jcomponent","kind":"recipe","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/jcomponent/upload/","markdown_url":"https://forge.totaljs.es/en/jcomponent/upload.md","tags":["jcomponent","frontend","upload","files"],"license":"CC-BY-4.0","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"46b8c005545bac1520619afaa3bcbd4e2bf2b2a821da7fa16bd91f7f3e01468b","checked_at":"2026-10-20T12:19:40.752Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"browser","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Upload files with UPLOAD() and progress","description":"Send a FormData to a Total.js @upload route with UPLOAD() and follow the progress.","messages":[{"role":"user","content":"Upload files with UPLOAD() and progress\n\nSend a FormData to a Total.js @upload route with UPLOAD() and follow the progress."},{"role":"assistant","content":"`UPLOAD('POST /url/', formdata, callback, onprogress)` posts a `FormData` (the address starts with the HTTP method, like in `AJAX()`) and calls the callback with the parsed response. The optional fourth argument receives the percentage while the upload runs. Build the `FormData` from a file input (`new FormData()` and `append('file', input.files[0])`); the server side is the `@upload` route of the file upload recipe.\n\n```js controllers/upload.js\nexports.install = function() {\n\tROUTE('POST /upload/ @upload <5mb', upload);\n};\n\nfunction upload($) {\n\n\tvar file = $.files[0];\n\n\tfile.read(function(err, buffer) {\n\t\t$.json({ filename: file.filename, text: buffer.toString('utf8'), title: $.body.title });\n\t});\n}\n```\n\n```html public/index.html\n<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<script>\n\n\t\tvar form = new FormData();\n\t\tform.append('title', 'My note');\n\t\tform.append('file', new Blob(['hello from the browser'], { type: 'text/plain' }), 'note.txt');\n\n\t\tvar progress = [];\n\n\t\tUPLOAD('POST /upload/', form, function(response) {\n\t\t\tSET('uploaded', response);\n\t\t}, function(percentage) {\n\t\t\tprogress.push(percentage);\n\t\t});\n\n\t</script>\n\n</body>\n</html>\n```"}],"knowledge":{"content_sha256":"da88694097efc0c028b9c3f06e8e97cf47f582786aa0d893029af4e08f134cef","source_sha256":"46b8c005545bac1520619afaa3bcbd4e2bf2b2a821da7fa16bd91f7f3e01468b","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"jcomponent20","url":"https://github.com/totaljs/jComponent","revision":"3fedd009a277f50fddc608b97afbec553b67c51b","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"01ce7c4d3dbd3d14b5a7ca9f44172ed332fa1eed250505811d4f828b38254bf9","license_files":[{"path":"LICENSE","sha256":"abf1260b51729c2fc0da7917e3564878725212bd7859683e7d5af91b6f9192bd","text":true,"characters":1095},{"path":"license.txt","sha256":"7226fe6e0be959f3c5e337bec8ca39ec08e694c8051dd7bc09d3e1a242abdc88","text":true,"characters":1099}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["AJAX","DOCTYPE","HTTP","POST","ROUTE","SET","UPLOAD"],"language":"en"},"files":[{"path":"controllers/upload.js","language":"js","content":"exports.install = function() {\n\tROUTE('POST /upload/ @upload <5mb', upload);\n};\n\nfunction upload($) {\n\n\tvar file = $.files[0];\n\n\tfile.read(function(err, buffer) {\n\t\t$.json({ filename: file.filename, text: buffer.toString('utf8'), title: $.body.title });\n\t});\n}"},{"path":"public/index.html","language":"html","content":"<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<script>\n\n\t\tvar form = new FormData();\n\t\tform.append('title', 'My note');\n\t\tform.append('file', new Blob(['hello from the browser'], { type: 'text/plain' }), 'note.txt');\n\n\t\tvar progress = [];\n\n\t\tUPLOAD('POST /upload/', form, function(response) {\n\t\t\tSET('uploaded', response);\n\t\t}, function(percentage) {\n\t\t\tprogress.push(percentage);\n\t\t});\n\n\t</script>\n\n</body>\n</html>"}],"test":"const results = [];\nconst check = (ok, msg) => ok || results.push(msg);\nconst until = async (fn, ms = 4000) => { const t = Date.now(); while (Date.now() - t < ms) { try { if (fn()) return true; } catch (e) {} await new Promise(r => setTimeout(r, 50)); } return false; };\nawait until(() => GET('uploaded'));\nconst r = GET('uploaded');\ncheck(r && r.filename === 'note.txt', 'the server received the file, got: ' + JSON.stringify(r));\ncheck(r && r.text === 'hello from the browser' && r.title === 'My note', 'the content and the field arrived, got: ' + JSON.stringify(r));\nresults","test_kind":"browser"}
{"id":"jcomponent/utilities","section":"jcomponent","kind":"recipe","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/jcomponent/utilities/","markdown_url":"https://forge.totaljs.es/en/jcomponent/utilities.md","tags":["jcomponent","frontend","utilities"],"license":"CC-BY-4.0","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"f8c69870e5955a9d9a43b171a239b83998bdba824b20d601c8e65b4285a2379a","checked_at":"2026-10-20T12:19:41.539Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"browser","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Small helpers: GUID, HASH, parseConfig, format and group","description":"Use the helper functions that jComponent adds: random ids, hashes, config strings, string and date formatting and grouping.","messages":[{"role":"user","content":"Small helpers: GUID, HASH, parseConfig, format and group\n\nUse the helper functions that jComponent adds: random ids, hashes, config strings, string and date formatting and grouping."},{"role":"assistant","content":"jComponent adds a few helpers that save you from loading a utility library:\n\n- `GUID(length)` returns a random id; `HASH(value)` returns a numeric hash of any value\n- `'key:value;key2:value2'.parseConfig()` parses a configuration string into an object, converting numbers and booleans\n- `'{0} is {1}'.format('a', 'b')` replaces `{index}` placeholders\n- `number.format(decimals, thousandsSeparator, decimalSeparator)` and `date.format('yyyy-MM-dd')` format numbers and dates\n- `array.group('field')` groups objects by a field into `[{ name, items }]`\n- `PARSE(json)` parses JSON and converts date strings into `Date` objects; `date.add('1 day')` moves a date\n\n```html public/index.html\n<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<p class=\"out\"></p>\n\n\t<script>\n\n\t\twindow.sample = {\n\t\t\tguid: GUID(12),\n\t\t\tsamehash: HASH('total') === HASH('total'),\n\t\t\tconfig: 'size:3;active:true;name:Peter'.parseConfig(),\n\t\t\ttext: '{0} is {1}'.format('Total.js', 'great'),\n\t\t\tprice: (1234.5).format(2),\n\t\t\tdate: new Date(2026, 9, 16).format('yyyy-MM-dd'),\n\t\t\tgroups: [{ type: 'a', n: 1 }, { type: 'b', n: 2 }, { type: 'a', n: 3 }].group('type'),\n\t\t\tparsed: PARSE('{\"created\":\"2026-10-16T10:00:00.000Z\"}').created instanceof Date,\n\t\t\ttomorrow: new Date(2026, 9, 16).add('1 day').getDate()\n\t\t};\n\n\t</script>\n\n</body>\n</html>\n```"}],"knowledge":{"content_sha256":"b9e420d51ad53b7df7e6efd61f353dd2e8437a96bbefa7c096a92090eb39e52c","source_sha256":"f8c69870e5955a9d9a43b171a239b83998bdba824b20d601c8e65b4285a2379a","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"jcomponent20","url":"https://github.com/totaljs/jComponent","revision":"3fedd009a277f50fddc608b97afbec553b67c51b","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"01ce7c4d3dbd3d14b5a7ca9f44172ed332fa1eed250505811d4f828b38254bf9","license_files":[{"path":"LICENSE","sha256":"abf1260b51729c2fc0da7917e3564878725212bd7859683e7d5af91b6f9192bd","text":true,"characters":1095},{"path":"license.txt","sha256":"7226fe6e0be959f3c5e337bec8ca39ec08e694c8051dd7bc09d3e1a242abdc88","text":true,"characters":1099}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["DOCTYPE","GUID","HASH","JSON","PARSE","Total.js"],"language":"en"},"files":[{"path":"public/index.html","language":"html","content":"<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<p class=\"out\"></p>\n\n\t<script>\n\n\t\twindow.sample = {\n\t\t\tguid: GUID(12),\n\t\t\tsamehash: HASH('total') === HASH('total'),\n\t\t\tconfig: 'size:3;active:true;name:Peter'.parseConfig(),\n\t\t\ttext: '{0} is {1}'.format('Total.js', 'great'),\n\t\t\tprice: (1234.5).format(2),\n\t\t\tdate: new Date(2026, 9, 16).format('yyyy-MM-dd'),\n\t\t\tgroups: [{ type: 'a', n: 1 }, { type: 'b', n: 2 }, { type: 'a', n: 3 }].group('type'),\n\t\t\tparsed: PARSE('{\"created\":\"2026-10-16T10:00:00.000Z\"}').created instanceof Date,\n\t\t\ttomorrow: new Date(2026, 9, 16).add('1 day').getDate()\n\t\t};\n\n\t</script>\n\n</body>\n</html>"}],"test":"const results = [];\nconst check = (ok, msg) => ok || results.push(msg);\nconst until = async (fn, ms = 4000) => { const t = Date.now(); while (Date.now() - t < ms) { try { if (fn()) return true; } catch (e) {} await new Promise(r => setTimeout(r, 50)); } return false; };\nconst s = window.sample;\ncheck(s.guid.length === 12, 'GUID has the requested length, got: ' + s.guid);\ncheck(s.samehash === true, 'HASH is stable');\ncheck(s.config.size === 3 && s.config.active === true && s.config.name === 'Peter', 'parseConfig converts types, got: ' + JSON.stringify(s.config));\ncheck(s.text === 'Total.js is great', 'format replaces placeholders, got: ' + s.text);\ncheck(/^1\\s?234\\.50$/.test(s.price), 'number format, got: ' + s.price);\ncheck(s.date === '2026-10-16', 'date format, got: ' + s.date);\ncheck(s.groups.length === 2 && s.groups[0].name === 'a' && s.groups[0].items.length === 2 && s.groups[1].items.length === 1, 'group returns [{ name, items }], got: ' + JSON.stringify(s.groups));\ncheck(s.parsed === true, 'PARSE converts date strings to Date');\ncheck(s.tomorrow === 17, 'date.add moves the date, got: ' + s.tomorrow);\nresults","test_kind":"browser"}
{"id":"jcomponent/state-shortcuts","section":"jcomponent","kind":"recipe","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/jcomponent/state-shortcuts/","markdown_url":"https://forge.totaljs.es/en/jcomponent/state-shortcuts.md","tags":["jcomponent","frontend","state","utilities"],"license":"CC-BY-4.0","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"3f66aa11a154d4586cf10b299ba451f8df0b725bf47d3731ae5c0d941ed5379d","checked_at":"2026-10-20T12:19:32.050Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"browser","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"State shortcuts: INC, TOGGLE, NUL, UPDATE, UNWATCH, WAIT, BLOCKED and CACHEPATH","description":"Small functions that change or watch a value of the state by its path, wait until a value exists, run something only once in a period and keep a value of the state in the cache.","messages":[{"role":"user","content":"State shortcuts: INC, TOGGLE, NUL, UPDATE, UNWATCH, WAIT, BLOCKED and CACHEPATH\n\nSmall functions that change or watch a value of the state by its path, wait until a value exists, run something only once in a period and keep a value of the state in the cache."},{"role":"assistant","content":"These functions work on the state of jComponent by **path** (`'user.visits'`), like `SET()` and `GET()`, and every one of them notifies the components and the watchers of the path.\n\n- `INC(path, [value])` adds `value` (default `1`) to a number; when the value is empty it starts from `0` and when it is a text it is read as a number first.\n- `TOGGLE(path)` changes a boolean (an empty value becomes `true`). `NUL(path)` and its alias `NULL(path)` set the value to `null`.\n- `UPDATE(path)` (alias `UPD`) notifies everybody who uses the path **without changing the value**: use it after you changed an object or an array in place.\n- `UNWATCH(path, [fn])` removes the watchers of the path made with `WATCH()`; with `fn` only that one.\n- `WAIT(fn or path, callback)` runs `callback` as soon as the function returns something true, or the path has a value. It checks at once and then **every 500 ms**.\n- `BLOCKED(key, timeout, [callback])` returns `false` the first time and `true` for the next `timeout` ms (`'5 seconds'` also works): it is a guard against a double click or a repeated request. The `callback` runs only when it is not blocked.\n- `CACHEPATH(path, expire, [default])` keeps a value of the state in the cache (`CACHE()`): it sets the path to the cached value when there is one (or to `default`, which can be a function) and saves every change under the key `path_<path>`.\n- A path is a path in the state of the page, so a name like `name` or `location` is a property of `window`: use names of your own (`person`, `prefs.mode`).\n- `setTimeout2(id, fn, ms, [limit], [param])` is a debounced `setTimeout()`: calling it again with the same `id` restarts the timer, so `fn` runs once, `ms` after the last call. With `limit` the timer is restarted at most that many times and the following calls are ignored until `fn` has run, so a stream of calls cannot postpone it forever. `clearTimeout2(id)` cancels it and returns `true` when there was one.\n- `CLONE(value)` makes a deep copy (dates stay dates), `COPY(from, to)` copies the properties of one object into another, deep, and returns `to`. `NOOP` is a function that does nothing and `WARN(...)` writes to `console.warn`.\n\n```html public/index.html\n<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<p class=\"out\"></p>\n\t<script>\n\n\t\twindow.sample = { watched: 0 };\n\n\t</script>\n</body>\n</html>\n```"}],"knowledge":{"content_sha256":"ba5b6aa6fcd769def5a014d40e4f3a44daaac5d6226829ae150b35912a5664de","source_sha256":"3f66aa11a154d4586cf10b299ba451f8df0b725bf47d3731ae5c0d941ed5379d","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"jcomponent20","url":"https://github.com/totaljs/jComponent","revision":"3fedd009a277f50fddc608b97afbec553b67c51b","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"01ce7c4d3dbd3d14b5a7ca9f44172ed332fa1eed250505811d4f828b38254bf9","license_files":[{"path":"LICENSE","sha256":"abf1260b51729c2fc0da7917e3564878725212bd7859683e7d5af91b6f9192bd","text":true,"characters":1095},{"path":"license.txt","sha256":"7226fe6e0be959f3c5e337bec8ca39ec08e694c8051dd7bc09d3e1a242abdc88","text":true,"characters":1099}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["BLOCKED","CACHE","CACHEPATH","CLONE","COPY","DOCTYPE","GET","INC","NOOP","NUL","NULL","SET","TOGGLE","UNWATCH","UPD","UPDATE","WAIT","WARN","WATCH"],"language":"en"},"files":[{"path":"public/index.html","language":"html","content":"<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<p class=\"out\"></p>\n\t<script>\n\n\t\twindow.sample = { watched: 0 };\n\n\t</script>\n</body>\n</html>"}],"test":"const results = [];\nconst check = (ok, msg) => ok || results.push(msg);\nconst sleep = ms => new Promise(r => setTimeout(r, ms));\n\nSET('count', 5);\nINC('count');\nINC('count', 10);\ncheck(GET('count') === 16, 'INC adds 1 or the value, got: ' + GET('count'));\nSET('text', '4');\nINC('text');\ncheck(GET('text') === 5, 'INC reads a text as a number, got: ' + GET('text'));\nINC('fresh');\ncheck(GET('fresh') === 1, 'INC starts from 0, got: ' + GET('fresh'));\n\nSET('flag', true);\nTOGGLE('flag');\ncheck(GET('flag') === false, 'TOGGLE changes true to false');\nTOGGLE('nothing');\ncheck(GET('nothing') === true, 'TOGGLE of an empty value gives true');\n\nSET('person', 'x');\nSET('person2', 'y');\nNUL('person');\nNULL('person2');\ncheck(GET('person') === null && GET('person2') === null, 'NUL and NULL set null');\n\n// UPDATE notifies without a change; UNWATCH stops the watcher\nlet calls = 0;\nconst watcher = () => calls++;\nWATCH('list', watcher);\nSET('list', [1]);\nconst afterset = calls;\nGET('list').push(2);\nUPDATE('list');\ncheck(calls === afterset + 1, 'UPDATE notifies the watchers of the path, got: ' + calls);\nUNWATCH('list', watcher);\nSET('list', [3]);\nUPD('list');\ncheck(calls === afterset + 1, 'UNWATCH removed the watcher, got: ' + calls);\n\n// WAIT: a function and a path, checked every 500 ms\nlet byfn = false;\nlet bypath = false;\nWAIT(() => GET('later'), () => byfn = true);\nWAIT('later', () => bypath = true);\nawait sleep(200);\ncheck(!byfn && !bypath, 'WAIT does not run while there is no value');\nSET('later', 'now');\nawait sleep(1100);\ncheck(byfn && bypath, 'WAIT ran both callbacks after the value came, got: ' + byfn + ' ' + bypath);\nlet immediate = false;\nWAIT('later', () => immediate = true);\ncheck(immediate === true, 'WAIT runs at once when the value is already there');\n\n// BLOCKED\nlet runs = 0;\nconst first = BLOCKED('save', 300, () => runs++);\nconst second = BLOCKED('save', 300, () => runs++);\ncheck(first === false && second === true && runs === 1, 'BLOCKED lets the first call pass and blocks the next, got: ' + [first, second, runs]);\nawait sleep(400);\ncheck(BLOCKED('save', 300) === false, 'BLOCKED lets it pass again after the time');\n\n// CACHEPATH\nCACHEPATH('prefs.mode', '1 day', 'light');\ncheck(GET('prefs.mode') === 'light', 'CACHEPATH sets the default, got: ' + GET('prefs.mode'));\nSET('prefs.mode', 'dark');\ncheck(CACHE('path_prefs.mode') === 'dark', 'every change is saved in the cache, got: ' + CACHE('path_prefs.mode'));\n\n// CLONE, COPY, NOOP\nconst original = { n: { x: 1 }, d: new Date(2026, 0, 2), list: [1, 2] };\nconst clone = CLONE(original);\nclone.n.x = 9;\nclone.list.push(3);\ncheck(original.n.x === 1 && original.list.length === 2 && clone.d instanceof Date && clone.d.getTime() === original.d.getTime(), 'CLONE makes a deep copy and keeps the dates');\nconst target = COPY({ a: 1, inner: { y: 1 } }, { z: 1 });\ncheck(target.z === 1 && target.a === 1 && target.inner.y === 1, 'COPY adds the properties to the target, got: ' + JSON.stringify(target));\ncheck(typeof NOOP === 'function' && NOOP() === undefined, 'NOOP does nothing');\n\n// setTimeout2: a debounce, with and without limit; clearTimeout2\nlet ran = 0;\nfor (let i = 0; i < 5; i++)\n\tsetTimeout2('save', () => ran++, 100);\nawait sleep(250);\ncheck(ran === 1, 'five calls with the same id run once, got: ' + ran);\n\nlet limited = 0;\nfor (let i = 0; i < 10; i++)\n\tsetTimeout2('limited', () => limited++, 100, 3);\nawait sleep(250);\ncheck(limited === 1, 'with a limit the calls over the limit are ignored and the function runs once, got: ' + limited);\n\nlet cancelled = 0;\nsetTimeout2('cancel', () => cancelled++, 100);\ncheck(clearTimeout2('cancel') === true && clearTimeout2('cancel') !== true, 'clearTimeout2 returns true when it cancelled one');\nawait sleep(200);\ncheck(cancelled === 0, 'a cancelled timer does not run');\n\nresults","test_kind":"browser"}
{"id":"jcomponent/data-helpers","section":"jcomponent","kind":"recipe","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/jcomponent/data-helpers/","markdown_url":"https://forge.totaljs.es/en/jcomponent/data-helpers.md","tags":["jcomponent","frontend","utilities","templates"],"license":"CC-BY-4.0","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"fa9ad3dbea9989e2ae3eaaba32943ace3c4365832520c80b2c6c8f1202908ac4","checked_at":"2026-10-20T12:19:19.855Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"browser","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Data helpers: QUERIFY, STRINGIFY, ENCRYPT, FN, CL, TRANSLATE, UNAUTHORIZED and Thelpers","description":"Build query strings, write JSON without what you do not want, encode a value with a key, compile an expression, load lists of values, translate texts, check permissions and add helpers to the templates.","messages":[{"role":"user","content":"Data helpers: QUERIFY, STRINGIFY, ENCRYPT, FN, CL, TRANSLATE, UNAUTHORIZED and Thelpers\n\nBuild query strings, write JSON without what you do not want, encode a value with a key, compile an expression, load lists of values, translate texts, check permissions and add helpers to the templates."},{"role":"assistant","content":"- `QUERIFY([url], object)` makes a query string: `null` values are skipped, arrays are joined with commas, dates become ISO texts and everything is encoded. With a url it is added to it (`?` or `&`), also after a method as in `'POST /api/x'`.\n- `STRINGIFY(object, [compress], [fields])` is `JSON.stringify()` with two options: `compress: true` leaves out empty texts, `null` and `false`, and trims the texts; `fields` is a list of the names that are kept, a function `(key, value)` that decides, or an object where `name: false` leaves a field out.\n- `ENCRYPT(value, [key], [type])` turns a text or an object into a hexadecimal text that ends with a check of the key, and `DECRYPT(text, [key], [type])` gives the value back, or `undefined` when the key or the type are not the same. **It is an obfuscation for the browser, not security:** anybody can read it without the key.\n- `FN('n => n * 2')` compiles an expression into a function and remembers it; without `=>` the text is the body and the argument is called `value`.\n- `NOOP`, `EMPTYARRAY` and `EMPTYOBJECT` (frozen: they cannot be changed by mistake), `MONTHS` and `DAYS` (the names in English) and `NOW` (a `Date` that jComponent keeps up to date).\n- **Code lists:** `CLINIT(name, function(next) {}, [expire], [init])` registers a list that `function` loads (it calls `next(value)`; with a text instead of a function, an address like `GET /api/countries/`). `CL('a,b', callback)` loads the lists that are not loaded and stores each one in `DEF.cl.<name>` (and in the state at that path); `CLRELOAD(name, callback)` loads them again.\n- **Translation:** `LOCALIZE(text)` loads a dictionary in the format of the Total.js resource files (`key : translation`, one by line, `//` for comments) and `TRANSLATE(text)` replaces every `@(text)` with its translation (the same text when there is none). The key is `T` plus the `HASH()` of the text in base 36.\n- `UNAUTHORIZED(...names)` returns `false` when `window.user` has one of the permissions (`user.permissions` or `user.roles`, a list or an object) or is `sa` / `su`, and `true` otherwise (also when there is no user).\n- `Thelpers` (the same object as `Tangular.helpers`, and `Ta` is `Tangular`) holds the helpers of the templates: add one with `Thelpers.name = function(value, ...arguments)`, where `this` is the model, and use it as `{{ value | name(argument) }}`.\n\n```html public/index.html\n<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<p class=\"out\"></p>\n</body>\n</html>\n```"}],"knowledge":{"content_sha256":"dffd31596690d343866ae9498b493c7db67dcd3147728ef8175a0bd4c2523204","source_sha256":"fa9ad3dbea9989e2ae3eaaba32943ace3c4365832520c80b2c6c8f1202908ac4","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"jcomponent20","url":"https://github.com/totaljs/jComponent","revision":"3fedd009a277f50fddc608b97afbec553b67c51b","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"01ce7c4d3dbd3d14b5a7ca9f44172ed332fa1eed250505811d4f828b38254bf9","license_files":[{"path":"LICENSE","sha256":"abf1260b51729c2fc0da7917e3564878725212bd7859683e7d5af91b6f9192bd","text":true,"characters":1095},{"path":"license.txt","sha256":"7226fe6e0be959f3c5e337bec8ca39ec08e694c8051dd7bc09d3e1a242abdc88","text":true,"characters":1099}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CLINIT","CLRELOAD","DAYS","DECRYPT","DEF","DOCTYPE","EMPTYARRAY","EMPTYOBJECT","ENCRYPT","GET","HASH","ISO","JSON","LOCALIZE","MONTHS","NOOP","NOW","POST","QUERIFY","STRINGIFY","TRANSLATE","Tangular.helpers","Total.js","UNAUTHORIZED"],"language":"en"},"files":[{"path":"public/index.html","language":"html","content":"<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<p class=\"out\"></p>\n</body>\n</html>"}],"test":"const results = [];\nconst check = (ok, msg) => ok || results.push(msg);\n\ncheck(QUERIFY('/api/users', { page: 2, tags: ['a', 'b'], skip: null, q: 'a b' }) === '/api/users?page=2&tags=a%2Cb&q=a%20b', 'QUERIFY with an address, got: ' + QUERIFY('/api/users', { page: 2, tags: ['a', 'b'], skip: null, q: 'a b' }));\ncheck(QUERIFY({ a: 1 }) === '?a=1', 'QUERIFY without an address');\ncheck(QUERIFY('POST /api/x?z=1', { a: 1 }) === 'POST /api/x?z=1&a=1', 'QUERIFY after a method and with a query already');\n\ncheck(STRINGIFY({ a: 1, b: '', c: null, d: ' x ' }, true) === '{\"a\":1,\"d\":\"x\"}', 'STRINGIFY compress, got: ' + STRINGIFY({ a: 1, b: '', c: null, d: ' x ' }, true));\ncheck(STRINGIFY({ a: 1, b: 2, c: 3 }, false, ['a', 'c']) === '{\"a\":1,\"c\":3}', 'STRINGIFY with a list of fields');\ncheck(STRINGIFY({ a: 1, b: 2 }, false, { b: false }) === '{\"a\":1}', 'STRINGIFY with a field that is left out');\ncheck(STRINGIFY({ a: 1, b: 2 }, false, (key, value) => value > 1) === '{\"b\":2}', 'STRINGIFY with a function');\n\nconst secret = ENCRYPT({ id: 7 }, 'key');\ncheck(/^[0-9a-f]+x[0-9a-z]+$/.test(secret), 'ENCRYPT gives hexadecimal and a check, got: ' + secret);\ncheck(JSON.stringify(DECRYPT(secret, 'key')) === '{\"id\":7}', 'DECRYPT with the same key');\ncheck(DECRYPT(secret, 'other') == null, 'DECRYPT with another key gives nothing');\nconst typed = ENCRYPT('text', 'key', 'T');\ncheck(DECRYPT(typed, 'key', 'T') === 'text' && DECRYPT(typed, 'key') == null, 'the type has to be the same, got: ' + typed);\n\ncheck(FN('n => n * 2')(4) === 8 && FN('value.a')({ a: 3 }) === 3, 'FN compiles an arrow and a body');\ncheck(FN('n => n * 2') === FN('n => n * 2'), 'FN remembers the function');\n\ncheck(Object.isFrozen(EMPTYARRAY) && Object.isFrozen(EMPTYOBJECT) && EMPTYARRAY.length === 0, 'EMPTYARRAY and EMPTYOBJECT are frozen');\ncheck(MONTHS.length === 12 && MONTHS[9] === 'October' && DAYS.length === 7 && DAYS[0] === 'Sunday', 'MONTHS and DAYS');\ncheck(NOW instanceof Date, 'NOW is a Date');\ncheck(window.$totaluidate(13) === 1 && window.$totaluidate(0) === 0, 'the helper of the 12-hour clock, got: ' + window.$totaluidate(13) + ' ' + window.$totaluidate(0));\n\n// code lists\nlet loads = 0;\nCLINIT('colors', next => next([{ id: 'r', name: 'Red' }, { id: 'b', name: 'Blue' }].map(n => (loads++, n))));\nCLINIT('countries', next => next([{ id: 'sk', name: 'Slovakia' }]));\nawait new Promise(resolve => CL('colors,countries', resolve));\ncheck(GET('DEF.cl.colors').length === 2 && GET('DEF.cl.countries')[0].name === 'Slovakia', 'CL loads the lists, got: ' + JSON.stringify(GET('DEF.cl.colors')));\nawait new Promise(resolve => CL('colors', resolve));\ncheck(loads === 2, 'CL does not load a list that is loaded, got: ' + loads);\nawait new Promise(resolve => CLRELOAD('colors', resolve));\ncheck(loads === 4, 'CLRELOAD loads it again, got: ' + loads);\n\n// translation: the key is T + HASH(text) in base 36\nLOCALIZE('T' + HASH('hello').toString(36) + ' : Hola\\n// a comment\\n' + HASH('bye').toString(36) + ' : Adios');\ncheck(TRANSLATE('@(hello), @(bye)!') === 'Hola, Adios!', 'TRANSLATE, got: ' + TRANSLATE('@(hello), @(bye)!'));\ncheck(TRANSLATE('@(unknown) text') === 'unknown text' && TRANSLATE('plain') === 'plain', 'a text without translation stays');\n\n// permissions\nwindow.user = { permissions: ['read'] };\ncheck(UNAUTHORIZED('read') === false && UNAUTHORIZED('write') === true && UNAUTHORIZED('write', 'read') === false, 'UNAUTHORIZED with a list');\nwindow.user = { roles: { admin: true } };\ncheck(UNAUTHORIZED('admin') === false && UNAUTHORIZED('guest') === true, 'UNAUTHORIZED with an object');\nwindow.user = { sa: true };\ncheck(UNAUTHORIZED('anything') === false, 'a super administrator is always authorized');\nwindow.user = null;\ncheck(UNAUTHORIZED('read') === true, 'without a user nothing is authorized');\n\n// helpers of the templates\nThelpers.double = function(n) { return n * 2; };\nThelpers.suffix = function(text, value) { return text + value + this.unit; };\ncheck(Ta.render('{{ value | double }}', { value: 4 }) === '8', 'a helper receives the value, got: ' + Ta.render('{{ value | double }}', { value: 4 }));\ncheck(Ta.render('{{ name | suffix(5) }}', { name: 'n', unit: 'kg' }) === 'n5kg', 'a helper with an argument and this as the model, got: ' + Ta.render('{{ name | suffix(5) }}', { name: 'n', unit: 'kg' }));\ncheck(Thelpers === Tangular.helpers && Ta === Tangular, 'Thelpers and Ta are the objects of Tangular');\ncheck(Ta.render('{{ value | cl(\\'countries\\') }}', { value: 'sk' }) === 'Slovakia', 'the helper cl reads the list, got: ' + Ta.render('{{ value | cl(\\'countries\\') }}', { value: 'sk' }));\n\nresults","test_kind":"browser"}
{"id":"jcomponent/dom-helpers","section":"jcomponent","kind":"recipe","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/jcomponent/dom-helpers/","markdown_url":"https://forge.totaljs.es/en/jcomponent/dom-helpers.md","tags":["jcomponent","frontend","dom","utilities"],"license":"CC-BY-4.0","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"2b729f0f087c1b6f0a8a996b920b8fe753bb5ebb3ef62745f627f54ea858573a","checked_at":"2026-10-20T12:19:20.651Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"browser","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"DOM helpers: ATTRD, HIDDEN, VISIBLE, WIDTH, STYLE, NODEMOVE, DIFFDOM and APPEARANCE","description":"Read data attributes, ask if an element is shown or on the screen, add styles that you can replace, move elements in their parent, update a list without redrawing it, and set the colors and the dark mode of the page.","messages":[{"role":"user","content":"DOM helpers: ATTRD, HIDDEN, VISIBLE, WIDTH, STYLE, NODEMOVE, DIFFDOM and APPEARANCE\n\nRead data attributes, ask if an element is shown or on the screen, add styles that you can replace, move elements in their parent, update a list without redrawing it, and set the colors and the dark mode of the page."},{"role":"assistant","content":"- `ATTRD(element or event, [name])` reads `data-<name>` (default `data-id`) of an element, a jQuery object or the element of an event; for a plain object it reads the property.\n- `HIDDEN(element)` is `true` when the element is not shown (it or a parent is `display: none`; **an element that is a direct child of the body is always reported as not hidden**, the check starts from its parent); `VISIBLE(element, [threshold], [mode])` is `true` when it is inside the visible part of the window (`mode` `'above'` or `'below'` asks where it is when it is not). `NOTFOCUSED()` is `true` when the browser tab is in the background and `inDOM(element)` is true while the element is in the document.\n- `WIDTH([element])` returns the size class of the width of the element (default: the window): `'xs'`, `'sm'`, `'md'` or `'lg'` (the limits are in `DEF.devices`). `WW` and `WH` are the size of the window; `$W` is the jQuery object of the window; `SCROLLBARWIDTH()` is the width that a scrollbar takes.\n- `SCROLLBAR(element, [options])` wraps an element with a custom scrollbar and returns it; `scrollbar.resize()` recalculates it after the content changed.\n- `isTOUCH`, `isMOBILE`, `isROBOT`, `isSTANDALONE`, `isPRIVATEMODE` and `isIE` are booleans about the browser, set when the page starts.\n- `STYLE(css, [id], [selector])` adds a `<style>` to the head. With an `id` the style is replaced when you call it again with the same id; with a `selector` every rule is wrapped by it (`'.a { }'` becomes `'#p .a{ }'`).\n- `NODEINDEXOF(element)` is the position of the element among its siblings, `NODEINSERT(element, reference, [before])` moves an element after (or before) another and `NODEMOVE(element, [up])` moves it one place down (or up).\n- `DIFFDOM(parent, selector, html, [attribute])` makes the children of `parent` that match `selector` the same as the ones in `html`, **without redrawing** those that are already there: an element is the same when its `attribute` has the same value (without `attribute`, when its whole HTML is the same). It removes the ones that are not in the new list, adds the new ones and puts them in the order of `html`, and returns `{ add, upd, rem }`. The content of an element that is kept is **not** replaced.\n- `APPEARANCE({ color, dark, large, <name>: value })` sets the look of the page: `dark` and `large` switch the classes `ui-dark` and `ui-large` of the body, `color` and every other key become CSS variables of `:root` (`--color`, and `--rgb` is calculated from the color) in one `<style id=\"cssappearance\">`.\n- `ENVIRONMENT(name, version, language, [env])` sets the name that prefixes the keys in `localStorage`, the version and the language (`DEF.localstorage`, `DEF.version`, `DEF.language`) and, with `env`, several `ENV()` values.\n\n```html public/index.html\n<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<p id=\"p\">A paragraph</p>\n\t<div id=\"shown\" style=\"width:300px;height:40px\">Shown</div>\n\t<div id=\"hidden\" style=\"display:none\"><span id=\"inside\">Inside</span></div>\n\t<div id=\"far\" style=\"margin-top:3000px\">Far away</div>\n\t<div id=\"box\" style=\"width:200px;height:50px;overflow:auto\"><div style=\"height:300px\">tall</div></div>\n\t<ul id=\"list\"><li id=\"a\" data-id=\"a\">a</li><li id=\"b\" data-id=\"b\">b</li><li id=\"c\" data-id=\"c\">c</li></ul>\n\t<ul id=\"diff\"><li data-id=\"1\">one</li><li data-id=\"2\">two</li></ul>\n</body>\n</html>\n```"}],"knowledge":{"content_sha256":"03844133175a14c82a371246bc135818ff6e040648985e9be5866b997a1b4e84","source_sha256":"2b729f0f087c1b6f0a8a996b920b8fe753bb5ebb3ef62745f627f54ea858573a","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"jcomponent20","url":"https://github.com/totaljs/jComponent","revision":"3fedd009a277f50fddc608b97afbec553b67c51b","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"01ce7c4d3dbd3d14b5a7ca9f44172ed332fa1eed250505811d4f828b38254bf9","license_files":[{"path":"LICENSE","sha256":"abf1260b51729c2fc0da7917e3564878725212bd7859683e7d5af91b6f9192bd","text":true,"characters":1095},{"path":"license.txt","sha256":"7226fe6e0be959f3c5e337bec8ca39ec08e694c8051dd7bc09d3e1a242abdc88","text":true,"characters":1099}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["APPEARANCE","ATTRD","CSS","DEF","DIFFDOM","DOCTYPE","ENV","ENVIRONMENT","HIDDEN","HTML","NODEINDEXOF","NODEINSERT","NODEMOVE","NOTFOCUSED","SCROLLBAR","SCROLLBARWIDTH","STYLE","VISIBLE","WIDTH"],"language":"en"},"files":[{"path":"public/index.html","language":"html","content":"<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<p id=\"p\">A paragraph</p>\n\t<div id=\"shown\" style=\"width:300px;height:40px\">Shown</div>\n\t<div id=\"hidden\" style=\"display:none\"><span id=\"inside\">Inside</span></div>\n\t<div id=\"far\" style=\"margin-top:3000px\">Far away</div>\n\t<div id=\"box\" style=\"width:200px;height:50px;overflow:auto\"><div style=\"height:300px\">tall</div></div>\n\t<ul id=\"list\"><li id=\"a\" data-id=\"a\">a</li><li id=\"b\" data-id=\"b\">b</li><li id=\"c\" data-id=\"c\">c</li></ul>\n\t<ul id=\"diff\"><li data-id=\"1\">one</li><li data-id=\"2\">two</li></ul>\n</body>\n</html>"}],"test":"const results = [];\nconst check = (ok, msg) => ok || results.push(msg);\n\nconst tag = $('<div class=\"x\" data-id=\"7\" data-name=\"n\"></div>');\ncheck(ATTRD(tag) === '7' && ATTRD(tag, 'name') === 'n', 'ATTRD reads data-id and data-name');\ncheck(ATTRD(tag[0]) === '7' && ATTRD({ id: 5 }) === 5 && ATTRD(null) === null, 'ATTRD with an element, an object and nothing');\n\ncheck(HIDDEN($('#shown')) === false && HIDDEN($('#inside')) === true && HIDDEN(null) === true, 'HIDDEN');\ncheck(HIDDEN($('#hidden')) === false, 'an element that is a direct child of the body is never reported as hidden, even with display none');\ncheck(VISIBLE($('#shown')) === true && VISIBLE($('#far')) === false && VISIBLE($('#far'), 0, 'below') === true && VISIBLE($('#far'), 0, 'above') === false, 'VISIBLE and its mode');\ncheck(typeof NOTFOCUSED() === 'boolean', 'NOTFOCUSED is a boolean');\ncheck(inDOM($('#p')[0]) === true && !inDOM($('<div></div>')[0]), 'inDOM');\n\ncheck(['xs', 'sm', 'md', 'lg'].includes(WIDTH()) && ['xs', 'sm', 'md', 'lg'].includes(WIDTH($('#shown'))), 'WIDTH gives a size class, got: ' + WIDTH() + ' ' + WIDTH($('#shown')));\ncheck(WW > 0 && WH > 0 && WW === $W.width() && WH === $W.height() && SCROLLBARWIDTH() >= 0, 'WW, WH, $W and SCROLLBARWIDTH');\ncheck([isTOUCH, isMOBILE, isROBOT, isSTANDALONE, isPRIVATEMODE, isIE].every(n => typeof n === 'boolean'), 'the is... flags are booleans');\n\nconst bar = SCROLLBAR($('#box'), { visibleY: true });\ncheck(typeof bar === 'object' && typeof bar.resize === 'function', 'SCROLLBAR returns the scrollbar');\n\nSTYLE('#p { color: rgb(255, 0, 0) }', 'one');\ncheck(getComputedStyle(document.getElementById('p')).color === 'rgb(255, 0, 0)', 'STYLE adds the rules');\nSTYLE('#p { color: rgb(0, 0, 255) }', 'one');\ncheck(getComputedStyle(document.getElementById('p')).color === 'rgb(0, 0, 255)' && $('style#cssone').length === 1, 'the same id replaces the style');\nSTYLE('.scoped { color: green }', 'two', '#p');\ncheck($('#csstwo').text() === '#p .scoped{ color: green }', 'a selector wraps the rules, got: ' + $('#csstwo').text());\n\ncheck(NODEINDEXOF($('#b')) === 1 && NODEINDEXOF(document.getElementById('c')) === 2, 'NODEINDEXOF');\nNODEMOVE($('#c'), true);\ncheck($('#list').text() === 'acb', 'NODEMOVE up, got: ' + $('#list').text());\nNODEMOVE($('#a'));\ncheck($('#list').text() === 'cab', 'NODEMOVE down, got: ' + $('#list').text());\nNODEINSERT($('#c'), $('#b'));\ncheck($('#list').text() === 'abc', 'NODEINSERT after, got: ' + $('#list').text());\nNODEINSERT($('#c'), $('#a'), true);\ncheck($('#list').text() === 'cab', 'NODEINSERT before, got: ' + $('#list').text());\n\nconst kept = $('#diff li[data-id=\"2\"]')[0];\nconst result = DIFFDOM($('#diff'), 'li', '<li data-id=\"2\">TWO</li><li data-id=\"3\">three</li>', 'data-id');\ncheck(result.add === 1 && result.upd === 1 && result.rem === 1, 'DIFFDOM counts, got: ' + JSON.stringify(result));\ncheck($('#diff li').length === 2 && $('#diff li')[0] === kept && $('#diff').text() === 'twothree', 'the element that was there is the same node and keeps its content, got: ' + $('#diff').text());\n\nAPPEARANCE({ color: '#ff0000', dark: true });\ncheck($('body').hasClass('ui-dark') && $('#cssappearance').text() === ':root{--color:#ff0000;--rgb:255,0,0}', 'APPEARANCE, got: ' + $('#cssappearance').text());\nAPPEARANCE({ dark: false });\ncheck(!$('body').hasClass('ui-dark') && $('#cssappearance').length === 0, 'APPEARANCE without color removes the style');\n\nENVIRONMENT('myapp', '1.2', 'en', { theme: 'dark' });\ncheck(DEF.localstorage === 'myapp' && DEF.version === '1.2' && DEF.language === 'en' && ENV('theme') === 'dark', 'ENVIRONMENT');\n\nresults","test_kind":"browser"}
{"id":"jcomponent/find-and-call","section":"jcomponent","kind":"recipe","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/jcomponent/find-and-call/","markdown_url":"https://forge.totaljs.es/en/jcomponent/find-and-call.md","tags":["jcomponent","frontend","components","api"],"license":"CC-BY-4.0","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"8be9c5a72fa0b1dbb9b9a848d453391bb18153736380ba7dc911ed7f3692a0bd","checked_at":"2026-10-20T12:19:24.641Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"browser","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Find and call components and plugins: FIND, COMPONENTS, SETTER, EXE, TAPI, MIDDLEWARE and ADAPT","description":"Look for the components of the page by name or path, call a method of several components at once, call functions by name, use two API addresses with TAPI and PIPE, run middleware before a route and replace the markers of a text.","messages":[{"role":"user","content":"Find and call components and plugins: FIND, COMPONENTS, SETTER, EXE, TAPI, MIDDLEWARE and ADAPT\n\nLook for the components of the page by name or path, call a method of several components at once, call functions by name, use two API addresses with TAPI and PIPE, run middleware before a route and replace the markers of a text."},{"role":"assistant","content":"- `FIND(selector, [all])` returns a component that is ready: `'notice'` is its **name**, `'.msg'` its **path** (it matches the paths that contain it) and `'#id'` its id; with `all` set to `true` it returns the list. `COMPONENTS(path)` returns the list of the ready components whose path is under `path`; the flags of a path filter it (`'msg @visible'`).\n- `SETTER('name/method', a, b, c, d)` calls `method` of **every** ready component with that name (`'.path/method'` for a path, `'#id/method'` for an id, `'*/method'` for all of them). A name that is not loaded yet is loaded first (a lazy component). `ASETTER(...)` does not call it: it returns a function that will.\n- `EXE(name, a, b, c, d)` (the same as `EXEC()`) calls what the name says: `'plugin/method'` a method of a plugin, `'function'` a global function, or a path in the state that holds a function (`'FUNC.save'`). `SEEX(name, value)` is the same, but when the name is not a function it **sets** the path to `value`.\n- `TAPI(name, data, callback)` is `API(DEF.api, ...)` and `PIPE(name, data, callback)` is `API(DEF.pipe, ...)`: two addresses of endpoints that you set once in `DEF.api` and `DEF.pipe`.\n- `MIDDLEWARE(name, function(next) {})` registers a function that runs before a route: it has to call `next()` to let it go on. A route asks for one (or more, separated by commas) as its third argument: `ROUTE('/path/', handler, 'name')`. `MIDDLEWARE(['a', 'b'], callback)` runs the middleware in order and then the callback; one that does not call `next()` stops the chain.\n- `NEWUIBIND(element, path, config)` makes a `ui-bind` on an element from the code (the element does not need the attributes), and returns it.\n- `ADAPT(path, id, text)` prepares the text of a component that comes from outside: it translates `@(...)`, replaces `~PATH~` and `CLASS` with the path and `~ID~` with the id, and puts the `path` in the `<ui-plugin>` tags that do not have one. `COMPILE` is the same function as `NOOP` in jComponent 20: it does nothing.\n- The objects behind everything: `jComponent`, `Total`, `MAIN` and `M` are the same object (the engine: `M.components` is the list of the components, `M.version` the version), `W` is `window`, `DEF` the defaults, and `REPO`, `FUNC`, `TEMP` and `PLUGINS` are plain objects of yours: `FUNC` for functions that `EXE('FUNC.name')` can call, `TEMP` is emptied by jComponent when the page starts again.\n\n```js definitions/math.js\nNEWACTION('Math/add', {\n\tname: 'Add two numbers',\n\tinput: '*a:Number, *b:Number',\n\taction: function($, model) {\n\t\t$.callback({ sum: model.a + model.b });\n\t}\n});\n```\n\n```js controllers/api.js\nexports.install = function() {\n\tROUTE('API /api/ +math_add --> Math/add');\n\tROUTE('API /pipe/ +math_add --> Math/add');\n};\n```\n\n```html public/index.html\n<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<ui-component name=\"notice\" path=\"msg\"></ui-component>\n\t<ui-component name=\"notice\" path=\"other\"></ui-component>\n\t<ui-plugin path=\"shop\"></ui-plugin>\n\t<p id=\"target\">x</p>\n\t<script>\n\n\t\tCOMPONENT('notice', function(self) {\n\t\t\tself.make = function() { self.element.html('<b></b>'); };\n\t\t\tself.say = function(text, extra) { self.find('b').text(text + (extra || '')); };\n\t\t});\n\n\t\tPLUGIN('shop', function(exports) {\n\t\t\texports.hello = function(a) { window.shoplog = 'hello ' + a; };\n\t\t});\n\n\t\twindow.globalfn = function(a, b) { window.globallog = [a, b]; };\n\t\tFUNC.local = function(a) { window.funclog = a; };\n\n\t\tDEF.api = '/api/';\n\t\tDEF.pipe = '/pipe/';\n\n\t</script>\n</body>\n</html>\n```"}],"knowledge":{"content_sha256":"ec11cb7c6892e8b6319d74607b472f92e3007b14b67d22fac5d5035710d4f9c6","source_sha256":"8be9c5a72fa0b1dbb9b9a848d453391bb18153736380ba7dc911ed7f3692a0bd","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"jcomponent20","url":"https://github.com/totaljs/jComponent","revision":"3fedd009a277f50fddc608b97afbec553b67c51b","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"01ce7c4d3dbd3d14b5a7ca9f44172ed332fa1eed250505811d4f828b38254bf9","license_files":[{"path":"LICENSE","sha256":"abf1260b51729c2fc0da7917e3564878725212bd7859683e7d5af91b6f9192bd","text":true,"characters":1095},{"path":"license.txt","sha256":"7226fe6e0be959f3c5e337bec8ca39ec08e694c8051dd7bc09d3e1a242abdc88","text":true,"characters":1099}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["ADAPT","API","ASETTER","CLASS","COMPILE","COMPONENT","COMPONENTS","DEF","DOCTYPE","EXE","EXEC","FIND","FUNC","MAIN","MIDDLEWARE","NEWACTION","NEWUIBIND","NOOP","PATH","PIPE","PLUGIN","PLUGINS","REPO","ROUTE","SEEX","SETTER","TAPI","TEMP"],"language":"en"},"files":[{"path":"definitions/math.js","language":"js","content":"NEWACTION('Math/add', {\n\tname: 'Add two numbers',\n\tinput: '*a:Number, *b:Number',\n\taction: function($, model) {\n\t\t$.callback({ sum: model.a + model.b });\n\t}\n});"},{"path":"controllers/api.js","language":"js","content":"exports.install = function() {\n\tROUTE('API /api/ +math_add --> Math/add');\n\tROUTE('API /pipe/ +math_add --> Math/add');\n};"},{"path":"public/index.html","language":"html","content":"<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\" />\n\t<title>jComponent</title>\n\t<link rel=\"stylesheet\" href=\"//cdn.componentator.com/spa.min@20.css\" />\n\t<script src=\"//cdn.componentator.com/spa.min@20.js\"></script>\n</head>\n<body>\n\t<ui-component name=\"notice\" path=\"msg\"></ui-component>\n\t<ui-component name=\"notice\" path=\"other\"></ui-component>\n\t<ui-plugin path=\"shop\"></ui-plugin>\n\t<p id=\"target\">x</p>\n\t<script>\n\n\t\tCOMPONENT('notice', function(self) {\n\t\t\tself.make = function() { self.element.html('<b></b>'); };\n\t\t\tself.say = function(text, extra) { self.find('b').text(text + (extra || '')); };\n\t\t});\n\n\t\tPLUGIN('shop', function(exports) {\n\t\t\texports.hello = function(a) { window.shoplog = 'hello ' + a; };\n\t\t});\n\n\t\twindow.globalfn = function(a, b) { window.globallog = [a, b]; };\n\t\tFUNC.local = function(a) { window.funclog = a; };\n\n\t\tDEF.api = '/api/';\n\t\tDEF.pipe = '/pipe/';\n\n\t</script>\n</body>\n</html>"}],"test":"const results = [];\nconst check = (ok, msg) => ok || results.push(msg);\nconst until = async (fn, ms = 4000) => { const t = Date.now(); while (Date.now() - t < ms) { try { if (fn()) return true; } catch (e) {} await new Promise(r => setTimeout(r, 50)); } return false; };\nconst texts = () => $('ui-component').map((i, n) => $(n).text()).get().join('|');\n\nawait until(() => FIND('notice') && FIND('.other'));\n\ncheck(FIND('notice').name === 'notice' && FIND('.msg').path.path === 'msg' && FIND('notice', true).length === 2 && FIND('nothing') == null, 'FIND by name, by path and all');\ncheck(COMPONENTS('msg').length === 1 && COMPONENTS('other').length === 1 && COMPONENTS('nothing').length === 0, 'COMPONENTS by path');\n\nSETTER('notice/say', 'hi', '!');\ncheck(texts() === 'hi!|hi!', 'SETTER calls the method of every component with that name, got: ' + texts());\nSETTER('.other/say', 'only');\ncheck(texts() === 'hi!|only', 'SETTER with a path calls only that one, got: ' + texts());\nconst later = ASETTER('notice/say', 'later');\ncheck(texts() === 'hi!|only', 'ASETTER does not call it yet');\nlater();\ncheck(texts() === 'later|later', 'the function that ASETTER returned calls it, got: ' + texts());\n\nEXE('shop/hello', 'x');\nEXE('globalfn', 1, 2);\ncheck(window.shoplog === 'hello x', 'EXE calls the method of a plugin, got: ' + window.shoplog);\ncheck(JSON.stringify(window.globallog) === '[1,2]', 'EXE calls a global function');\nEXE('FUNC.local', 'viafunc');\ncheck(window.funclog === 'viafunc', 'EXE calls a function that is in the state, got: ' + window.funclog);\nSEEX('globalfn', 3, 4);\ncheck(JSON.stringify(window.globallog) === '[3,4]', 'SEEX calls a function');\nSEEX('some.path', 'value');\ncheck(GET('some.path') === 'value', 'SEEX sets the path when the name is not a function');\n\nconst bind = NEWUIBIND($('#target'), 'bound.text', 'text:value');\nSET('bound.text', 'from the state');\ncheck(await until(() => $('#target').text() === 'from the state'), 'NEWUIBIND binds an element made by the code, got: ' + $('#target').text());\n\ncheck(ADAPT('plug', 'id1', '<ui-plugin>~PATH~ ~ID~ CLASS</ui-plugin>') === '<ui-plugin path=\"plug\">plug id1 plug</ui-plugin>', 'ADAPT replaces the markers and adds the path, got: ' + ADAPT('plug', 'id1', '<ui-plugin>~PATH~ ~ID~ CLASS</ui-plugin>'));\ncheck(ADAPT('p', 'i', 5) === 5, 'ADAPT returns what is not a text');\ncheck(COMPILE === NOOP, 'COMPILE is NOOP');\n\n// middleware, one chain at a time\nconst log = [];\nMIDDLEWARE('first', next => { log.push('first'); setTimeout(next, 30); });\nMIDDLEWARE('second', next => { log.push('second'); next(); });\nMIDDLEWARE('stop', next => log.push('stop'));\n\nMIDDLEWARE(['first', 'second'], () => log.push('done'));\nawait until(() => log.includes('done'));\ncheck(log.join() === 'first,second,done', 'an array runs the middleware in order and then the callback, got: ' + log.join());\n\nlog.length = 0;\nMIDDLEWARE(['stop', 'second'], () => log.push('never'));\nawait new Promise(resolve => setTimeout(resolve, 200));\ncheck(log.join() === 'stop', 'a middleware that does not call next() stops the chain, got: ' + log.join());\n\nlog.length = 0;\nROUTE('/mw/', () => log.push('route'), 'second');\nREDIRECT('/mw/');\nawait until(() => log.includes('route'));\ncheck(log.join() === 'second,route', 'a route runs its middleware first, got: ' + log.join());\n\n// TAPI and PIPE use their own address\nconst sums = [];\nTAPI('math_add', { a: 1, b: 2 }, response => sums.push('api:' + response.sum));\nPIPE('math_add', { a: 10, b: 20 }, response => sums.push('pipe:' + response.sum));\nawait until(() => sums.length === 2);\ncheck(sums.sort().join() === 'api:3,pipe:30', 'TAPI uses DEF.api and PIPE uses DEF.pipe, got: ' + sums.join());\n\n// the objects behind\ncheck(jComponent === Total && Total === MAIN && MAIN === M && W === window && DEF === window.DEF, 'jComponent, Total, MAIN and M are the same object, and W is window');\ncheck(M.components.length >= 2 && /^20/.test(String(M.version)) && M.is20 === true, 'M.components and M.version, got: ' + M.version);\ncheck(typeof REPO === 'object' && typeof FUNC === 'object' && typeof TEMP === 'object' && PLUGINS.shop != null, 'REPO, FUNC, TEMP and PLUGINS, got: ' + Object.keys(PLUGINS));\n\nresults","test_kind":"browser"}
{"id":"jcomponent/custom-flags","section":"jcomponent","kind":"recipe","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/jcomponent/custom-flags/","markdown_url":"https://forge.totaljs.es/en/jcomponent/custom-flags.md","tags":["jcomponent","flags","events","paths"],"license":"CC-BY-4.0","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"01b671c1d07e9c1b60510a05c6afb5112301624dddfdbc3852924841e037425c","checked_at":"2026-10-20T12:19:19.062Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"browser","scope":"Only the declared HTTP and browser assertions and page errors.","attempts":1},"title":"Dispatch custom path flags with FLAGS","description":"Execute custom flags, observe their events and distinguish them from model changes and reserved state flags.","messages":[{"role":"user","content":"Dispatch custom path flags with FLAGS\n\nExecute custom flags, observe their events and distinguish them from model changes and reserved state flags."},{"role":"assistant","content":"`FLAGS('path @name', callback)` parses a jComponent path, emits `@flag name` with the parsed path and invokes the callback. Register the listener with `ON('@flag name', function(path) { ... })`. Multiple distinct custom flags emit separate events; repeating a name in the same expression emits it once. In this example, execution and the callback are synchronous.\n\nThe parsed path is the event argument. A value such as `@refresh:yes` still selects the flag named `refresh`; the value is not passed as a second event argument. Reserved flags such as `@reset` are excluded from these custom events. Calling `FLAGS()` alone does not call `SET()` or reset component state: state operations interpret their own flags. Use application-specific flag names to avoid collisions.\n\n```html public/index.html\n<!DOCTYPE html>\n<html><head><meta charset=\"utf-8\"><title>Custom flags</title>\n<script src=\"//cdn.componentator.com/spa.min@20.js\"></script></head>\n<body><p>Dispatch a custom refresh notification without modifying the model.</p></body></html>\n```"}],"knowledge":{"content_sha256":"815919932ed4aa0297d3717cc646b3b3d35fba02bb5f9a3d3ac08c6ff1d6a72f","source_sha256":"01b671c1d07e9c1b60510a05c6afb5112301624dddfdbc3852924841e037425c","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"jcomponent20","url":"https://github.com/totaljs/jComponent","revision":"3fedd009a277f50fddc608b97afbec553b67c51b","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"01ce7c4d3dbd3d14b5a7ca9f44172ed332fa1eed250505811d4f828b38254bf9","license_files":[{"path":"LICENSE","sha256":"abf1260b51729c2fc0da7917e3564878725212bd7859683e7d5af91b6f9192bd","text":true,"characters":1095},{"path":"license.txt","sha256":"7226fe6e0be959f3c5e337bec8ca39ec08e694c8051dd7bc09d3e1a242abdc88","text":true,"characters":1099}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["DOCTYPE","FLAGS","SET"],"language":"en"},"files":[{"path":"public/index.html","language":"html","content":"<!DOCTYPE html>\n<html><head><meta charset=\"utf-8\"><title>Custom flags</title>\n<script src=\"//cdn.componentator.com/spa.min@20.js\"></script></head>\n<body><p>Dispatch a custom refresh notification without modifying the model.</p></body></html>"}],"test":"const results = [];\nconst check = (ok, message) => ok || results.push(message);\nconst events = [];\nON('@flag forge_refresh', function(path) { events.push('refresh:' + path); });\nON('@flag forge_save', function(path) { events.push('save:' + path); });\nON('@flag reset', function() { events.push('reserved'); });\nSET('flagdemo.value', 'unchanged');\nlet callback = false;\nFLAGS('flagdemo.value @forge_refresh @forge_save', function() { callback = true; });\ncheck(events.join('|') === 'refresh:flagdemo.value|save:flagdemo.value', 'custom events contain the parsed path');\ncheck(callback, 'callback ran synchronously');\nFLAGS('flagdemo.value @forge_refresh @forge_refresh');\ncheck(events.length === 3, 'duplicate custom flag is dispatched once');\nFLAGS('flagdemo.value @reset');\ncheck(events.length === 3, 'reserved reset flag emits no custom event');\ncheck(GET('flagdemo.value') === 'unchanged', 'FLAGS alone does not mutate the model');\nFLAGS('flagdemo.value');\ncheck(events.length === 3, 'plain path emits no flag event and callback is optional');\nresults","test_kind":"browser"}
{"id":"components/ajaxloading","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/ajaxloading/","markdown_url":"https://forge.totaljs.es/en/components/ajaxloading.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"0011a905eb96dea23a87b493543abc50ae05e58bea8b4880a13efed54c67723b","checked_at":"2026-10-20T12:19:43.880Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-AjaxLoading","description":"This component can show a loading text for async AJAX operations.","messages":[{"role":"user","content":"j-AjaxLoading\n\nThis component can show a loading text for async AJAX operations."},{"role":"assistant","content":"This component can show a loading text for async AJAX operations.\n\n- works with `AJAX()` method only\n\n__Configuration__:\n\n- `url {String}` a pattern for URL is evaluated as RegExp, __must be defined__\n- `label {String}` optional (default: `HTML content`)\n- `icon {Boolean}` adds __spinner__ icon (default: `true`)\n\nUse it with `<ui-component name=\"ajaxloading\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"width:300px\">\n\t<ui-component name=\"ajaxloading\" config=\"url:/users/\" class=\"hidden\">Loading users</ui-component>\n\t<br />\n</div>\n\n<script>\n\tsetTimeout(function() {\n\t\tAJAX('GET /users/');\n\t}, 2000);\n</script>\n```"}],"knowledge":{"content_sha256":"0ff86ad42a0da5bebc9f6964eea76d5854e53014125b82f73aab38a876a6ad60","source_sha256":"0011a905eb96dea23a87b493543abc50ae05e58bea8b4880a13efed54c67723b","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["AJAX","CDN","DEF","GET","HTML","MIT","URL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"width:300px\">\n\t<ui-component name=\"ajaxloading\" config=\"url:/users/\" class=\"hidden\">Loading users</ui-component>\n\t<br />\n</div>\n\n<script>\n\tsetTimeout(function() {\n\t\tAJAX('GET /users/');\n\t}, 2000);\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/animation","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/animation/","markdown_url":"https://forge.totaljs.es/en/components/animation.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"766b3afe3541c57dd3213844f2b2868ec7645166f03d36df0e78489db20d8e83","checked_at":"2026-10-20T12:19:44.919Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Animation","description":"This component can animate elements with .animation class.","messages":[{"role":"user","content":"j-Animation\n\nThis component can animate elements with .animation class."},{"role":"assistant","content":"This component can animate elements with `.animation` class.\n\n\n__Configuration__:\n\n- `style {Number}` style of animation:\n\t- `1` opacity\n\t- `2` scale from big to normal (__default__)\n\t- `3` scale from small to normal\n\t- `4` rotation Z\n\t- `5` changing of X position\n\t- `6` changing of Y position\n- `delay {Number}` delay between animation (default: `200`)\n- `init {Number}` initialization delay (default: `1000`)\n- `cleaner {Number`} delay for cleaner of animation classes (default: `1000` is multipled by count of elements)\n- `together {Boolean}` all elements will be animated together (default: `false`)\n- `visible {Boolean}` performs animation when the element with animation is visible (default: `false`)\n- `offset {Number}` offset (threshold) for visibility (default: `50`)\n- `exec {String}` a link to method which will be executed after animation\n- `datasource {String}` a link to data-source (if the data-source will be modified then the animation will be performed again)\n- `if {String}` a condition for path (same feature like `j-Part`), if the value will be same as `if` value then the animation will be executed (otherwise will all elements restored for previous state)\n\n### Element configuration\n\nElement configuration can be defined in `data-animation=\"delay:100;order:3\"` attribute and it's optional:\n\n- `noanimation {Boolean}` disables animation for this element (only removes `.animation` class)\n- `delay {Number}` animation delay (default: `config.delay`)\n- `order {Number}` animation order index, must start with `1` ... (elements with same order will be displayed together)\n- `style {Number}` a custom style for the element\n\nUse it with `<ui-component name=\"animation\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-plugin path=\"form\" class=\"container\">\n\t<ui-component name=\"animation\" config=\"style:6\">\n\t\t<div class=\"row\">\n\t\t\t<div class=\"col-md-3 m\">\n\t\t\t\t<ui-component name=\"input\" path=\"?.name\" class=\"animation\">Name</ui-component>\n\t\t\t</div>\n\t\t\t<div class=\"col-md-3 m\">\n\t\t\t\t<ui-component name=\"input\" path=\"?.price\" config=\"type:number\" class=\"animation\">Price</ui-component>\n\t\t\t</div>\n\t\t\t<div class=\"col-md-3 m\">\n\t\t\t\t<ui-component name=\"input\" path=\"?.address\" class=\"animation\" data-animation=\"noanimation:1\">Without animation</ui-component>\n\t\t\t</div>\n\t\t</div>\n\t</ui-component>\n</ui-plugin>\n```"}],"knowledge":{"content_sha256":"3eb3e6d5e0cdbb03e10f8cfc520114316464e154eba89f57ec750e4f484eeb34","source_sha256":"766b3afe3541c57dd3213844f2b2868ec7645166f03d36df0e78489db20d8e83","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-plugin path=\"form\" class=\"container\">\n\t<ui-component name=\"animation\" config=\"style:6\">\n\t\t<div class=\"row\">\n\t\t\t<div class=\"col-md-3 m\">\n\t\t\t\t<ui-component name=\"input\" path=\"?.name\" class=\"animation\">Name</ui-component>\n\t\t\t</div>\n\t\t\t<div class=\"col-md-3 m\">\n\t\t\t\t<ui-component name=\"input\" path=\"?.price\" config=\"type:number\" class=\"animation\">Price</ui-component>\n\t\t\t</div>\n\t\t\t<div class=\"col-md-3 m\">\n\t\t\t\t<ui-component name=\"input\" path=\"?.address\" class=\"animation\" data-animation=\"noanimation:1\">Without animation</ui-component>\n\t\t\t</div>\n\t\t</div>\n\t</ui-component>\n</ui-plugin>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/approve","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/approve/","markdown_url":"https://forge.totaljs.es/en/components/approve.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"131f4333f7c9cce2def5ba2d728138471bf3bd4cf2acb65856bceab6ee18bd87","checked_at":"2026-10-20T12:19:45.798Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Approve","description":"j-Approve: easy usage; singleton","messages":[{"role":"user","content":"j-Approve\n\nj-Approve: easy usage; singleton"},{"role":"assistant","content":"- singleton\n- works with Bootstrap\n- supports __dark mode__\n- modal\n\n__Configuration__:\n\n- `cancel` {String} a label for cancel button (default: `Cancel`)\n- __NEW__: `style` {Number} changes style (supported style `1` (default) and `2`)\n\n__Methods__:\n\n- `component.show(message, approve_button_label, [cancel_button_label], callback, [callback_cancel])`\n\n__Usage__:\n\n```js\nSETTER('approve/show', message, 'Yes', function() {\n\n});\n```\n\n## A simple styling of buttons\n\n```js\n// \"Remove\" button will be with Font-Awesome icon called: \"trash-o\"\nSETTER('approve/show', 'Are you sure you want to remove selected item?', ':trash-o: Remove', REMOVE_FUNCTION);\n\n// \"Confirm\" button will be \"green\" with Font-Awesome icon called: \"checked-circle\"\nSETTER('approve/show', 'Are you sure you want to confirm selected items?', ':checked-circle: Confirm #2BA433', REMOVE_FUNCTION);\n```\n\nUse it with `<ui-component name=\"approve\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"approve\"></ui-component>\n\n<script>\n\tSETTER(true, 'approve/show', 'Do you want to remove React/Vue/Angular.js from your device?', ':check-circle: OK', function() {\n\t\talert('Thank you for using of jComponent library');\n\t});\n</script>\n```"}],"knowledge":{"content_sha256":"8d831ab240b9d8c40eff98e7c563f7865a526602ffde8abd49d1c7e6c3d122c1","source_sha256":"131f4333f7c9cce2def5ba2d728138471bf3bd4cf2acb65856bceab6ee18bd87","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","REMOVE_FUNCTION","SETTER"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"approve\"></ui-component>\n\n<script>\n\tSETTER(true, 'approve/show', 'Do you want to remove React/Vue/Angular.js from your device?', ':check-circle: OK', function() {\n\t\talert('Thank you for using of jComponent library');\n\t});\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/aselected","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/aselected/","markdown_url":"https://forge.totaljs.es/en/components/aselected.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"736502e10c6e61323b727780e2ffd2ce8b810b4acabed74a8e77ba6c6d7c2922","checked_at":"2026-10-20T12:19:46.533Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-ASelected","description":"The component adds selected class to <a element if the href attribute of the element will contain URL address from the web browser. It's targeted for public websites.","messages":[{"role":"user","content":"j-ASelected\n\nThe component adds selected class to <a element if the href attribute of the element will contain URL address from the web browser. It's targeted for public websites."},{"role":"assistant","content":"- readonly\n\nThe component adds `selected` class to `<a` element if the `href` attribute of the element will contain URL address from the web browser. It's targeted for public websites.\n\n__Configuration__:\n\n- `class {String}` is a class name for selected element (default: `selected`)\n- `selector {String}` is a jQuery selector (default: `a`)\n- `attr {String}` attribute for comparing (default: `href`)\n- `strict {Boolean}` enables strict comparing of URL address (default: `false`)\n- `delay {Number}` a delay for rebinding of selection when the model is changed (default: `300`)\n- __NEW__: `watch {String}` a path for watching changes\n\nUse it with `<ui-component name=\"aselected\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\t.links a { display: block; border: 0; text-align: left; color: gray; font-size: 14px; border-radius: 2px; padding: 2px 10px; margin-bottom: 1px; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; position: relative; }\n\t.links a i { width: 14px; text-align: center; font-size: 12px; margin-right: 7px; color: #4285F4; }\n\t.links .selected { font-weight: bold; color: white !important; background-color: #4285F4; }\n\t.links .selected i { color: white !important }\n\t.links a:hover { text-decoration: none; color: black; }\n</style>\n\n<ui-component name=\"aselected\" class=\"links\">\n\t<a href=\"/\"><i class=\"ti ti-users\"></i>Users</a>\n\t<a href=\"/products/\"><i class=\"ti ti-box\"></i>Products</a>\n\t<a href=\"/settings/\"><i class=\"ti ti-cog\"></i>Settings</a>\n</ui-component>\n\n<script>\n\n\tvar arr = ['/', '/products/', '/settings/'];\n\n\tsetInterval(function() {\n\t\tREDIRECT(arr[(Math.random() * arr.length) >> 0]);\n\t}, 1500);\n\n</script>\n```"}],"knowledge":{"content_sha256":"21de2ef425f56311218675e526ca9db12e5831eb8813c3d2cb7a43450de6af43","source_sha256":"736502e10c6e61323b727780e2ffd2ce8b810b4acabed74a8e77ba6c6d7c2922","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","REDIRECT","URL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\t.links a { display: block; border: 0; text-align: left; color: gray; font-size: 14px; border-radius: 2px; padding: 2px 10px; margin-bottom: 1px; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; position: relative; }\n\t.links a i { width: 14px; text-align: center; font-size: 12px; margin-right: 7px; color: #4285F4; }\n\t.links .selected { font-weight: bold; color: white !important; background-color: #4285F4; }\n\t.links .selected i { color: white !important }\n\t.links a:hover { text-decoration: none; color: black; }\n</style>\n\n<ui-component name=\"aselected\" class=\"links\">\n\t<a href=\"/\"><i class=\"ti ti-users\"></i>Users</a>\n\t<a href=\"/products/\"><i class=\"ti ti-box\"></i>Products</a>\n\t<a href=\"/settings/\"><i class=\"ti ti-cog\"></i>Settings</a>\n</ui-component>\n\n<script>\n\n\tvar arr = ['/', '/products/', '/settings/'];\n\n\tsetInterval(function() {\n\t\tREDIRECT(arr[(Math.random() * arr.length) >> 0]);\n\t}, 1500);\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/attachments","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/attachments/","markdown_url":"https://forge.totaljs.es/en/components/attachments.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"0b5d3a1f55485d91015bd6f40273e72cb419542c1bc2ef49f3b48dfe21fdabb3","checked_at":"2026-10-20T12:19:47.411Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Attachments","description":"The component displays attachments in the simple list. A user can remove attachments.","messages":[{"role":"user","content":"j-Attachments\n\nThe component displays attachments in the simple list. A user can remove attachments."},{"role":"assistant","content":"The component displays attachments in the simple list. A user can remove attachments.\n\n\nA value be `Array` in the form:\n\n```\n[\n\t{\n\t\tname: 'attachment.docx',\n\t\turl: 'url_to_download',\n\t\tsize: 1024, // in bytes\n\t\tdtcreated: Date\n\t}\n]\n```\n\nUse it with `<ui-component name=\"attachments\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"attachments\" path=\"common.attachments\"></ui-component>\n\n<script>\n\n\tvar common = {};\n\n\tcommon.attachments = [];\n\tcommon.attachments.push({ name: 'logo.png', url: 'https://cdn.totaljs.com/logo.png', size: 5700, dtcreated: NOW });\n\n</script>\n```"}],"knowledge":{"content_sha256":"73585e39ce9439d9f3720ad8cfa19bf7be8910e6e207b5048251a99982743dde","source_sha256":"0b5d3a1f55485d91015bd6f40273e72cb419542c1bc2ef49f3b48dfe21fdabb3","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","NOW"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"attachments\" path=\"common.attachments\"></ui-component>\n\n<script>\n\n\tvar common = {};\n\n\tcommon.attachments = [];\n\tcommon.attachments.push({ name: 'logo.png', url: 'https://cdn.totaljs.com/logo.png', size: 5700, dtcreated: NOW });\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/audio","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/audio/","markdown_url":"https://forge.totaljs.es/en/components/audio.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"de6aea89f819f04d3f17802b119270c062bc5cefed2a59164f5960bbabc82fb3","checked_at":"2026-10-20T12:19:48.289Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Audio","description":"j-Audio: singleton; the component can play and stop audio/sound mp3","messages":[{"role":"user","content":"j-Audio\n\nj-Audio: singleton; the component can play and stop audio/sound mp3"},{"role":"assistant","content":"- singleton\n- the component can `play` and `stop` audio/sound `mp3`\n- `path` represents a volume control\n\n__Methods__:\n\n- `component.play(url)` can play audio/sound from URL\n- `component.stop(url)` can stop audio/sound\n\n__Properties__:\n- `component.items` contains all audio objects\n\nUse it with `<ui-component name=\"audio\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"audio\" path=\"volume\"></ui-component>\n\n<script>\n\tvar volume = 50; // 50%\n\tif (jComponent.is20)\n\t\tSETTER('audio/play @important', 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3');\n\telse\n\t\tSETTER(true, 'audio/play', 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3');\n</script>\n```"}],"knowledge":{"content_sha256":"581947923f6c15cbdb76624cf7cc6ec18080d483b8ef315a4eef3833a630991a","source_sha256":"de6aea89f819f04d3f17802b119270c062bc5cefed2a59164f5960bbabc82fb3","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","SETTER","URL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"audio\" path=\"volume\"></ui-component>\n\n<script>\n\tvar volume = 50; // 50%\n\tif (jComponent.is20)\n\t\tSETTER('audio/play @important', 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3');\n\telse\n\t\tSETTER(true, 'audio/play', 'https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3');\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/autocomplete","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/autocomplete/","markdown_url":"https://forge.totaljs.es/en/components/autocomplete.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"8230d0041ddfc48ee2b9cf9e39bcec00583dd6fc67149f6aa694c7f8712be1dd","checked_at":"2026-10-20T12:19:49.169Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-AutoComplete","description":"j-AutoComplete: works with <input tags only; singleton","messages":[{"role":"user","content":"j-AutoComplete\n\nj-AutoComplete: works with <input tags only; singleton"},{"role":"assistant","content":"- works with `<input` tags only\n- singleton\n- __NEW__: works with `contenteditable` attribute\n\n```javascript\nvar opt = {};\n\nopt.element = 'YOUR_ELEMENT';\n\nopt.search = function(query, render) {\n\t// `query` contains a value from the input\n\t// `render` is a function\n\t// Here we have to find a datasource and render argument helps with rendering HTML\n\n\tAJAX('GET /cities/', { q: query }, render);\n\n\t// or e.g.\n\t// render([{ name: 'Item 1', type: 'Pages' }, { name: 'Item 2', type: 'Widgets' }]);\n};\n\nopt.callback = function(value, input) {\n\tconsole.log('---> selected value:', value);\n\t// input.val('');\n};\n// opt.callback {Function}\n// OR:\n// opt.path {String} a path to variable (for result)\n\n// opf.offsetWidth {Number} \"width\" offset\n// opt.offsetX {Number} \"x\" offset\n// opt.offsetX {Number} \"y\" offset\n// opt.autoselect {Boolean} selects first suggestion automatically (default: false)\n\nSETTER('autocomplete/show', opt);\n```\n\nUse it with `<ui-component name=\"autocomplete\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"autocomplete\" config=\"height:200\"></ui-component>\n\n<div style=\"margin: 80px auto 0;width:300px;\">\n\t<input type=\"text\" style=\"width:100%;padding:5px 10px;border:1px solid #E0E0E0\" placeholder=\"Type something ...\" />\n</div>\n\n<script>\n\n\tvar opt = {};\n\topt.element = $('input');\n\topt.offsetY = 17;\n\topt.offsetWidth = 21;\n\n\topt.search = function(q, render) {\n\t\tconsole.log('---> autocomplete search query:', q);\n\t\t// e.g. we have to call e.g. AJAX or another datasource\n\t\trender([{ name: 'Item 1', type: 'pages' }, { name: 'Item 2', type: 'widgets' }, { name: 'Item 3' }, { name: 'Item 4' }, { name: 'Item 5' }, { name: 'Item 6' }, { name: 'Item 7' }, { name: 'Item 8' }, { name: 'Item 9' }, { name: 'Item 10' }]);\n\t};\n\n\topt.callback = function(value, input) {\n\t\talert('autocomplete selected --> ' + JSON.stringify(value));\n\t\tinput.val('');\n\t};\n\n\tif (jComponent.is20)\n\t\tSETTER('autocomplete/show @important', opt);\n\telse\n\t\tSETTER(true, 'autocomplete/show', opt);\n\n</script>\n```"}],"knowledge":{"content_sha256":"b7d7adaadc9d372189cb39ccd7cc511c9f039c0092418853497296a9618d0f11","source_sha256":"8230d0041ddfc48ee2b9cf9e39bcec00583dd6fc67149f6aa694c7f8712be1dd","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["AJAX","CDN","DEF","E0E0E0","GET","HTML","JSON","MIT","SETTER","YOUR_ELEMENT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"autocomplete\" config=\"height:200\"></ui-component>\n\n<div style=\"margin: 80px auto 0;width:300px;\">\n\t<input type=\"text\" style=\"width:100%;padding:5px 10px;border:1px solid #E0E0E0\" placeholder=\"Type something ...\" />\n</div>\n\n<script>\n\n\tvar opt = {};\n\topt.element = $('input');\n\topt.offsetY = 17;\n\topt.offsetWidth = 21;\n\n\topt.search = function(q, render) {\n\t\tconsole.log('---> autocomplete search query:', q);\n\t\t// e.g. we have to call e.g. AJAX or another datasource\n\t\trender([{ name: 'Item 1', type: 'pages' }, { name: 'Item 2', type: 'widgets' }, { name: 'Item 3' }, { name: 'Item 4' }, { name: 'Item 5' }, { name: 'Item 6' }, { name: 'Item 7' }, { name: 'Item 8' }, { name: 'Item 9' }, { name: 'Item 10' }]);\n\t};\n\n\topt.callback = function(value, input) {\n\t\talert('autocomplete selected --> ' + JSON.stringify(value));\n\t\tinput.val('');\n\t};\n\n\tif (jComponent.is20)\n\t\tSETTER('autocomplete/show @important', opt);\n\telse\n\t\tSETTER(true, 'autocomplete/show', opt);\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/autodarkmode","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/autodarkmode/","markdown_url":"https://forge.totaljs.es/en/components/autodarkmode.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"6ec757e5ff2d0386464c200dabdf0f4e18a1be8af565d9d389f4ab13fef1032b","checked_at":"2026-10-20T12:19:50.060Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-AutoDarkMode","description":"The component automatically applies dark mode according to the operation system.","messages":[{"role":"user","content":"j-AutoDarkMode\n\nThe component automatically applies dark mode according to the operation system."},{"role":"assistant","content":"- singleton\n- readonly\n\nThe component automatically applies dark mode according to the operation system.\n\n__Configuration__:\n\n- `exec {String}` optional, a link to the `function(is_darkmode)`\n\nUse it with `<ui-component name=\"autodarkmode\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"autodarkmode\"></ui-component>\n```"}],"knowledge":{"content_sha256":"4ce08caa1a61338051e3869333a3db06bbc2d0991f329cd285d8a75d3188fd2a","source_sha256":"6ec757e5ff2d0386464c200dabdf0f4e18a1be8af565d9d389f4ab13fef1032b","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"autodarkmode\"></ui-component>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/autoexec","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/autoexec/","markdown_url":"https://forge.totaljs.es/en/components/autoexec.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"451d333efd7bf6c41324a5c3703cdb30acd81ec45c56f01a60df72014264799d","checked_at":"2026-10-20T12:19:50.950Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-AutoExec","description":"This component automatically executes a method defined in exec option.","messages":[{"role":"user","content":"j-AutoExec\n\nThis component automatically executes a method defined in exec option."},{"role":"assistant","content":"This component automatically executes a method defined in `exec` option.\n\n\n__Configuration__:\n\n- __IMPORTANT__ `exec {String}` a path to the method `function(el, value)`\n- `delay {Number}` a delay for evaluating code (default: `0`)\n- `init {Boolean}` executes initial value (default: `false`)\n- `input {Boolean}` executes value binded by the input (default: `true`)\n- `manually {Boolean}` executes value binded manually via e.g. `SET()` method (default: `false`)\n- `track {String}` can contain partial paths divided by the comma\n\nUse it with `<ui-component name=\"autoexec\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"autoexec\" path=\"common.value\" config=\"exec:autoexec;manually:1\"></ui-component>\n\n<script>\n\n\tfunction autoexec(el, value) {\n\t\tconsole.log('Exec:', value);\n\t\tel.html(value);\n\t}\n\n\tvar common = {};\n\n\tsetInterval(function() {\n\t\tSET('common.value', new Date().format('HH:mm:ss'));\n\t}, 1000);\n\n</script>\n```"}],"knowledge":{"content_sha256":"81e4f9406413d93e08ef32e832409dc8d8d9545b974786ae1cb01c115213af57","source_sha256":"451d333efd7bf6c41324a5c3703cdb30acd81ec45c56f01a60df72014264799d","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","SET"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"autoexec\" path=\"common.value\" config=\"exec:autoexec;manually:1\"></ui-component>\n\n<script>\n\n\tfunction autoexec(el, value) {\n\t\tconsole.log('Exec:', value);\n\t\tel.html(value);\n\t}\n\n\tvar common = {};\n\n\tsetInterval(function() {\n\t\tSET('common.value', new Date().format('HH:mm:ss'));\n\t}, 1000);\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/autofill","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/autofill/","markdown_url":"https://forge.totaljs.es/en/components/autofill.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"46289674ae27efeb602f8821c3cfacbfa12c4b55a2eca3471e5fbffebbbd252d","checked_at":"2026-10-20T12:19:51.997Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-AutoFill","description":"The component wraps all nested elements into the <form> element with disabled form events.","messages":[{"role":"user","content":"j-AutoFill\n\nThe component wraps all nested elements into the <form> element with disabled form events."},{"role":"assistant","content":"The component wraps all nested elements into the `<form>` element with disabled form events.\n\nUse it with `<ui-component name=\"autofill\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-plugin path=\"form\">\n\t<ui-component name=\"autofill\">\n\t\t<ui-component name=\"input\" path=\"?.name\" config=\"autofill:1\">Name</ui-component>\n\t</ui-component>\n</ui-plugin>\n```"}],"knowledge":{"content_sha256":"4aeb3b248f1b239cd1520f15c56fc0f2f4b1d1b9c59ebeeb2367170c27c3cfe9","source_sha256":"46289674ae27efeb602f8821c3cfacbfa12c4b55a2eca3471e5fbffebbbd252d","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-plugin path=\"form\">\n\t<ui-component name=\"autofill\">\n\t\t<ui-component name=\"input\" path=\"?.name\" config=\"autofill:1\">Name</ui-component>\n\t</ui-component>\n</ui-plugin>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/autoselect","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/autoselect/","markdown_url":"https://forge.totaljs.es/en/components/autoselect.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"add0d8be4c7755c2b61adf88c60193a112744bc8f5fc1ca7a50f4c1b875a8caa","checked_at":"2026-10-20T12:19:52.736Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-AutoSelect","description":"The component automatically adds .selected class according to the selector class='autoselect' within the entire HTML document. This component is a perfect solution for server-side rendering and subseq","messages":[{"role":"user","content":"j-AutoSelect\n\nThe component automatically adds .selected class according to the selector class='autoselect' within the entire HTML document. This component is a perfect solution for server-side rendering and subseq"},{"role":"assistant","content":"- singleton\n\nThe component automatically adds `.selected` class according to the selector `class=\"autoselect\"` within the entire HTML document. This component is a perfect solution for server-side rendering and subsequent highlighting.\n\n__Configuration__:\n\n- `selector {String}` is a jQuery selector (default: `.autoselect a`)\n- `class {String}` is a class name for selected element (default: `selected`)\n\nUse it with `<ui-component name=\"autoselect\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\t.links a { display: block; border: 0; text-align: left; color: gray; font-size: 14px; border-radius: 2px; padding: 2px 10px; margin-bottom: 1px; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; position: relative; }\n\t.links a i { width: 14px; text-align: center; font-size: 12px; margin-right: 7px; color: #4285F4; }\n\t.links .selected { font-weight: bold; color: white !important; background-color: #4285F4; }\n\t.links .selected i { color: white !important }\n\t.links a:hover { text-decoration: none; color: black; }\n</style>\n\n<ui-component name=\"autoselect\" path=\"NAV.url\"></ui-component>\n\n<div class=\"links autoselect\">\n\t<div class=\"m\">Menu 1</div>\n\t<a href=\"/\"><i class=\"ti ti-users\"></i>Users</a>\n\t<a href=\"/products/\"><i class=\"ti ti-box\"></i>Products</a>\n\t<a href=\"/settings/\"><i class=\"ti ti-cog\"></i>Settings</a>\n</div>\n<br />\n<div class=\"links autoselect\">\n\t<div class=\"m\">Menu 2</div>\n\t<a href=\"/\"><i class=\"ti ti-users\"></i>Users</a>\n\t<a href=\"/products/\"><i class=\"ti ti-box\"></i>Products</a>\n\t<a href=\"/settings/\"><i class=\"ti ti-cog\"></i>Settings</a>\n</div>\n\n<script>\n\n\tvar arr = ['/', '/products/', '/settings/'];\n\n\tsetInterval(function() {\n\t\tREDIRECT(arr[(Math.random() * arr.length) >> 0]);\n\t}, 1500);\n\n</script>\n```"}],"knowledge":{"content_sha256":"4e0c7372a832ec191efe44cc481869979c176fdb208253ec729135469c0d46ff","source_sha256":"add0d8be4c7755c2b61adf88c60193a112744bc8f5fc1ca7a50f4c1b875a8caa","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","HTML","MIT","NAV","REDIRECT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\t.links a { display: block; border: 0; text-align: left; color: gray; font-size: 14px; border-radius: 2px; padding: 2px 10px; margin-bottom: 1px; text-overflow: ellipsis; overflow: hidden; white-space: nowrap; position: relative; }\n\t.links a i { width: 14px; text-align: center; font-size: 12px; margin-right: 7px; color: #4285F4; }\n\t.links .selected { font-weight: bold; color: white !important; background-color: #4285F4; }\n\t.links .selected i { color: white !important }\n\t.links a:hover { text-decoration: none; color: black; }\n</style>\n\n<ui-component name=\"autoselect\" path=\"NAV.url\"></ui-component>\n\n<div class=\"links autoselect\">\n\t<div class=\"m\">Menu 1</div>\n\t<a href=\"/\"><i class=\"ti ti-users\"></i>Users</a>\n\t<a href=\"/products/\"><i class=\"ti ti-box\"></i>Products</a>\n\t<a href=\"/settings/\"><i class=\"ti ti-cog\"></i>Settings</a>\n</div>\n<br />\n<div class=\"links autoselect\">\n\t<div class=\"m\">Menu 2</div>\n\t<a href=\"/\"><i class=\"ti ti-users\"></i>Users</a>\n\t<a href=\"/products/\"><i class=\"ti ti-box\"></i>Products</a>\n\t<a href=\"/settings/\"><i class=\"ti ti-cog\"></i>Settings</a>\n</div>\n\n<script>\n\n\tvar arr = ['/', '/products/', '/settings/'];\n\n\tsetInterval(function() {\n\t\tREDIRECT(arr[(Math.random() * arr.length) >> 0]);\n\t}, 1500);\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/avatar","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/avatar/","markdown_url":"https://forge.totaljs.es/en/components/avatar.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"47a6e3492dfc93c21ec5938c647a989f2817d8253835b2d5c1289ff0ce1ad658","checked_at":"2026-10-20T12:19:53.475Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Avatar","description":"Avatar component needs to register your own theme. It contains default theme.","messages":[{"role":"user","content":"j-Avatar\n\nAvatar component needs to register your own theme. It contains default theme."},{"role":"assistant","content":"- supports themes\n- it's singleton\n- inspiration from `react-user-avatar`\n\n__Themes__:\n\nAvatar component needs to register your own theme. It contains `default` theme.\n\n```javascript\nSETTER(true, 'avatar', 'register', 'THEME_NAME', 'options')\n```\n\nOptions can contain:\n\n- `size` {Number} Optional, a size in pixels (default: `100`)\n- `lighten` {Number} Optional, the font color will be lightened by lighten factor of background color (default: `80` | from -255 to 255)\n- `radius` {Number} Optional, how rounded border corners will be (default: `50`)\n- `background` {Hex} Optional, set background color (default: selected from colors `Array`)\n- `color` {Hex} Optional, set font color (default: generated from background color by lighten)\n- `font` {String} Optional, set font family (default: `Arial`)\n- `weight` {String} Optional, font-weight (default: `bold`)\n- `class` {String} Optional, this property perfoms `toggleClass` for this class\n\n__Usage__:\n\nEach element of avatar needs to contain `avatar` class like this: `<div class=\"avatar\">`. Here are custom attributes:\n\n- `data-a=\"THEME_NAME\"` Optional, can contain a theme name (default: `default`)\n- `data-a-url=\"URL_TO_PICTURE\"` Optional, can contain a URL for user photo\n- `data-a-class=\"class_for_toggle_effect\"` Optional, can contain a class for `toggleClass` effect\n\nElement needs to contain a user name, for example:\n\n- `<div class=\"avatar\">Peter Širka</div>` or\n- `<a href=\"#\" class=\"avatar\">Peter Širka</a>`\n\n__Good to know__:\n\n- avatar component binds all new avatars in all new components automatically\n- `SETTER('avatar', 'refresh')` performs refreshing\n\nUse it with `<ui-component name=\"avatar\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Denis Granec. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"avatar\"></ui-component>\n\n<a href=\"https://www.totaljs.com\" class=\"avatar hidden\" data-a-class=\"hidden\" data-a-url=\"https://scontent.fbts5-1.fna.fbcdn.net/v/t1.0-9/12552918_10209045123711641_5810437084053575333_n.jpg?oh=f0d9f5acdbab2f1f2cf165bf9aed8da5&oe=5A3F378D\">Peter Širka</a>\n<div class=\"avatar hidden\" data-a-class=\"hidden\">Martin Smola</div>\n<div class=\"avatar hidden\" data-a-class=\"hidden\" data-a-url=\"https://avatars3.githubusercontent.com/u/7585285?v=4&s=460\">Denis Granec</div>\n<div class=\"avatar hidden\" data-a-class=\"hidden\">Denis Oliver</div>\n<div class=\"avatar hidden\" data-a=\"black\">Kristle Leverone</div>\n<div class=\"avatar hidden\" data-a=\"black\">Denis</div>\n<div class=\"avatar hidden\" data-a=\"black\">Peter</div>\n\n<script>\n\t// Registers a new theme\n\tSETTER(true, 'avatar/register', 'black', 'background:black;color:white;class:hidden');\n</script>\n```"}],"knowledge":{"content_sha256":"979bd45a30450b1d777fde37685a390e473c963ddbe0607683151c1951d11c28","source_sha256":"47a6e3492dfc93c21ec5938c647a989f2817d8253835b2d5c1289ff0ce1ad658","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","SETTER","THEME_NAME","URL","URL_TO_PICTURE"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"avatar\"></ui-component>\n\n<a href=\"https://www.totaljs.com\" class=\"avatar hidden\" data-a-class=\"hidden\" data-a-url=\"https://scontent.fbts5-1.fna.fbcdn.net/v/t1.0-9/12552918_10209045123711641_5810437084053575333_n.jpg?oh=f0d9f5acdbab2f1f2cf165bf9aed8da5&oe=5A3F378D\">Peter Širka</a>\n<div class=\"avatar hidden\" data-a-class=\"hidden\">Martin Smola</div>\n<div class=\"avatar hidden\" data-a-class=\"hidden\" data-a-url=\"https://avatars3.githubusercontent.com/u/7585285?v=4&s=460\">Denis Granec</div>\n<div class=\"avatar hidden\" data-a-class=\"hidden\">Denis Oliver</div>\n<div class=\"avatar hidden\" data-a=\"black\">Kristle Leverone</div>\n<div class=\"avatar hidden\" data-a=\"black\">Denis</div>\n<div class=\"avatar hidden\" data-a=\"black\">Peter</div>\n\n<script>\n\t// Registers a new theme\n\tSETTER(true, 'avatar/register', 'black', 'background:black;color:white;class:hidden');\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/backtotop","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/backtotop/","markdown_url":"https://forge.totaljs.es/en/components/backtotop.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"e8566f37551b40d380d7c3c5460649f130a0d53ba34940d60dc540add31a07cf","checked_at":"2026-10-20T12:19:54.357Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-BackToTop","description":"j-BackToTop: works as singleton","messages":[{"role":"user","content":"j-BackToTop\n\nj-BackToTop: works as singleton"},{"role":"assistant","content":"- works as singleton\n\n__Configuration__:\n\n- `icon {String}` Default button icon\n\nUse it with `<ui-component name=\"backtotop\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Martin Jirku. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"backtotop\"></ui-component>\n\n<div style=\"height:1500px\"></div>\n```"}],"knowledge":{"content_sha256":"2cddcd9df40a38c54632f7336a8016b6e24eaea92539bf9553bddb39d020b715","source_sha256":"e8566f37551b40d380d7c3c5460649f130a0d53ba34940d60dc540add31a07cf","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"backtotop\"></ui-component>\n\n<div style=\"height:1500px\"></div>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/backup","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/backup/","markdown_url":"https://forge.totaljs.es/en/components/backup.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"fb327cd45f8502426f0c0f476dbd0a5f827abf5869b904d01ccc008b3f588b5d","checked_at":"2026-10-20T12:19:55.096Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Backup","description":"The component for data saving data to and loading from the local storage of the browser.","messages":[{"role":"user","content":"j-Backup\n\nThe component for data saving data to and loading from the local storage of the browser."},{"role":"assistant","content":"The component for data saving data to and loading from the local storage of the browser.\n\n- supports __dark mode__\n\n__Configuration__:\n\n- `exec {String}` Optional property which contains a link to a function, which will be executed after clicking on a button of the component. The function can have an argument, which is the name of a clicked button - function(name).\n- `expire {String}` Optional property, which specifies the period during which the data will be stored in the local storage. The default value is '1 day'. The value of the property should be written in the following format: \"number [seconds, minutes, hours, days, weeks, months, years]\"\n\nUse it with `<ui-component name=\"backup\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Kirill Zlobin. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"backup\" path=\"userform\" class=\"m\"></ui-component>\n\n<ui-plugin path=\"userform\">\n\t<div class=\"row\">\n\t\t<div class=\"col-xs-6 m\">\n\t\t\t<ui-component name=\"input\" path=\"?.firstname\">First name</ui-component>\n\t\t</div>\n\t\t<div class=\"col-xs-6 m\">\n\t\t\t<ui-component name=\"input\" path=\"?.lastname\">Last name</ui-component>\n\t\t</div>\n\t</div>\n</ui-plugin>\n```"}],"knowledge":{"content_sha256":"d8597d14b01b327b11f32642eb89553c8a29254c5c9a74da9f303490f0248703","source_sha256":"fb327cd45f8502426f0c0f476dbd0a5f827abf5869b904d01ccc008b3f588b5d","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"backup\" path=\"userform\" class=\"m\"></ui-component>\n\n<ui-plugin path=\"userform\">\n\t<div class=\"row\">\n\t\t<div class=\"col-xs-6 m\">\n\t\t\t<ui-component name=\"input\" path=\"?.firstname\">First name</ui-component>\n\t\t</div>\n\t\t<div class=\"col-xs-6 m\">\n\t\t\t<ui-component name=\"input\" path=\"?.lastname\">Last name</ui-component>\n\t\t</div>\n\t</div>\n</ui-plugin>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/banners","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/banners/","markdown_url":"https://forge.totaljs.es/en/components/banners.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"38ae50f814abe24a031547102461852bc3cfb473524010ddb51f128f45b7d671","checked_at":"2026-10-20T12:19:55.838Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Banners","description":"j-Banners: fully responsive; simple CSS animation","messages":[{"role":"user","content":"j-Banners\n\nj-Banners: fully responsive; simple CSS animation"},{"role":"assistant","content":"- fully responsive\n- simple CSS animation\n- it needs an empty picture for width/height defined in config: `empty:/download/banner-empty.gif`\n\nUse it with `<ui-component name=\"banners\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"banners\" config=\"interval:3000;empty:/static/banners-empty.gif\" class=\"invisible\">\n\t<div><img src=\"/static/banners-1.jpg\" alt=\"A\" class=\"img-responsive\" border=\"0\" /></div>\n\t<div class=\"ui-banners-hidden\"><img src=\"/static/banners-2.jpg\" alt=\"B\" class=\"img-responsive\" border=\"0\" /></div>\n\t<div class=\"ui-banners-hidden\"><img src=\"/static/banners-3.jpg\" alt=\"C\" class=\"img-responsive\" border=\"0\" /></div>\n\t<div class=\"ui-banners-hidden\"><img src=\"/static/banners-4.jpg\" alt=\"D\" class=\"img-responsive\" border=\"0\" /></div>\n\t<div class=\"ui-banners-hidden\"><img src=\"/static/banners-5.jpg\" alt=\"E\" class=\"img-responsive\" border=\"0\" /></div>\n</ui-component>\n```"}],"knowledge":{"content_sha256":"abb8a74c2e502bc28f3b3b919249b7a0eaf06002d72c6c3593aaf28b51ed2aab","source_sha256":"38ae50f814abe24a031547102461852bc3cfb473524010ddb51f128f45b7d671","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","CSS","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"banners\" config=\"interval:3000;empty:/static/banners-empty.gif\" class=\"invisible\">\n\t<div><img src=\"/static/banners-1.jpg\" alt=\"A\" class=\"img-responsive\" border=\"0\" /></div>\n\t<div class=\"ui-banners-hidden\"><img src=\"/static/banners-2.jpg\" alt=\"B\" class=\"img-responsive\" border=\"0\" /></div>\n\t<div class=\"ui-banners-hidden\"><img src=\"/static/banners-3.jpg\" alt=\"C\" class=\"img-responsive\" border=\"0\" /></div>\n\t<div class=\"ui-banners-hidden\"><img src=\"/static/banners-4.jpg\" alt=\"D\" class=\"img-responsive\" border=\"0\" /></div>\n\t<div class=\"ui-banners-hidden\"><img src=\"/static/banners-5.jpg\" alt=\"E\" class=\"img-responsive\" border=\"0\" /></div>\n</ui-component>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/barchart","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/barchart/","markdown_url":"https://forge.totaljs.es/en/components/barchart.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"1599f66b775219c8331976734da22a9a80218a5c31e550914b325bcc6f953adc","checked_at":"2026-10-20T12:19:56.726Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-BarChart","description":"Please review an example and keep a data-source.","messages":[{"role":"user","content":"j-BarChart\n\nPlease review an example and keep a data-source."},{"role":"assistant","content":"Please review an example and keep a data-source.\n\n\n__Configuration__:\n\n- `paddingbars {Number}` padding between bars (default: `5`)\n- `paddinggroup {Number}` padding between groups of bars (default: `10`)\n- `radius {Number}` enables presentation mode (default: `2`)\n- `offsetX {Number}` An offset X for value tooltip (default: `10`)\n- `offsetY {Number}` An offset Y for value tooltip (default: `10`)\n- `templateX {String}` Tangular template for X axis (default: `{{ value }}`)\n- `templateY {String}` Tangular template for Y axis (default: `{{ value | format(0) }}`)\n- `height {Number/String}` height (default: `0` = auto-height) or `string` is meant as a parent element\n- `width {Number/String}` width (default: `0` = auto-width) or `string` is meant as a parent element\n- `limit {Number}` a maximum limit of value for Y axis (default: `0`)\n- `pl {Number}` left padding for axis (default: `0`)\n- `pr {Number}` lines right padding (default: `10`)\n- `pt {Number}` lines top padding (default: `10`)\n- `pb {Number}` lines bottom padding (default: `25`)\n- `prselected {Number}` right padding for selected value (default: `0`)\n- `marginW {Number}` a margin for `width`\n- `marginH {Number}` a margin for `height`\n\nUse it with `<ui-component name=\"barchart\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div class=\"container\">\n\t<div class=\"row\">\n\t\t<div class=\"col-sm-6\">\n\t\t\t<ui-component name=\"barchart\" path=\"values\"></ui-component>\n\t\t</div>\n\t</div>\n</div>\n\n<script>\n\tvar values = [];\n\n\tvalues.push({\n\t\tname: 'Apples',\n\t\tvalues: [{ x: 2009, y: 100 }, { x: 2010, y: 120 }, { x: 2011, y: 130 }, { x: 2012, y: 150 }, { x: 2013, y: 50 }, { x: 2014, y: 200 }, { x: 2015, y: 80 }, { x: 2016, y: 170 }, { x: 2017, y: 300 }]\n\t});\n\n\tvalues.push({\n\t\tname: 'Oranges',\n\t\tvalues: [{ x: 2009, y: 40 }, { x: 2010, y: 80 }, { x: 2011, y: 400 }, { x: 2012, y: 170 }, { x: 2013, y: 90 }, { x: 2014, y: 60 }, { x: 2015, y: 160 }, { x: 2016, y: 120 }, { x: 2017, y: 180 }]\n\t});\n\n\tvalues.push({\n\t\tname: 'Bananas',\n\t\tvalues: [{ x: 2009, y: 80 }, { x: 2010, y: 40 }, { x: 2011, y: 300 }, { x: 2012, y: 200 }, { x: 2013, y: 120 }, { x: 2014, y: 150 }, { x: 2015, y: 220 }, { x: 2016, y: 40 }, { x: 2017, y: 80 }]\n\t});\n</script>\n```"}],"knowledge":{"content_sha256":"cea304818ea5d9fec74a7586217cf05c3faceb61a4baf158589fc9914f743240","source_sha256":"1599f66b775219c8331976734da22a9a80218a5c31e550914b325bcc6f953adc","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div class=\"container\">\n\t<div class=\"row\">\n\t\t<div class=\"col-sm-6\">\n\t\t\t<ui-component name=\"barchart\" path=\"values\"></ui-component>\n\t\t</div>\n\t</div>\n</div>\n\n<script>\n\tvar values = [];\n\n\tvalues.push({\n\t\tname: 'Apples',\n\t\tvalues: [{ x: 2009, y: 100 }, { x: 2010, y: 120 }, { x: 2011, y: 130 }, { x: 2012, y: 150 }, { x: 2013, y: 50 }, { x: 2014, y: 200 }, { x: 2015, y: 80 }, { x: 2016, y: 170 }, { x: 2017, y: 300 }]\n\t});\n\n\tvalues.push({\n\t\tname: 'Oranges',\n\t\tvalues: [{ x: 2009, y: 40 }, { x: 2010, y: 80 }, { x: 2011, y: 400 }, { x: 2012, y: 170 }, { x: 2013, y: 90 }, { x: 2014, y: 60 }, { x: 2015, y: 160 }, { x: 2016, y: 120 }, { x: 2017, y: 180 }]\n\t});\n\n\tvalues.push({\n\t\tname: 'Bananas',\n\t\tvalues: [{ x: 2009, y: 80 }, { x: 2010, y: 40 }, { x: 2011, y: 300 }, { x: 2012, y: 200 }, { x: 2013, y: 120 }, { x: 2014, y: 150 }, { x: 2015, y: 220 }, { x: 2016, y: 40 }, { x: 2017, y: 80 }]\n\t});\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/box","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/box/","markdown_url":"https://forge.totaljs.es/en/components/box.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"d5559e3316e6df0761d9875dc71a22f9e7ea69b57903cd23cb0a65248ee45d14","checked_at":"2026-10-20T12:19:57.605Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Box","description":"The component supports dynamic evaluation of the content of <script type='text/html'>. The example below contains a script with HTML and the component evaluates the content if the j-Form will be displ","messages":[{"role":"user","content":"j-Box\n\nThe component supports dynamic evaluation of the content of <script type='text/html'>. The example below contains a script with HTML and the component evaluates the content if the j-Form will be displ"},{"role":"assistant","content":"__Configuration__:\n\n- `title {String}` form title\n- `if {String}` condition for showing of the form, it's compared with the value within of `path`\n- `icon {String}` Font-Awesome icon without `ti-` or __new__: `ti ti-home`\n- `reload {String}` link to a global `function(fullform_component)` and it's executed if the form is displaying\n- `submit {String}` link to a global `function(hide)` and it's executed if the submit button is pressed\n- `cancel {String}` link to a global `function(hide)` and it's executed if the cancel button is pressed\n- `enter {Boolean}` captures `enter` key automatically and performs submit (default: `false`)\n- `center {Boolean}` centers the form to middle of screen\n- `autofocus {Boolean/String}` can focus an input. `String` === `jQuery selector` for the input\n- `default {String}` a short alias for `DEFAULT(default, true)`\n- `closebutton {Boolean}` can hide `x` button (default: `false`)\n- `zindex {Number}` can affect z-index (default: `12`)\n- `scrollbar {Boolean}` enables custom scrollbar (default: `true`)\n- `scrolltop {Boolean}` enables scrolling to top after form is displayed (default: `true`)\n- `scrollbarshadow {Boolean}` optional, enables a shadow in scrollbar (default: `false`)\n- `style {Number}` supports two styles (1: classic (default), 2: box bottom without padding, 3: box top without padding)\n- `closeoutside {Boolean}` closes the form when the user clicks outside of the form (default: `false`)\n- `closeesc {Boolean}` closes the form when the user presses `ESC` key\n- `independent {Boolean}` closing doesn't depent on the condition according to the path\n- `padding {Number}` padding for the box (default: `30`)\n- `width {Number}` optional, a maximal width (default: `0` - full width)\n- `background {Boolean}` optional, enables/disables background (default: `true`)\n- `transparent {Boolean}` optional, all elements on the background of the box will be clickable (default: `false`)\n- `align {String}` optional, `center` (default), `left`, `right`\n- `close {String}` link to a global `function()` and it's executed if the form is closing\n- __NEW__ `hide {String}` link to a global `function()` and it's executed if the form hides\n\nThe component supports dynamic evaluation of the content of `<script type=\"text/html\">`. The example below contains a script with HTML and the component evaluates the content if the j-Form will be displayed (only once).\n\n```html\n<ui-component name=\"box\" path=\"path\" config=\"config\">\n\t<script type=\"text/html\">\n\t\tA CONTENT\n\t</script>\n</ui-component>\n```\n\nUse it with `<ui-component name=\"box\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"box\" path=\"common.form\" config=\"if:user;width:500;title:Title;submit:userform_submit\" class=\"hidden\">\n\t<nav>\n\t\t<button><i class=\"ti ti-angle-down\"></i>Menu</button>\n\t</nav>\n\t<div class=\"padding\">\n\t\tSOME CONTENT\n\t</div>\n\t<nav>\n\t\t<button name=\"submit\">SUBMIT</button>\n\t\t<button name=\"cancel\">CANCEL</button>\n\t</nav>\n</ui-component>\n\n<script>\n\n\tfunction userform_submit(hide) {\n\t\talert('SUBMIT');\n\t\thide();\n\t}\n\n\tvar common = {};\n\n\t// Show the form\n\tcommon.form = 'user';\n</script>\n```"}],"knowledge":{"content_sha256":"9cfbaa16fa1c7ab6a0947124e4baefe5ad033123f91f8dcdb153d3a874d3f634","source_sha256":"d5559e3316e6df0761d9875dc71a22f9e7ea69b57903cd23cb0a65248ee45d14","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CANCEL","CDN","CONTENT","DEF","DEFAULT","ESC","HTML","MIT","SOME","SUBMIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"box\" path=\"common.form\" config=\"if:user;width:500;title:Title;submit:userform_submit\" class=\"hidden\">\n\t<nav>\n\t\t<button><i class=\"ti ti-angle-down\"></i>Menu</button>\n\t</nav>\n\t<div class=\"padding\">\n\t\tSOME CONTENT\n\t</div>\n\t<nav>\n\t\t<button name=\"submit\">SUBMIT</button>\n\t\t<button name=\"cancel\">CANCEL</button>\n\t</nav>\n</ui-component>\n\n<script>\n\n\tfunction userform_submit(hide) {\n\t\talert('SUBMIT');\n\t\thide();\n\t}\n\n\tvar common = {};\n\n\t// Show the form\n\tcommon.form = 'user';\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/breadcrumb","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/breadcrumb/","markdown_url":"https://forge.totaljs.es/en/components/breadcrumb.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"665eb782204c36311ddcb8f79aeddcfee46689c204a7e12591aa2cc3bd00ab11","checked_at":"2026-10-20T12:19:58.364Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Breadcrumb","description":"j-Breadcrumb: NEW: arrowicon {String} arrow icon (default: ti ti-angle-right)","messages":[{"role":"user","content":"j-Breadcrumb\n\nj-Breadcrumb: NEW: arrowicon {String} arrow icon (default: ti ti-angle-right)"},{"role":"assistant","content":"__Configuration__:\n\n- `icon {String}` home icon (default: `ti ti-home`)\n- `exec {String}` path to the method `function(url, el)` for manual processing of links\n- `historyapi {Boolean}` enables redirecting via `REDIRECT()` (default `true`)\n- `style {Number}` supports two styles `1` classic (default), `2` with bigger padding and only with bottom border\n- `root {String}` a label for root item (default: `Root`)\n- `rooturl {String}` a relative URL address for root item (default: `/`)\n- `title {String}` enables generating `document.title` from the last item of the breadcrumb (default: `null`)\n- __NEW__: `arrowicon {String}` arrow icon (default: `ti ti-angle-right`)\n\n## Data-source example\n\n```js\n[\n\t{\n\t\tname: 'Home',                // A link label\n\t\turl: '/'                     // URL address\n\t\tcallback: Function(el, e)    // Optional, a callback for the click event\n\t},\n\t{\n\t\tname: 'Products',            // A link label\n\t\turl: '/'                     // URL address\n\t}\n];\n```\n\n## Methods\n\n- `component.add(name1, url1, [callback])(name2, url2)(name3, url3)...`\n- `component.root()` it resets a breadcrumb\n\nUse it with `<ui-component name=\"breadcrumb\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"breadcrumb\" path=\"common.breadcrumb\">\n\t<div>Here you can use j-VirtualWire component</div>\n</ui-component>\n\n<script>\n\tvar common = {};\n\tcommon.breadcrumb = [{ name: 'Home', url: '/' }, { name: 'Entities', url: '/entities/' }];\n</script>\n```"}],"knowledge":{"content_sha256":"f6b15bf709de4fa96624418a2a37ea0d0d5e417740570d94dbbc285ac02e4157","source_sha256":"665eb782204c36311ddcb8f79aeddcfee46689c204a7e12591aa2cc3bd00ab11","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","REDIRECT","URL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"breadcrumb\" path=\"common.breadcrumb\">\n\t<div>Here you can use j-VirtualWire component</div>\n</ui-component>\n\n<script>\n\tvar common = {};\n\tcommon.breadcrumb = [{ name: 'Home', url: '/' }, { name: 'Entities', url: '/entities/' }];\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/button","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/button/","markdown_url":"https://forge.totaljs.es/en/components/button.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"07c497c99371c6632d9e07e132acca8705f98a1eb28e782ceb88b2eca70178bb","checked_at":"2026-10-20T12:19:59.252Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Button","description":"A simple button component with great features.","messages":[{"role":"user","content":"j-Button\n\nA simple button component with great features."},{"role":"assistant","content":"A simple button component with great features.\n\n- fully responsive\n- supports validation\n\n__Configuration__:\n\n- `exec {String}` __required__ a path to the `function(el, path)` method\n- `icon {String}` Total JS icons (default: `undefined`)\n- `color {String}` An icon color (default: `undefined`)\n- `validation {Boolean}` Enables validation for all components on the path (default: `true`)\n- `delay` {Number} a timeout for validation (default: `100`)\n- `name {String}` a name for the `name` attribute (default: `submit`)\n- `size {String}` a size:\n\t- `small`\n\t- `normal` (default)\n\t- `large`\n\t- `big`\n- `ddos {String}` A simple DDOS prevetion for multiple clicks, possible values:\n\t- `undefined` disables prevention\n\t- `10 seconds`\n\t- `session`\n\t- `1 day`\n\t- etc.\n- `if` {String} can contain JS condition for evaluation(optional)\n\t- `value` {Object} contains entire model\n\t- `path` {String} contains changed path\n\t- __must return boolean__\n- `track` {String} can contain paths divided by comma which evaluate validation (executors for paths aren't components, but these methods `SET()`, `UPD()`, `INC()`, etc.)\n- `validonly` {Boolean} skips `dirty` state (default: `false`)\n\nUse it with `<ui-component name=\"button\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div class=\"row\">\n\t<div class=\"col-md-3 col-xs-6\">\n\t\t<ui-component name=\"button\" config=\"exec:clickme\" class=\"hidden\">Show me</ui-component>\n\t</div>\n</div>\n\n<script>\n\n\tfunction clickme() {\n\t\talert('Clicked!');\n\t}\n\n</script>\n```"}],"knowledge":{"content_sha256":"f5648b9c9e95a91ed8d9b26516e759e0205fa1be2394e7a03e357aa0ee7ab53f","source_sha256":"07c497c99371c6632d9e07e132acca8705f98a1eb28e782ceb88b2eca70178bb","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DDOS","DEF","INC","MIT","SET","UPD"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div class=\"row\">\n\t<div class=\"col-md-3 col-xs-6\">\n\t\t<ui-component name=\"button\" config=\"exec:clickme\" class=\"hidden\">Show me</ui-component>\n\t</div>\n</div>\n\n<script>\n\n\tfunction clickme() {\n\t\talert('Clicked!');\n\t}\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/calendar","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/calendar/","markdown_url":"https://forge.totaljs.es/en/components/calendar.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"374ec24c288d26480736ed38d3dfd7fc1d7ee125821a8fa78dea9d0f4a0c3671","checked_at":"2026-10-20T12:20:00.145Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Calendar","description":"The component must be called manually or works with textbox or j-Input component automatically. The component is a singleton.","messages":[{"role":"user","content":"j-Calendar\n\nThe component must be called manually or works with textbox or j-Input component automatically. The component is a singleton."},{"role":"assistant","content":"- supports __dark mode__\n- supports __touch gestures (swipe)__ and fixed full size on mobile\n\n__Configuration__:\n\n- `firstday` {Number} (optional) first day in the week\n- `today` {String} (optional) label for `Set today` button\n- `days` {String} (optional) days e.g. `SU,MO,TU,WE,TH,FR,SA` (default).\n- `months` {String} (optional) months e.g. `January,February,...` (default).\n- `yearselect` {Boolean} (optional) dropdown with years (default `true`).\n- `monthselect` {Boolean} (optional) dropdown with months (default `true`).\n- `yearfrom` {String} (optional) set minimum year limit in dropdown e.g. `-5 years` `current`  (default `-100 years`).\n- `yearto` {String} (optional) set maximum year limit in dropdown  `+5 years` `current` (default `current`).\n\nThe component must be called manually or works with `textbox` or `j-Input` component automatically. The component is a singleton.\n\n- works __only__ with Bootstrap Grid System (otherwise is need to fix CSS)\n- __important__ add the calendar component under `<body>` element\n\n## Usage\n\n```javascript\nvar ELEMENT = $(document);\nvar PATH = 'some.path.to.date';\nvar X_OFFSET = 100;\nFIND('calendar').toggle(ELEMENT, PATH, function(date) {\n\tconsole.log('SELECTED DATE:', date);\n}, X_OFFSET);\n\n// OR\n\nFIND('calendar').toggle(ELEMENT, 'path.to.date OR date object', 'path.to.binding.date');\n```\n\nUse it with `<ui-component name=\"calendar\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<a href=\"#calendar\">SHOW CALENDAR</a>\n<ui-component name=\"calendar\" config=\"today:Set today;firstday:0\"></ui-component>\n\n<script>\n\tvar form = {};\n\tform.date = new Date();\n\n\t$('a[href=\"#calendar\"]').on('click', function(e) {\n\t\tSETTER('calendar', 'toggle', $(this), form.date, function(date) {\n\t\t\talert(date);\n\t\t});\n\t});\n</script>\n```"}],"knowledge":{"content_sha256":"92c1d1b46586864f93ac3c32e61f0b4464c046ba8e569d2f3f5bc8166fdbc36f","source_sha256":"374ec24c288d26480736ed38d3dfd7fc1d7ee125821a8fa78dea9d0f4a0c3671","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CALENDAR","CDN","CSS","DATE","DEF","ELEMENT","FIND","MIT","PATH","SELECTED","SETTER","SHOW","X_OFFSET"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<a href=\"#calendar\">SHOW CALENDAR</a>\n<ui-component name=\"calendar\" config=\"today:Set today;firstday:0\"></ui-component>\n\n<script>\n\tvar form = {};\n\tform.date = new Date();\n\n\t$('a[href=\"#calendar\"]').on('click', function(e) {\n\t\tSETTER('calendar', 'toggle', $(this), form.date, function(date) {\n\t\t\talert(date);\n\t\t});\n\t});\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/carousel","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/carousel/","markdown_url":"https://forge.totaljs.es/en/components/carousel.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"f167c8dd2be7b229872c54ff37c22a8d20101ed6a4b468013159d7d3889c9566","checked_at":"2026-10-20T12:20:00.878Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Carousel","description":"j-Carousel: supports touch gestures (swipe) and fixed full size on mobile; learn from example","messages":[{"role":"user","content":"j-Carousel\n\nj-Carousel: supports touch gestures (swipe) and fixed full size on mobile; learn from example"},{"role":"assistant","content":"- supports __touch gestures (swipe)__ and fixed full size on mobile\n__Configuration__:\n\n- `animate {Number}` animation interval (default: `3000`, zero `0` disables animation)\n\n__Methods__:\n\n- `left` moves the content to left\n- `right` moves the content to right\n\nUse it with `<ui-component name=\"carousel\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"width:500px\">\n\t<ui-component name=\"carousel\" path=\"datasource\" class=\"invisible\">\n\t\t<script type=\"text/html\">\n\t\t\t<div style=\"width:300px;background-color:#E0E0E0;height:150px;margin:0 10px 0 0\">{{ name }}</div>\n\t\t</script>\n\t</ui-component>\n</div>\n<br />\n<div style=\"width:500px\">\n\t<ui-component name=\"carousel\" class=\"invisible\">\n\t\t<figure><div style=\"width:300px;background-color:#E0E0E0;height:150px;margin:0 10px 0 0\">A</div></figure>\n\t\t<figure><div style=\"width:300px;background-color:#E0E0E0;height:150px;margin:0 10px 0 0\">B</div></figure>\n\t\t<figure><div style=\"width:300px;background-color:#E0E0E0;height:150px;margin:0 10px 0 0\">C</div></figure>\n\t\t<figure><div style=\"width:300px;background-color:#E0E0E0;height:150px;margin:0 10px 0 0\">D</div></figure>\n\t\t<figure><div style=\"width:300px;background-color:#E0E0E0;height:150px;margin:0 10px 0 0\">E</div></figure>\n\t\t<figure><div style=\"width:300px;background-color:#E0E0E0;height:150px;margin:0 10px 0 0\">F</div></figure>\n\t\t<figure><div style=\"width:300px;background-color:#E0E0E0;height:150px;margin:0 10px 0 0\">G</div></figure>\n\t\t<figure><div style=\"width:300px;background-color:#E0E0E0;height:150px;margin:0 10px 0 0\">H</div></figure>\n\t\t<figure><div style=\"width:300px;background-color:#E0E0E0;height:150px;margin:0 10px 0 0\">I</div></figure>\n\t\t<figure><div style=\"width:300px;background-color:#E0E0E0;height:150px;margin:0 10px 0 0\">J</div></figure>\n\t</ui-component>\n</div>\n\n<br />\n<button class=\"exec\" data-exec=\"goLeft\"><i class=\"ti ti-angle-left\"></i></button>\n<button class=\"exec\" data-exec=\"goRight\"><i class=\"ti ti-angle-right\"></i></button>\n\n<script>\n\n\tfunction goLeft() {\n\t\tSETTER('carousel/left');\n\t}\n\n\tfunction goRight() {\n\t\tSETTER('carousel/right');\n\t}\n\n\tvar datasource = [{ name: 'A' }, { name: 'B' }, { name: 'C' }, { name: 'D' }, { name: 'E' }, { name: 'F' }, { name: 'G' }, { name: 'H' }, { name: 'I' }, { name: 'J' }]\n</script>\n```"}],"knowledge":{"content_sha256":"fca3689ed6fa253b2423d4a8a24fa02f15edc44212d522aac9cb1c74a7be648a","source_sha256":"f167c8dd2be7b229872c54ff37c22a8d20101ed6a4b468013159d7d3889c9566","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","E0E0E0","MIT","SETTER"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"width:500px\">\n\t<ui-component name=\"carousel\" path=\"datasource\" class=\"invisible\">\n\t\t<script type=\"text/html\">\n\t\t\t<div style=\"width:300px;background-color:#E0E0E0;height:150px;margin:0 10px 0 0\">{{ name }}</div>\n\t\t</script>\n\t</ui-component>\n</div>\n<br />\n<div style=\"width:500px\">\n\t<ui-component name=\"carousel\" class=\"invisible\">\n\t\t<figure><div style=\"width:300px;background-color:#E0E0E0;height:150px;margin:0 10px 0 0\">A</div></figure>\n\t\t<figure><div style=\"width:300px;background-color:#E0E0E0;height:150px;margin:0 10px 0 0\">B</div></figure>\n\t\t<figure><div style=\"width:300px;background-color:#E0E0E0;height:150px;margin:0 10px 0 0\">C</div></figure>\n\t\t<figure><div style=\"width:300px;background-color:#E0E0E0;height:150px;margin:0 10px 0 0\">D</div></figure>\n\t\t<figure><div style=\"width:300px;background-color:#E0E0E0;height:150px;margin:0 10px 0 0\">E</div></figure>\n\t\t<figure><div style=\"width:300px;background-color:#E0E0E0;height:150px;margin:0 10px 0 0\">F</div></figure>\n\t\t<figure><div style=\"width:300px;background-color:#E0E0E0;height:150px;margin:0 10px 0 0\">G</div></figure>\n\t\t<figure><div style=\"width:300px;background-color:#E0E0E0;height:150px;margin:0 10px 0 0\">H</div></figure>\n\t\t<figure><div style=\"width:300px;background-color:#E0E0E0;height:150px;margin:0 10px 0 0\">I</div></figure>\n\t\t<figure><div style=\"width:300px;background-color:#E0E0E0;height:150px;margin:0 10px 0 0\">J</div></figure>\n\t</ui-component>\n</div>\n\n<br />\n<button class=\"exec\" data-exec=\"goLeft\"><i class=\"ti ti-angle-left\"></i></button>\n<button class=\"exec\" data-exec=\"goRight\"><i class=\"ti ti-angle-right\"></i></button>\n\n<script>\n\n\tfunction goLeft() {\n\t\tSETTER('carousel/left');\n\t}\n\n\tfunction goRight() {\n\t\tSETTER('carousel/right');\n\t}\n\n\tvar datasource = [{ name: 'A' }, { name: 'B' }, { name: 'C' }, { name: 'D' }, { name: 'E' }, { name: 'F' }, { name: 'G' }, { name: 'H' }, { name: 'I' }, { name: 'J' }]\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/carousel2","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/carousel2/","markdown_url":"https://forge.totaljs.es/en/components/carousel2.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"7772a91eec1e9497a1b3db9410f9cd682e320ee23372357e9d693a796fe705a9","checked_at":"2026-10-20T12:20:01.758Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Carousel2","description":"Is a simple carousel component optimized for desktop computers and mobile devices. Each elemet must be wrapped into the <figure> element (look to example).","messages":[{"role":"user","content":"j-Carousel2\n\nIs a simple carousel component optimized for desktop computers and mobile devices. Each elemet must be wrapped into the <figure> element (look to example)."},{"role":"assistant","content":"Is a simple carousel component optimized for desktop computers and mobile devices. Each elemet must be wrapped into the `<figure>` element (look to example).\n\n\n__Configuration__:\n\n- `selector {String}` a selector for cell element (default: `figure`)\n- `margin {Number}` left margin between items (default: `10`)\n- `animate {Number}` animation timeout, `0` disables animation (default: `5000`)\n- `delay {Number}` A delay between animation (default: `2000`)\n- `parent {String}` optional, a selector for obtaining of height\n- `marginheight {Number}` optional, a margin for `height` (default: `0`)\n- `scrolldivider {Number}` optional, if the `count` isn't declared then the __element width__ will be divided according to this value, and the value will be used for `scrollLeft` (default: `3`)\n- `offsetwidth {Number}` optional, increases the content width (default: `0`)\n- `duration {Number}` optional, animation duration between scrolling items (default: `300`)\n- `durationsnap {Number}` optional, duration for snap animation (default: `200`)\n- `count {Number}` count of visible items (default: `1`)\n- __NEW__: `countxs {Number}` optional\n- __NEW__: `countsm {Number}` optional\n- __NEW__: `countmd {Number}` optional\n- __NEW__: `countlg {Number}` optional\n\n__Methods__:\n\n- `component.move('next|right/prev|left')` scrolls the content right or left\n- `component.focus('data-id value')` scrolls to the specific figure with `data-id` attribute\n\n__Good to know__:\n\nIf the component's path is changed then the component summarizes all cells again.\n\nUse it with `<ui-component name=\"carousel2\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\t.bg { background-color: #F0F0F0; padding: 15px; border-radius: 15px; }\n</style>\n\n<ui-component name=\"carousel2\" config=\"count:3;margin:10\" class=\"invisible\">\n\t<figure>\n\t\t<div class=\"bg\">\n\t\t\t<h2>Title 1</h2>\n\t\t\t<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis, dolores.</p>\n\t\t</div>\n\t</figure>\n\t<figure>\n\t\t<div class=\"bg\">\n\t\t\t<h2>Title 2</h2>\n\t\t\t<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis, dolores.</p>\n\t\t</div>\n\t</figure>\n\t<figure>\n\t\t<div class=\"bg\">\n\t\t\t<h2>Title 3</h2>\n\t\t\t<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis, dolores.</p>\n\t\t</div>\n\t</figure>\n\t<figure>\n\t\t<div class=\"bg\">\n\t\t\t<h2>Title 4</h2>\n\t\t\t<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis, dolores.</p>\n\t\t</div>\n\t</figure>\n\t<figure>\n\t\t<div class=\"bg\">\n\t\t\t<h2>Title 5</h2>\n\t\t\t<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis, dolores.</p>\n\t\t</div>\n\t</figure>\n\t<figure>\n\t\t<div class=\"bg\">\n\t\t\t<h2>Title 6</h2>\n\t\t\t<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis, dolores.</p>\n\t\t</div>\n\t</figure>\n\t<figure>\n\t\t<div class=\"bg\">\n\t\t\t<h2>Title 7</h2>\n\t\t\t<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis, dolores.</p>\n\t\t</div>\n\t</figure>\n</ui-component>\n```"}],"knowledge":{"content_sha256":"e640ec637380217158b019a93f382ea446c13a588ceab66302c581cad8dbd7d0","source_sha256":"7772a91eec1e9497a1b3db9410f9cd682e320ee23372357e9d693a796fe705a9","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","F0F0F0","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\t.bg { background-color: #F0F0F0; padding: 15px; border-radius: 15px; }\n</style>\n\n<ui-component name=\"carousel2\" config=\"count:3;margin:10\" class=\"invisible\">\n\t<figure>\n\t\t<div class=\"bg\">\n\t\t\t<h2>Title 1</h2>\n\t\t\t<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis, dolores.</p>\n\t\t</div>\n\t</figure>\n\t<figure>\n\t\t<div class=\"bg\">\n\t\t\t<h2>Title 2</h2>\n\t\t\t<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis, dolores.</p>\n\t\t</div>\n\t</figure>\n\t<figure>\n\t\t<div class=\"bg\">\n\t\t\t<h2>Title 3</h2>\n\t\t\t<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis, dolores.</p>\n\t\t</div>\n\t</figure>\n\t<figure>\n\t\t<div class=\"bg\">\n\t\t\t<h2>Title 4</h2>\n\t\t\t<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis, dolores.</p>\n\t\t</div>\n\t</figure>\n\t<figure>\n\t\t<div class=\"bg\">\n\t\t\t<h2>Title 5</h2>\n\t\t\t<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis, dolores.</p>\n\t\t</div>\n\t</figure>\n\t<figure>\n\t\t<div class=\"bg\">\n\t\t\t<h2>Title 6</h2>\n\t\t\t<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis, dolores.</p>\n\t\t</div>\n\t</figure>\n\t<figure>\n\t\t<div class=\"bg\">\n\t\t\t<h2>Title 7</h2>\n\t\t\t<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis, dolores.</p>\n\t\t</div>\n\t</figure>\n</ui-component>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/centered","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/centered/","markdown_url":"https://forge.totaljs.es/en/components/centered.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"61ca5ac1891c95a768bd8d030dd33e7a6b4606f9202bfb0c2344d806548a696e","checked_at":"2026-10-20T12:20:02.552Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Centered","description":"New version of this component supports dynamic evaluation of the content of <script type='text/html'>. The example below contains a script with HTML and the component evaluates the content if the j-Fo","messages":[{"role":"user","content":"j-Centered\n\nNew version of this component supports dynamic evaluation of the content of <script type='text/html'>. The example below contains a script with HTML and the component evaluates the content if the j-Fo"},{"role":"assistant","content":"- The component moves the content of this component under `<body>` tag (because of positioning)\n\n\n__Configuration__:\n\n- `if {String}` condition for showing of the form, it's compared with the value within of `data-jc-path`\n- `reload {String}` link to a global `function(form_component)` and it's executed if the form is displaying\n- `autofocus {Boolean/String}` can focus an input. `String` === `jQuery selector` for the input\n- `default {String}` a short alias for `DEFAULT(default, true)`\n- `zindex {Number}` can affect z-index (default: `12`)\n- `closebutton {Boolean}` can hide `x` button (default: `false`)\n- `closeoutside {Boolean}` closes the form when the user clicks outside of the form (default: `false`)\n- or `closeoutside {String}` can contain a jQuery selector which will be included to a close event\n- `closeesc {Boolean}` closes the form when the user presses `ESC`\n- `scrollbar {Boolean}` enables custom scrollbar (default: `true`)\n- `visibleY {Boolean}` shows vertical scrollbar (default: `false`)\n- `close {String}` link to a global `function()` and it's executed if the form is closing\n\n__Good to know__:\n\nNew version of this component supports dynamic evaluation of the content of `<script type=\"text/html\">`. The example below contains a script with HTML and the component evaluates the content if the j-Form will be displayed (only once).\n\n```html\n<ui-component name=\"centered\" path=\"path\" config=\"config\">\n\t<script type=\"text/html\">\n\t\tA CONTENT\n\t</script>\n</ui-component>\n```\n\n- all buttons with `name=\"close\"` attribute closes the component automatically\n- all elements with `data-name=\"close\"` attribute closes the component automatically\n\nUse it with `<ui-component name=\"centered\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sed, nam distinctio numquam dolore neque impedit, voluptate quam fugiat culpa minus.</div>\n<br />\n<a href=\"javascript:show()\">Show</a>\n\n<ui-component name=\"centered\" path=\"form\" config=\"if:login\" class=\"hidden\">\n\t<figure style=\"width:300px;margin:0 auto;background-color:rgba(100,100,100,0.1);display:block;padding:20px\">\n\t\t<div><img src=\"https://www.totaljs.com/img/logo.svg\" border=\"0\" alt=\"Componentator\" class=\"img-responsive\" /></div>\n\t\t<br />\n\t\t<div class=\"m\">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Error nulla ratione possimus ducimus iusto porro, non laboriosam sunt reiciendis at.</div>\n\t\t<div class=\"center\">\n\t\t\t<button name=\"close\">CLOSE</button>\n\t\t</div>\n\t</figure>\n</ui-component>\n\n<script>\n\tvar form = '';\n\tfunction show() {\n\t\tSET('form', 'login');\n\t}\n</script>\n```"}],"knowledge":{"content_sha256":"14ac41f4a09f1ee183bcd1ac8ce4c0d0106c93a19e16013ba91cbd146b29f0ff","source_sha256":"61ca5ac1891c95a768bd8d030dd33e7a6b4606f9202bfb0c2344d806548a696e","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","CLOSE","CONTENT","DEF","DEFAULT","ESC","HTML","MIT","SET"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sed, nam distinctio numquam dolore neque impedit, voluptate quam fugiat culpa minus.</div>\n<br />\n<a href=\"javascript:show()\">Show</a>\n\n<ui-component name=\"centered\" path=\"form\" config=\"if:login\" class=\"hidden\">\n\t<figure style=\"width:300px;margin:0 auto;background-color:rgba(100,100,100,0.1);display:block;padding:20px\">\n\t\t<div><img src=\"https://www.totaljs.com/img/logo.svg\" border=\"0\" alt=\"Componentator\" class=\"img-responsive\" /></div>\n\t\t<br />\n\t\t<div class=\"m\">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Error nulla ratione possimus ducimus iusto porro, non laboriosam sunt reiciendis at.</div>\n\t\t<div class=\"center\">\n\t\t\t<button name=\"close\">CLOSE</button>\n\t\t</div>\n\t</figure>\n</ui-component>\n\n<script>\n\tvar form = '';\n\tfunction show() {\n\t\tSET('form', 'login');\n\t}\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/checkbox","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/checkbox/","markdown_url":"https://forge.totaljs.es/en/components/checkbox.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"130fccdea08bce2ff59bffac9cd3e6a95fe9f5a6fae63c973114d69e3d841da8","checked_at":"2026-10-20T12:20:03.437Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Checkbox","description":"j-Checkbox options: required, label, disabled, checkicon, reverse","messages":[{"role":"user","content":"j-Checkbox\n\nj-Checkbox options: required, label, disabled, checkicon, reverse"},{"role":"assistant","content":"__Configuration__:\n\n- `required` {Boolean} optional, enables \"required\" (default: `false`)\n- `label` {String} optional, label (default is HTML content)\n- `disabled` {Boolean} optional, disables this component\n- `checkicon` {String} optional, a check icon without `fa-` (default: `check`)\n- `reverse` {Boolean} optional, enables reverse behaviour (default: `false`)\n\nUse it with `<ui-component name=\"checkbox\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"checkbox\" path=\"form.newsletter\">I want to receive a newsletter</ui-component>\n<ui-component name=\"checkbox\" path=\"form.terms\">I agree with terms and conditions</ui-component>\n\n<script>\n\tvar form = {};\n\tform.terms = false;\n\tform.newsletter = true;\n</script>\n```"}],"knowledge":{"content_sha256":"4f41c404c48d787b0546211c2343ec82152968f7ebb48820ef16aa8230b23271","source_sha256":"130fccdea08bce2ff59bffac9cd3e6a95fe9f5a6fae63c973114d69e3d841da8","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","HTML","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"checkbox\" path=\"form.newsletter\">I want to receive a newsletter</ui-component>\n<ui-component name=\"checkbox\" path=\"form.terms\">I agree with terms and conditions</ui-component>\n\n<script>\n\tvar form = {};\n\tform.terms = false;\n\tform.newsletter = true;\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/checkboxlist","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/checkboxlist/","markdown_url":"https://forge.totaljs.es/en/components/checkboxlist.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"f44cc6efbc9d0587c56ff77201260f6e6faafba060b0223af79fddf1823bfd7c","checked_at":"2026-10-20T12:20:04.316Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Checkboxlist","description":"j-Checkboxlist: for custom design you can use j-CheckboxListExpert; now supports darkmode","messages":[{"role":"user","content":"j-Checkboxlist\n\nj-Checkboxlist: for custom design you can use j-CheckboxListExpert; now supports darkmode"},{"role":"assistant","content":"- for custom design you can use __j-CheckboxListExpert__\n- now supports darkmode\n\n__Configuration__:\n\n- `required` {Boolean} (optional) enables \"required\" (default: `false`)\n- `items` {String} (optional) static items `key1|value1, key2 with value, key3|value`\n- `icon` {String} (optional) icon for label without `fa-` e.g. `home`, `cog`, etc.\n- `checkicon` {String} (optional) check icon without `fa-`, (default: `check`).\n- `label` {String} (optional) label (default is HTML content)\n- `type` {String} (optional) can be `number` (converts `String` values to `Number`)\n- `datasource` {String} path to data-source (must be array)\n- `text` {String} determines a property name for text (in data-source), default: `name`\n- `value` {String} determines a property name for value (in data-source) default :`id`\n- `empty` {String} (optional) empty text for empty data-source\n- `disabled` {Boolean} (optional) disables this component\n\nUse it with `<ui-component name=\"checkboxlist\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div class=\"container\">\n\t<div class=\"row\">\n\t\t<div class=\"col-md-8 col-md-offset-2\">\n\t\t\t<ui-component name=\"checkboxlist\" path=\"selected\" config=\"datasource:datasource;class:.col-sm-2;type:number\">Frameworks</ui-component>\n\t\t</div>\n\t</div>\n\t<br />\n\t<div class=\"row\">\n\t\t<div class=\"col-md-8 col-md-offset-2\">\n\t\t\t<ui-component name=\"checkboxlist\" path=\"selected\" config=\"icon:home;required:true;items:Total.js|1,jComponent|2,Express.js|3,React|4,Angular.js|5;class:.col-sm-2;type:number\">Frameworks</ui-component>\n\t\t</div>\n\t</div>\n</div>\n\n<script>\n\tvar selected = [1, 2];\n\tvar datasource = [{ id: 1, name: 'Total.js' }, { id: 2, name: 'jComponent' }, { id: 3, name: 'Express.js' }, { id: 4, name: 'React' }, { id: 5, name: 'Angular.js' }];\n</script>\n```"}],"knowledge":{"content_sha256":"3251fbc7735fe1228efee43f8329cb89a39e90d37d6f26a8217b3d81016408ce","source_sha256":"f44cc6efbc9d0587c56ff77201260f6e6faafba060b0223af79fddf1823bfd7c","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","HTML","MIT","Total.js"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div class=\"container\">\n\t<div class=\"row\">\n\t\t<div class=\"col-md-8 col-md-offset-2\">\n\t\t\t<ui-component name=\"checkboxlist\" path=\"selected\" config=\"datasource:datasource;class:.col-sm-2;type:number\">Frameworks</ui-component>\n\t\t</div>\n\t</div>\n\t<br />\n\t<div class=\"row\">\n\t\t<div class=\"col-md-8 col-md-offset-2\">\n\t\t\t<ui-component name=\"checkboxlist\" path=\"selected\" config=\"icon:home;required:true;items:Total.js|1,jComponent|2,Express.js|3,React|4,Angular.js|5;class:.col-sm-2;type:number\">Frameworks</ui-component>\n\t\t</div>\n\t</div>\n</div>\n\n<script>\n\tvar selected = [1, 2];\n\tvar datasource = [{ id: 1, name: 'Total.js' }, { id: 2, name: 'jComponent' }, { id: 3, name: 'Express.js' }, { id: 4, name: 'React' }, { id: 5, name: 'Angular.js' }];\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/checkboxlistexpert","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/checkboxlistexpert/","markdown_url":"https://forge.totaljs.es/en/components/checkboxlistexpert.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"176b7748196448a45713999dc1307a8eb6cd10153f9beb43d64d900874cc9c5e","checked_at":"2026-10-20T12:20:05.052Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-CheckboxListExpert","description":"Perfect component for checboxlist with custom styles. It's a little bit advanced but absolutely worth it!","messages":[{"role":"user","content":"j-CheckboxListExpert\n\nPerfect component for checboxlist with custom styles. It's a little bit advanced but absolutely worth it!"},{"role":"assistant","content":"Perfect component for checboxlist with custom styles. It's a little bit advanced but absolutely worth it!\n\n\n__Configuration__\n\n- `label {String}` can change the title label\n- `required {Boolean}` enables `required` (default: `false`)\n- `disabled {Boolean}` disables this control (default: `false`)\n- `type {String}` can contain `number` (default: `string`)\n- `class {String}` inserts a class into an element with class `.ui-checkboxlistexpert-container`\n- `datasource {String}` path to data-source (must be array or array of objects) or __NEW__: `id|name,id|name`\n- `value {String}` determines a property name for value (in data-source) (default: `id`)\n- `disabledkey {String}` determines a property name for disabled item (in data-source) (default: `disabled`)\n- __NEW__ `selector {String}` a jQuery selector for custom template (outside of the component). Works like `custom template` in [data-binding](https://wiki.totaljs.com/jcomponent/08-data-binding/)\n\n__Good to know__\n\n- You must write **all the styles**\n- Example object `{ name: 'My item', id: 1, disabled: false, classname: 'color-highlighted' }`\n- You can use `$index` and `$path` in template\n\n__Basic Styles__\n\n```css\n.my-button { display: inline-block; padding: 20px 40px; margin-right: 10px; border: 1px solid gray; cursor: pointer; border-radius: 2px; position: relative; user-select: none; }\n.my-button .checkbox { position: absolute; top: 10px; left: 10px; }\n.my-button.selected .checkbox i:before { content: \"\\f14a\"; font-weight: 900; }\n.my-button:hover { border-color: #4285F4; }\n.my-button.selected { color: white; background: #4285F4; border-color: rgba(0, 0, 0, 0.2); }\n.my-button[data-disabled=\"1\"] { background-color: #dcdcdc; color: gray; border-color: #dcdcdc; cursor: not-allowed; }\n.ui-disabled .my-button .checkbox { color: #c3c3c3; }\n.ui-disabled .my-button { background-color: #dcdcdc; color: gray; border-color: #dcdcdc; cursor: not-allowed; }\n.ui-disabled .my-button:hover { background-color: #dcdcdc; color: gray; border-color: #dcdcdc; }\n.ui-disabled .my-button.selected { border-color: gray; background: #efefef; color: #5a5a5a; }\n\n/* Dark mode */\n.ui-dark .my-button { border-color: 1px solid #404040; color: #adadad; }\n.ui-dark .my-button.selected { color: white; }\n.ui-dark .my-button[data-disabled=\"1\"] { background-color: #525252; color: gray; border-color: #525252; }\n.ui-dark .ui-disabled .my-button { background-color: #525252; color: gray; border-color: #525252; }\n.ui-dark .ui-disabled .my-button:hover { background-color: #525252; color: gray; border-color: #525252; }\n.ui-dark .ui-disabled .my-button.selected { border-color: rgba(0, 0, 0, 0.2); background: #969696; color: #ffffff; }\n\n/* Large mode */\n.ui-large .my-button { font-size: 16px; }\n```\n\nUse it with `<ui-component name=\"checkboxlistexpert\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Denis Granec. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\t.my-button { display: inline-block; padding: 20px 40px; margin-right: 10px; margin-bottom: 10px; border: 1px solid gray; cursor: pointer; border-radius: 2px; position: relative; user-select: none; }\n\t.my-button .checkbox { position: absolute; top: 10px; left: 10px; }\n\t.my-button.selected .checkbox i:before { content: \"\\f14a\"; font-weight: 900; }\n\t.my-button:hover { border-color: #4285F4; }\n\t.my-button.selected { color: white; background: #4285F4; border-color: rgba(0, 0, 0, 0.2); }\n\t.my-button[data-disabled=\"1\"] { background-color: #dcdcdc; color: gray; border-color: #dcdcdc; cursor: not-allowed; }\n\t.ui-disabled .my-button .checkbox { color: #c3c3c3; }\n\t.ui-disabled .my-button { background-color: #dcdcdc; color: gray; border-color: #dcdcdc; cursor: not-allowed; }\n\t.ui-disabled .my-button:hover { background-color: #dcdcdc; color: gray; border-color: #dcdcdc; }\n\t.ui-disabled .my-button.selected { border-color: gray; background: #efefef; color: #5a5a5a; }\n\n\t.ui-dark .my-button { border-color: 1px solid #404040; color: #adadad; }\n\t.ui-dark .my-button.selected { color: white; }\n\t.ui-dark .my-button[data-disabled=\"1\"] { background-color: #525252; color: gray; border-color: #525252; }\n\t.ui-dark .ui-disabled .my-button { background-color: #525252; color: gray; border-color: #525252; }\n\t.ui-dark .ui-disabled .my-button:hover { background-color: #525252; color: gray; border-color: #525252; }\n\t.ui-dark .ui-disabled .my-button.selected { border-color: rgba(0, 0, 0, 0.2); background: #969696; color: #ffffff; }\n\t.ui-large .my-button { font-size: 16px; }\n</style>\n\n<ui-component name=\"checkboxlistexpert\" path=\"model.framework\" config=\"datasource:list;disabled:1;required:true;label:Your favorite frameworks;type:number\" default=\"2\">\n\t<script type=\"text/html\">\n\t\t<div class=\"my-button\">\n\t\t\t<div>{{ name }}</div>\n\t\t</div>\n\t</script>\n</ui-component>\n<hr>\n<ui-component name=\"checkboxlistexpert\" path=\"model.framework2\" config=\"selector:#mybutton;datasource:list;required:true;label:Your favorite frameworks;type:number\" default=\"0\"></ui-component>\n<script type=\"text/html\" id=\"mybutton\">\n\t<div class=\"my-button\">\n\t\t<div class=\"checkbox\"><i class=\"ti ti-square\"></i></div>\n\t\t<div>{{ name }}</div>\n\t</div>\n</script>\n\n<script>\n\tvar list = [{ name: 'Total.js', id: 1 }, { name: 'express', id: 2 }, { name: 'Hapi', id: 3, disabled: false }, { name: 'Koa', id: 4, disabled: true }, { name: 'Sails', id: 5 }, { name: 'Meteor', id: 6 }, { name: 'Adonis', id: 7 }, { name: 'Nest', id: 8 }];\n</script>\n```"}],"knowledge":{"content_sha256":"b79ffb404b9273a732e49891ac3a7b224bf6b58d101fd1581d1502167978439a","source_sha256":"176b7748196448a45713999dc1307a8eb6cd10153f9beb43d64d900874cc9c5e","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","Total.js"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\t.my-button { display: inline-block; padding: 20px 40px; margin-right: 10px; margin-bottom: 10px; border: 1px solid gray; cursor: pointer; border-radius: 2px; position: relative; user-select: none; }\n\t.my-button .checkbox { position: absolute; top: 10px; left: 10px; }\n\t.my-button.selected .checkbox i:before { content: \"\\f14a\"; font-weight: 900; }\n\t.my-button:hover { border-color: #4285F4; }\n\t.my-button.selected { color: white; background: #4285F4; border-color: rgba(0, 0, 0, 0.2); }\n\t.my-button[data-disabled=\"1\"] { background-color: #dcdcdc; color: gray; border-color: #dcdcdc; cursor: not-allowed; }\n\t.ui-disabled .my-button .checkbox { color: #c3c3c3; }\n\t.ui-disabled .my-button { background-color: #dcdcdc; color: gray; border-color: #dcdcdc; cursor: not-allowed; }\n\t.ui-disabled .my-button:hover { background-color: #dcdcdc; color: gray; border-color: #dcdcdc; }\n\t.ui-disabled .my-button.selected { border-color: gray; background: #efefef; color: #5a5a5a; }\n\n\t.ui-dark .my-button { border-color: 1px solid #404040; color: #adadad; }\n\t.ui-dark .my-button.selected { color: white; }\n\t.ui-dark .my-button[data-disabled=\"1\"] { background-color: #525252; color: gray; border-color: #525252; }\n\t.ui-dark .ui-disabled .my-button { background-color: #525252; color: gray; border-color: #525252; }\n\t.ui-dark .ui-disabled .my-button:hover { background-color: #525252; color: gray; border-color: #525252; }\n\t.ui-dark .ui-disabled .my-button.selected { border-color: rgba(0, 0, 0, 0.2); background: #969696; color: #ffffff; }\n\t.ui-large .my-button { font-size: 16px; }\n</style>\n\n<ui-component name=\"checkboxlistexpert\" path=\"model.framework\" config=\"datasource:list;disabled:1;required:true;label:Your favorite frameworks;type:number\" default=\"2\">\n\t<script type=\"text/html\">\n\t\t<div class=\"my-button\">\n\t\t\t<div>{{ name }}</div>\n\t\t</div>\n\t</script>\n</ui-component>\n<hr>\n<ui-component name=\"checkboxlistexpert\" path=\"model.framework2\" config=\"selector:#mybutton;datasource:list;required:true;label:Your favorite frameworks;type:number\" default=\"0\"></ui-component>\n<script type=\"text/html\" id=\"mybutton\">\n\t<div class=\"my-button\">\n\t\t<div class=\"checkbox\"><i class=\"ti ti-square\"></i></div>\n\t\t<div>{{ name }}</div>\n\t</div>\n</script>\n\n<script>\n\tvar list = [{ name: 'Total.js', id: 1 }, { name: 'express', id: 2 }, { name: 'Hapi', id: 3, disabled: false }, { name: 'Koa', id: 4, disabled: true }, { name: 'Sails', id: 5 }, { name: 'Meteor', id: 6 }, { name: 'Adonis', id: 7 }, { name: 'Nest', id: 8 }];\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/checkpath","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/checkpath/","markdown_url":"https://forge.totaljs.es/en/components/checkpath.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"a699c5a9fb1cd946e48deace3df38eb6736b617005f54c865645cd1eb1ca8d80","checked_at":"2026-10-20T12:20:05.807Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-CheckPath","description":"j-CheckPath: Singleton; Development tool","messages":[{"role":"user","content":"j-CheckPath\n\nj-CheckPath: Singleton; Development tool"},{"role":"assistant","content":"- Singleton\n- Development tool\n\n__Configuration__:\n\n- `position {Number}` - `1` top right, `2` bottom right, `3` bottom left (default), `4` top left\n\nUse it with `<ui-component name=\"checkpath\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Kirill Zlobin. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"checkpath\"></ui-component>\n\n<ui-plugin path=\"common\">\n\t<div class=\"padding\">\n\t\t<ui-component name=\"input\" path=\"?.title\" class=\"m\">Title</ui-component>\n\t\t<ui-bind path=\"?.title\" config=\"html;empty:---\" class=\"block\"></ui-bind>\n\t</div>\n</ui-plugin>\n\n<script>\n\n\tvar common = { title: 'Hello world!' };\n\n</script>\n```"}],"knowledge":{"content_sha256":"1709748fb1b02b8eed2b0947e57eac7cd256122f1bb456a20643a817e1143b48","source_sha256":"a699c5a9fb1cd946e48deace3df38eb6736b617005f54c865645cd1eb1ca8d80","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"checkpath\"></ui-component>\n\n<ui-plugin path=\"common\">\n\t<div class=\"padding\">\n\t\t<ui-component name=\"input\" path=\"?.title\" class=\"m\">Title</ui-component>\n\t\t<ui-bind path=\"?.title\" config=\"html;empty:---\" class=\"block\"></ui-bind>\n\t</div>\n</ui-plugin>\n\n<script>\n\n\tvar common = { title: 'Hello world!' };\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/children","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/children/","markdown_url":"https://forge.totaljs.es/en/components/children.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"735bb63da576367083817a7d51db750b2d7277479e0ad9cbe01d602a015ac2b5","checked_at":"2026-10-20T12:20:06.693Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Children","description":"The component renders draggable items as a tree without the possibility of collapsing. Data-Source must be Object Array in the form: { id: String, children: [ ], YOUR PROPERTIES }, { ... }","messages":[{"role":"user","content":"j-Children\n\nThe component renders draggable items as a tree without the possibility of collapsing. Data-Source must be Object Array in the form: { id: String, children: [ ], YOUR PROPERTIES }, { ... }"},{"role":"assistant","content":"The component renders draggable items as a tree without the possibility of collapsing. Data-Source must be `Object Array` in the form: `{ id: String, children: [ ], YOUR PROPERTIES }, { ... }`\n\n\n__Configuration__:\n\n- `empty {String}` an empty text (default: `null`)\n- `onchange {String}` optional, a link to the `function(arr)` - captures items when resorted\n\n__Template__:\n\n```html\n{{ level }} contains a level (number)\n{{ value }} contains an item (object)\n```\n\nTry to understand the functionality from the example.\n\n__Good to know__:\n\n- `component.items {Object}` contains all references of all items in the form `items[item.id] = item`\n- `component.parents {Object}` contains all references of all parents in the form `parents[item.id] = parent`\n\nUse it with `<ui-component name=\"children\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"children\" path=\"mynav\">\n\t<script type=\"text/html\">\n\t\t<div style=\"background:#F0F0F0;padding:8px;border-radius:var(--radius);margin-bottom:2px;margin-left:{{ level * 30 }}px\">{{ value.name }}</div>\n\t</script>\n</ui-component>\n\n<script>\n\tvar mynav = [{ id: '1', name: 'Level 1', children: [{ id: '1-1', name: 'Level 1-1' }, { id: '1-2', name: 'Level 1-2', children: [{ id: '1-2-1', name: 'Level 1-2-1' }, { id: '1-2-2', name: 'Level 1-2-2' }, { id: '1-2-3', name: 'Level 1-2-3' }] }] }, { id: '2', name: 'Level 2' }];\n</script>\n```"}],"knowledge":{"content_sha256":"f95274001185d41d2fe27982f66f2b59631854af969288b56960e98d13429e1b","source_sha256":"735bb63da576367083817a7d51db750b2d7277479e0ad9cbe01d602a015ac2b5","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","F0F0F0","MIT","PROPERTIES","YOUR"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"children\" path=\"mynav\">\n\t<script type=\"text/html\">\n\t\t<div style=\"background:#F0F0F0;padding:8px;border-radius:var(--radius);margin-bottom:2px;margin-left:{{ level * 30 }}px\">{{ value.name }}</div>\n\t</script>\n</ui-component>\n\n<script>\n\tvar mynav = [{ id: '1', name: 'Level 1', children: [{ id: '1-1', name: 'Level 1-1' }, { id: '1-2', name: 'Level 1-2', children: [{ id: '1-2-1', name: 'Level 1-2-1' }, { id: '1-2-2', name: 'Level 1-2-2' }, { id: '1-2-3', name: 'Level 1-2-3' }] }] }, { id: '2', name: 'Level 2' }];\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/choose","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/choose/","markdown_url":"https://forge.totaljs.es/en/components/choose.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"9f57f50c7a157c09811e74cf19b9d0a4c224d681ba9ae675b38049a87108323d","checked_at":"2026-10-20T12:20:07.432Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Choose","description":"The component is alternative to CheckboxListExpert and RadioButtonListExpert.","messages":[{"role":"user","content":"j-Choose\n\nThe component is alternative to CheckboxListExpert and RadioButtonListExpert."},{"role":"assistant","content":"The component is alternative to `CheckboxListExpert` and `RadioButtonListExpert`.\n\n\n__Configuration__:\n\n- `limit {Number}` selection limit, `1` will return a value, `>1` will return array of values (default: `1`)\n- `datasource {String}` a path to the datasource (default: empty)\n- `required {Boolean}` enables validation (default: `false`)\n- `key {String}` an `identifier` key name for the datasource (default: `id`)\n- `attr {String}` a `data-[attr]` for storing of `identifier` (default: `id`)\n- `selector {String}` a jQuery selector for elements (default: `.selection`)\n- `class {String}` selected class (default: `selected`)\n- `type {String}` converting type, supports `string` or `number` (default: `string`)\n- `uncheck {Boolean}` allows to uncheck selected value for `limit:1` (default: `false`)\n- `disabled {Boolean}` disables the component\n- `click {String}` a link to the `function(val)` that is evaluted when the user changes the value\n- __NEW__: `indexer {String}` a HTML selector for binding of the selected index\n\nUse it with `<ui-component name=\"choose\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\t.iconmenu { height: 60px; text-align: center; }\n\t.iconmenu i { font-size: 20px; display: block; padding: 10px 0 0; }\n\t.iconmenu span { font-size: 12px; display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 3px 5px 0; color: #777; }\n\t.iconmenu > div.selected { background-color: rgba(0,0,0,0.07); }\n\t.iconmenu > div { height: 60px; width: 75px; border: 1px solid #E0E0E0; border-left-width: 0; float: left; cursor: pointer; }\n\t.iconmenu > div:first-child { border-left-width: 1px; border-top-left-radius: var(--radius); border-bottom-left-radius: var(--radius); }\n\t.iconmenu > div:last-child { border-top-right-radius: var(--radius); border-bottom-right-radius: var(--radius); }\n</style>\n\n<ui-component name=\"choose\" path=\"myselectionsingle\" config=\"selector:div;limit:1\" class=\"iconmenu m\">\n\t<div data-id=\"male\">\n\t\t<i class=\"ti ti-male\"></i>\n\t\t<span>Male</span>\n\t</div>\n\t<div data-id=\"female\">\n\t\t<i class=\"ti ti-female\"></i>\n\t\t<span>Female</span>\n\t</div>\n</ui-component>\n\n<ui-component name=\"choose\" path=\"myselectionmultiple\" config=\"selector:div;limit:2;indexer:b\" class=\"iconmenu m\">\n\t<div data-id=\"male\">\n\t\t<i class=\"ti ti-male\"></i>\n\t\t<span>Male</span><b></b>\n\t</div>\n\t<div data-id=\"female\">\n\t\t<i class=\"ti ti-female\"></i>\n\t\t<span>Female</span><b></b>\n\t</div>\n\t<div data-id=\"unknown\">\n\t\t<i class=\"ti ti-question-circle\"></i>\n\t\t<span>Unknown</span><b></b>\n\t</div>\n</ui-component>\n\n<div>Output single: <code><ui-bind path=\"myselectionsingle\" config=\"text:JSON.stringify(value)\"></ui-bind></code></div>\n<div>Output multiple: <code><ui-bind path=\"myselectionmultiple\" config=\"text:JSON.stringify(value)\"></ui-bind></code></div>\n```"}],"knowledge":{"content_sha256":"d97d49258dd12977f00a1cfec05abc5d69f22550424aa28c6f280ed3d7cfd106","source_sha256":"9f57f50c7a157c09811e74cf19b9d0a4c224d681ba9ae675b38049a87108323d","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","E0E0E0","HTML","JSON","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\t.iconmenu { height: 60px; text-align: center; }\n\t.iconmenu i { font-size: 20px; display: block; padding: 10px 0 0; }\n\t.iconmenu span { font-size: 12px; display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; padding: 3px 5px 0; color: #777; }\n\t.iconmenu > div.selected { background-color: rgba(0,0,0,0.07); }\n\t.iconmenu > div { height: 60px; width: 75px; border: 1px solid #E0E0E0; border-left-width: 0; float: left; cursor: pointer; }\n\t.iconmenu > div:first-child { border-left-width: 1px; border-top-left-radius: var(--radius); border-bottom-left-radius: var(--radius); }\n\t.iconmenu > div:last-child { border-top-right-radius: var(--radius); border-bottom-right-radius: var(--radius); }\n</style>\n\n<ui-component name=\"choose\" path=\"myselectionsingle\" config=\"selector:div;limit:1\" class=\"iconmenu m\">\n\t<div data-id=\"male\">\n\t\t<i class=\"ti ti-male\"></i>\n\t\t<span>Male</span>\n\t</div>\n\t<div data-id=\"female\">\n\t\t<i class=\"ti ti-female\"></i>\n\t\t<span>Female</span>\n\t</div>\n</ui-component>\n\n<ui-component name=\"choose\" path=\"myselectionmultiple\" config=\"selector:div;limit:2;indexer:b\" class=\"iconmenu m\">\n\t<div data-id=\"male\">\n\t\t<i class=\"ti ti-male\"></i>\n\t\t<span>Male</span><b></b>\n\t</div>\n\t<div data-id=\"female\">\n\t\t<i class=\"ti ti-female\"></i>\n\t\t<span>Female</span><b></b>\n\t</div>\n\t<div data-id=\"unknown\">\n\t\t<i class=\"ti ti-question-circle\"></i>\n\t\t<span>Unknown</span><b></b>\n\t</div>\n</ui-component>\n\n<div>Output single: <code><ui-bind path=\"myselectionsingle\" config=\"text:JSON.stringify(value)\"></ui-bind></code></div>\n<div>Output multiple: <code><ui-bind path=\"myselectionmultiple\" config=\"text:JSON.stringify(value)\"></ui-bind></code></div>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/chunkyscroll","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/chunkyscroll/","markdown_url":"https://forge.totaljs.es/en/components/chunkyscroll.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"86f5477fc3787790a3f50e0fb304e08c56c359386d7084caecf045e92c39f057","checked_at":"2026-10-20T12:20:08.330Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-ChunkyScroll","description":"j-ChunkyScroll: can handle thousands items/elements","messages":[{"role":"user","content":"j-ChunkyScroll\n\nj-ChunkyScroll: can handle thousands items/elements"},{"role":"assistant","content":"- can handle thousands items/elements\n\n\n__Configuration__:\n\n- `rowheight {Number}` __Important__, a row height (default: `30` with auto obtaining of size)\n- `parent {String}` optional, a container with fixed height, can be `window`. Default value: `parent` element.\n- `margin {Number}` optional, a top/bottom margin together (default: `0`)\n- `scrolltop {Boolean}` scrolls to top automatically if the `path` is changed (default: `false`)\n- `marginxs {Number}` optional, a top/bottom margin together for `xs` screen width\n- `marginsm` optional, a top/bottom margin together for `sm` screen width\n- `marginmd` optional, a top/bottom margin together for `md` screen width\n- `marginlg` optional, a top/bottom margin together for `lg` screen width\n\nUse it with `<ui-component name=\"chunkyscroll\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div class=\"m\">Awesome! Awesome! Awesome! Here you can scroll <b>30000 records</b> without laggy scrolling:</div>\n<div style=\"width:500px;height:300px;border:1px solid #E0E0E0\">\n\t<ui-component name=\"chunkyscroll\" path=\"items\">\n\t\t<script type=\"text/html\">\n\t\t\t<div style=\"height:30px;border-top:1px solid #E0E0E0\">{{ index }}</div>\n\t\t</script>\n\t</ui-component>\n</div>\n<br />\n<div>Free and open-soruce alternative to <a href=\"https://github.com/NeXTs/Clusterize.js/\" target=\"_blank\">Clusterize.js</a></div>\n\n<script>\n\tvar items = [];\n\tfor (var i = 0; i < 30000; i++)\n\t\titems.push({ index: i + 1 });\n</script>\n```"}],"knowledge":{"content_sha256":"d89e270677139f7f35649bbc70fdcf15d30f942d67b7195f18d555c1310e37e1","source_sha256":"86f5477fc3787790a3f50e0fb304e08c56c359386d7084caecf045e92c39f057","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","E0E0E0","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div class=\"m\">Awesome! Awesome! Awesome! Here you can scroll <b>30000 records</b> without laggy scrolling:</div>\n<div style=\"width:500px;height:300px;border:1px solid #E0E0E0\">\n\t<ui-component name=\"chunkyscroll\" path=\"items\">\n\t\t<script type=\"text/html\">\n\t\t\t<div style=\"height:30px;border-top:1px solid #E0E0E0\">{{ index }}</div>\n\t\t</script>\n\t</ui-component>\n</div>\n<br />\n<div>Free and open-soruce alternative to <a href=\"https://github.com/NeXTs/Clusterize.js/\" target=\"_blank\">Clusterize.js</a></div>\n\n<script>\n\tvar items = [];\n\tfor (var i = 0; i < 30000; i++)\n\t\titems.push({ index: i + 1 });\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/cl","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/cl/","markdown_url":"https://forge.totaljs.es/en/components/cl.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"01d1e1825bd453eb6e58e0f1a12735d68070f11a87bdc3925c97f3da662745a2","checked_at":"2026-10-20T12:20:09.058Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Cl","description":"The component parses inline data defined in the component's element. Data is parsed to the code list format.","messages":[{"role":"user","content":"j-Cl\n\nThe component parses inline data defined in the component's element. Data is parsed to the code list format."},{"role":"assistant","content":"The component parses inline data defined in the component's element. Data is parsed to the code list format.\n\n- Data source format: `id1|name1|icon1,id2|name2|icon2,id3|name3|icon3`\n- Parsed to: `[{ id, name, icon }]`\n\n__Example__:\n\n```html\n<ui-component name=\"cl\" path=\"#mycodelist\" value=\"SK|Slovakia,CZ|Czechia,DE|Germany,FR|France\"></ui-component>\n```\n\nUse it with `<ui-component name=\"cl\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"cl\" path=\"#mycodelist\" value=\"SK|Slovakia,CZ|Czechia,DE|Germany,FR|France\"></ui-component>\n\n<ui-bind path=\"#mycodelist\" config=\"html pre:JSON.stringify(value, null, '  ')\" class=\"block\">\n\t<pre></pre>\n</ui-bind>\n```"}],"knowledge":{"content_sha256":"5f3e33d524cacf124a201cd7da66e960e8e5571c7620e7e472bde03498b6c846","source_sha256":"01d1e1825bd453eb6e58e0f1a12735d68070f11a87bdc3925c97f3da662745a2","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","JSON","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"cl\" path=\"#mycodelist\" value=\"SK|Slovakia,CZ|Czechia,DE|Germany,FR|France\"></ui-component>\n\n<ui-bind path=\"#mycodelist\" config=\"html pre:JSON.stringify(value, null, '  ')\" class=\"block\">\n\t<pre></pre>\n</ui-bind>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/clbind","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/clbind/","markdown_url":"https://forge.totaljs.es/en/components/clbind.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"39bf2b200ff1412110cbe18ba4c8b5b9ebf2c3931a7ee038022a6936caa7e80c","checked_at":"2026-10-20T12:20:09.946Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-ClBind","description":"The component reads an item with a linked data source. The component supports Tangular template inside the element (optional). As the value is stored in the {{ value.something }} variable.","messages":[{"role":"user","content":"j-ClBind\n\nThe component reads an item with a linked data source. The component supports Tangular template inside the element (optional). As the value is stored in the {{ value.something }} variable."},{"role":"assistant","content":"The component reads an item with a linked data source. The component supports Tangular template inside the element (optional). As the value is stored in the `{{ value.something }}` variable.\n\n\n__Configuration__:\n\n- `datasource {String}` or `dirsource {String}` __required__ a data-source\n- `id {String}` a pair key (default: `id`)\n- `nullable {Boolean}` renders `null` value (default: `true`)\n- `exec {String}` executes `function(value, element)` method\n- `bind {String}` binds a read value according to the defined path\n\nUse it with `<ui-component name=\"clbind\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"clbind\" path=\"form.typeid\" config=\"datasource:#types;nullable:0;bind:form.type\">\n\t<script type=\"text/html\">\n\t\t<div>Value: {{ value.name }}</div>\n\t</script>\n</ui-component>\n<br />\n<ui-bind path=\"!form.type\" config=\"html:value.name\"></ui-bind>\n\n<script>\n\tDEF.cl.types = [{ id: 'totaljs', name: 'Total.js' }, { id: 'java', name: 'Java' }, { id: 'php', name: 'PHP' }, { id: '.net', name: '.NET' }];\n\tvar form = {};\n\tform.typeid = 'totaljs';\n</script>\n```"}],"knowledge":{"content_sha256":"33e141ae2d66fc7e95b8a002d044972fd987ef16dc51d88b7c60be89b049b955","source_sha256":"39bf2b200ff1412110cbe18ba4c8b5b9ebf2c3931a7ee038022a6936caa7e80c","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","NET","PHP","Total.js"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"clbind\" path=\"form.typeid\" config=\"datasource:#types;nullable:0;bind:form.type\">\n\t<script type=\"text/html\">\n\t\t<div>Value: {{ value.name }}</div>\n\t</script>\n</ui-component>\n<br />\n<ui-bind path=\"!form.type\" config=\"html:value.name\"></ui-bind>\n\n<script>\n\tDEF.cl.types = [{ id: 'totaljs', name: 'Total.js' }, { id: 'java', name: 'Java' }, { id: 'php', name: 'PHP' }, { id: '.net', name: '.NET' }];\n\tvar form = {};\n\tform.typeid = 'totaljs';\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/click","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/click/","markdown_url":"https://forge.totaljs.es/en/components/click.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"dfd18c24c642a8b985cb307dfbac51397517fbb842ec8e5f253ecb3977634bd8","checked_at":"2026-10-20T12:20:10.681Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Click","description":"The component calls a method or can set a value according to the path.","messages":[{"role":"user","content":"j-Click\n\nThe component calls a method or can set a value according to the path."},{"role":"assistant","content":"The component calls a method or can set a value according to the path.\n\n__Configuration__:\n\n- `enter` {String} (optional) is a jQuery selector\n- `value` {String/Number/Boolean} (optional) this value will be set to `data-jc-path`\n- `disabled` {Boolean} disables this control\n\nUse it with `<ui-component name=\"click\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div id=\"form\">\n    <div><input type=\"text\" placeholder=\"press enter\" /></div>\n</div>\n<div><button><ui-component name=\"click\" path=\"doSubmit\" config=\"enter:#form\">SUBMIT</ui-component></button></div>\n<br />\n<div><button><ui-component name=\"click\" path=\"user.name\" config=\"value:Peter\">SET VALUE user.name</ui-component></button></div>\n<ui-bind path=\"user.name\" config=\"text\"></ui-bind>\n\n<script>\n\tfunction doSubmit() {\n        alert('SUBMIT');\n    }\n</script>\n```"}],"knowledge":{"content_sha256":"09cf2ecb4bf9b2d71805f552ef77dd46e5757ae9e76234a1bcb0f0740ec30849","source_sha256":"dfd18c24c642a8b985cb307dfbac51397517fbb842ec8e5f253ecb3977634bd8","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","SET","SUBMIT","VALUE"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div id=\"form\">\n    <div><input type=\"text\" placeholder=\"press enter\" /></div>\n</div>\n<div><button><ui-component name=\"click\" path=\"doSubmit\" config=\"enter:#form\">SUBMIT</ui-component></button></div>\n<br />\n<div><button><ui-component name=\"click\" path=\"user.name\" config=\"value:Peter\">SET VALUE user.name</ui-component></button></div>\n<ui-bind path=\"user.name\" config=\"text\"></ui-bind>\n\n<script>\n\tfunction doSubmit() {\n        alert('SUBMIT');\n    }\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/clickbox","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/clickbox/","markdown_url":"https://forge.totaljs.es/en/components/clickbox.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"5667e3b5c44e51cc0c1e3931ad4d967eeb2a492d654c859f87ac2ccd0226e527","checked_at":"2026-10-20T12:20:11.416Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-ClickBox","description":"This component is optimized for SEO. It reads all links and creates a clickable box. The component automatically highlight a value according to the URL param.","messages":[{"role":"user","content":"j-ClickBox\n\nThis component is optimized for SEO. It reads all links and creates a clickable box. The component automatically highlight a value according to the URL param."},{"role":"assistant","content":"This component is optimized for SEO. It reads all links and creates a clickable box. The component automatically highlight a value according to the URL param.\n\n\n__Configuration__:\n\n- `param {String}` name of URL param for selecting value\n\nUse it with `<ui-component name=\"clickbox\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"padding:50px;width:300px\">\n\t<ui-component name=\"clickbox\" config=\"param:manufacturer\">\n\t\t<a href=\"?manufacturer=\">All manufacturers</a>\n\t\t<a href=\"?manufacturer=apple\">Apple</a>\n\t\t<a href=\"?manufacturer=google\">Google</a>\n\t\t<a href=\"?manufacturer=microsoft\">Microsoft</a>\n\t\t<a href=\"?manufacturer=ibm\">IBM</a>\n\t\t<a href=\"?manufacturer=dell\">Dell</a>\n\t\t<a href=\"?manufacturer=toshiba\">Toshiba</a>\n\t</ui-component>\n</div>\n```"}],"knowledge":{"content_sha256":"0bfb6b09f523cbf8b1d3ac2ea2ec2f1328be96a0782120e83ecd6f899a85a187","source_sha256":"5667e3b5c44e51cc0c1e3931ad4d967eeb2a492d654c859f87ac2ccd0226e527","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","IBM","MIT","SEO","URL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"padding:50px;width:300px\">\n\t<ui-component name=\"clickbox\" config=\"param:manufacturer\">\n\t\t<a href=\"?manufacturer=\">All manufacturers</a>\n\t\t<a href=\"?manufacturer=apple\">Apple</a>\n\t\t<a href=\"?manufacturer=google\">Google</a>\n\t\t<a href=\"?manufacturer=microsoft\">Microsoft</a>\n\t\t<a href=\"?manufacturer=ibm\">IBM</a>\n\t\t<a href=\"?manufacturer=dell\">Dell</a>\n\t\t<a href=\"?manufacturer=toshiba\">Toshiba</a>\n\t</ui-component>\n</div>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/clipboard","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/clipboard/","markdown_url":"https://forge.totaljs.es/en/components/clipboard.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"2b07dbb28cd66cfb7ea9a486911f61e45b692428822c61e4ea4f4d3c35a5ad0a","checked_at":"2026-10-20T12:20:12.141Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Clipboard","description":"j-Clipboard: It works as singleton; Uses navigator.clipboard.writeText() or document.execCommand('copy') as fallback","messages":[{"role":"user","content":"j-Clipboard\n\nj-Clipboard: It works as singleton; Uses navigator.clipboard.writeText() or document.execCommand('copy') as fallback"},{"role":"assistant","content":"- It works as singleton\n- Uses `navigator.clipboard.writeText()` or `document.execCommand('copy')` as fallback\n\n\n__Methods__:\n\n- `SETTER('clipboard/copy', 'TEXT TO CLIPBOARD')` or `FIND('clipboard').copy('TEXT TO CLIPBOARD')`\n\n__Configuration__:\n\n- `oncopy {String}` a link to the `function(value)`\n- `native {Boolean}` enables native clipboard (default: `false`)\n\nUse it with `<ui-component name=\"clipboard\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"clipboard\"></ui-component>\n<div style=\"cursor:pointer\">Copy to clipboard: My value to clipboard</div>\n<div style=\"cursor:pointer\">Copy to clipboard: Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusantium, voluptatem?</div>\n\n<script>\n\t$('div').on('click', function() {\n\t\tvar el = $(this);\n\n\t\tel.css('background-color', '#E0E0E0');\n\t\tsetTimeout(function() {\n\t\t\tel.css('background-color', 'transparent');\n\t\t}, 200);\n\n\t\t// Copy to clipboard\n\t\tSETTER('clipboard/copy', el.html());\n\t});\n</script>\n```"}],"knowledge":{"content_sha256":"82080a68b47aa642d392c7a681fd8845a68edede928d9bce5ff17e578d9cb2bc","source_sha256":"2b07dbb28cd66cfb7ea9a486911f61e45b692428822c61e4ea4f4d3c35a5ad0a","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","CLIPBOARD","DEF","E0E0E0","FIND","MIT","SETTER","TEXT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"clipboard\"></ui-component>\n<div style=\"cursor:pointer\">Copy to clipboard: My value to clipboard</div>\n<div style=\"cursor:pointer\">Copy to clipboard: Lorem ipsum dolor sit amet, consectetur adipisicing elit. Accusantium, voluptatem?</div>\n\n<script>\n\t$('div').on('click', function() {\n\t\tvar el = $(this);\n\n\t\tel.css('background-color', '#E0E0E0');\n\t\tsetTimeout(function() {\n\t\t\tel.css('background-color', 'transparent');\n\t\t}, 200);\n\n\t\t// Copy to clipboard\n\t\tSETTER('clipboard/copy', el.html());\n\t});\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/clipboardimage","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/clipboardimage/","markdown_url":"https://forge.totaljs.es/en/components/clipboardimage.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"0ebafab6f45a80f6704dfda4c593904f12b604412a76a6971cf668b52df17759","checked_at":"2026-10-20T12:20:13.028Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-ClipboardImage","description":"This component automaticaly captures images in Clipboard.","messages":[{"role":"user","content":"j-ClipboardImage\n\nThis component automaticaly captures images in Clipboard."},{"role":"assistant","content":"This component automaticaly captures images in Clipboard.\n\n\n__Configuration__:\n\n- `quality {Number}` JPEG quality, (default: `90`)\n- `maxwidth {Number}` a maximum width (default: `1024`)\n- `maxheight {Number}` a maximum height (default: `768`)\n- `exec {String}` a link to execute method if the image is obtained\n- `disabled {Boolean}` can disable this component\n- `type {String}` output type, supports `png` or `jpg` (default)\n- __NEW__ `output {String}` output type `base64` (default) or `file`\n\n__How to capture data?__\n\n```javascript\n// Attach event\nON('clipboardimage', function(base64) {\n\t// We have to send data on the server in Base64 format\n\tAJAX('POST /upload/', { file: base64 }, function(response) {\n\t});\n});\n```\n\nUse it with `<ui-component name=\"clipboardimage\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"clipboardimage\" config=\"quality:90\"></ui-component>\n<div>Create a screenshot into the clipboard and press shortcuts CMD+V or CTRL+V into the iFrame (iFrame has to be focused).</div>\n<br />\n<div id=\"output\"></div>\n\n<script>\n\tON('clipboardimage', function(base64) {\n\t\tvar img = new Image();\n\t\timg.src = base64;\n\t\t$('#output').empty().append(img);\n\t});\n</script>\n```"}],"knowledge":{"content_sha256":"484e3e0c8f4322826fe035c6fd609550366388ced0b5c94ae4c0e14465b38200","source_sha256":"0ebafab6f45a80f6704dfda4c593904f12b604412a76a6971cf668b52df17759","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["AJAX","CDN","CMD","CTRL","DEF","JPEG","MIT","POST"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"clipboardimage\" config=\"quality:90\"></ui-component>\n<div>Create a screenshot into the clipboard and press shortcuts CMD+V or CTRL+V into the iFrame (iFrame has to be focused).</div>\n<br />\n<div id=\"output\"></div>\n\n<script>\n\tON('clipboardimage', function(base64) {\n\t\tvar img = new Image();\n\t\timg.src = base64;\n\t\t$('#output').empty().append(img);\n\t});\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/clock","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/clock/","markdown_url":"https://forge.totaljs.es/en/components/clock.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"d6e56737befd0d706bd0f6a0bdb567f16190ad2d7588ce6c00aa25a693a68aa5","checked_at":"2026-10-20T12:20:13.917Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Clock","description":"j-Clock: supports dark mode; works only with Bootstrap Grid System (otherwise is need to fix CSS)","messages":[{"role":"user","content":"j-Clock\n\nj-Clock: supports dark mode; works only with Bootstrap Grid System (otherwise is need to fix CSS)"},{"role":"assistant","content":"- supports __dark mode__\n- works __only__ with Bootstrap Grid System (otherwise is need to fix CSS)\n- thanks [weareoutman](https://github.com/weareoutman/) for some things\n\n__Configuration__:\n\n- `twelvehour` {Boolean} (optional) the clock will be 12-hour (default `false`).\n\n## Usage\n\n```javascript\nvar ELEMENT = $(document);\nvar PATH = 'some.path.to.date';\nvar X_OFFSET = 100;\n\nSETTER('clock', 'toggle', ELEMENT, PATH, function(date) {\n\tconsole.log('SELECTED DATE:', date);\n}, X_OFFSET);\n```\n\nUse it with `<ui-component name=\"clock\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Denis Granec. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<a href=\"#clock\">SHOW CLOCK</a>\n<ui-component name=\"clock\"></ui-component>\n\n<script>\n\tvar form = {};\n\tform.date = new Date();\n\n\t$('a[href=\"#clock\"]').on('click', function(e) {\n\t\tSETTER('clock', 'toggle', $(this), form.date, function(date) {\n\t\t\talert(date);\n\t\t});\n\t});\n</script>\n```"}],"knowledge":{"content_sha256":"7ff4602d1f067cc7944921c979b37dd2d36bf9647f61a86122b3ac1b43e4c87f","source_sha256":"d6e56737befd0d706bd0f6a0bdb567f16190ad2d7588ce6c00aa25a693a68aa5","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","CLOCK","CSS","DATE","DEF","ELEMENT","MIT","PATH","SELECTED","SETTER","SHOW","X_OFFSET"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<a href=\"#clock\">SHOW CLOCK</a>\n<ui-component name=\"clock\"></ui-component>\n\n<script>\n\tvar form = {};\n\tform.date = new Date();\n\n\t$('a[href=\"#clock\"]').on('click', function(e) {\n\t\tSETTER('clock', 'toggle', $(this), form.date, function(date) {\n\t\t\talert(date);\n\t\t});\n\t});\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/cloudeditor","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/cloudeditor/","markdown_url":"https://forge.totaljs.es/en/components/cloudeditor.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"27956c0142a16f324bba70042fcb3d16d5b8defcf8fa52f2d81427e0f64382a6","checked_at":"2026-10-20T12:20:14.656Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-CloudEditor","description":"j-CloudEditor: it works in the iframe; supports multiple cursors (shift + cmd + arrow-down/up)","messages":[{"role":"user","content":"j-CloudEditor\n\nj-CloudEditor: it works in the iframe; supports multiple cursors (shift + cmd + arrow-down/up)"},{"role":"assistant","content":"- it works in the iframe\n- supports multiple cursors (`shift + cmd + arrow-down/up`)\n- supports finding selected text (`cmd + d`)\n- supports custom scrollbars\n- supports diff tracker\n- supports a simple emmet plugin\n- supports real-time collaboration\n- source-code: <https://github.com/totaljs/editor>\n- it uses editor: [CodeMirror](https://codemirror.net/)\n- very small code\n\n__Configuration__:\n\n- `parent {String}` a selector for the main container (default: `auto`)\n- `autosave {Boolean}` auto rebinds value from the editor to the component's path (default: `true`)\n- `event {String}` a path to the `function(name, value)`\n\t- `click`\n\t- `errors`\n\t- `shortcut`\n\t- `cursor` contains cursor position\n\t- `change` contains partial changes for real-time collaboration (`config.realtime` must be `true`)\n- `contextmenu {String}` a path to the `function(opt)`\n- `realtime {Boolean}` enables realtime sending changes to the `event` method (event name is `change`)\n- `keywords {String}` custom keywords in the form e.g. `ID,PATH`\n- `minheight {Number}` optional, a minimal height (default: `0`)\n- `margin {Number}` optional, a vertical margin (default: `0`)\n- __NEW__: `url {String}` optional, a link to the editor (default: `//cdn.componentator.com/editor/1.min.html`)\n\n__Method__:\n\n- `component.insert('some text')`\n- `component.replace('text', 'newvalue')`\n- `component.search('text', callback(output))`\n- `component.select(from, [to])`\n- `component.goto(pos)`\n- `component.focus()`\n- `component.save(callback(value))`\n- `component.marker(name, [from], [to], [color])` adds + modifies (with `from`) and removes (without `from` argument) marker\n- `component.change(changes)` for the real-time collaboration (`changes` can be generated in the `config.event` function)\n- `component.clear()` clears all changes\n- `component.replaceselections(selections)`\n- `component.replaceselection(selection)`\n- `component.replacerange(value, from, [to])`\n- `component.command(cmd)` executes `codemirror.execCommand()` method\n- `component.exec(a, [b], [c])` executes `document.execCommand(a, b, c)` method\n\n__IMPORTANT__:\n\n`from`, `to`, `pos` arguments must be in the form: `{ line: Number, ch: Number }`.\n\nUse it with `<ui-component name=\"cloudeditor\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"LAZY menu\"></ui-component>\n\n<div style=\"width:600px;height:200px;border:1px solid #E0E0E0\">\n\t<ui-component name=\"cloudeditor\" path=\"editormodel\" config=\"contextmenu:editor_contextmenu;event:editor_event;realtime:1\"></ui-component>\n</div>\n<br />\n<div style=\"width:600px;height:200px;border:1px solid #E0E0E0\">\n\t<ui-component name=\"cloudeditor\" path=\"editormirrored\" config=\"readonly:1;$id:mirrored\"></ui-component>\n</div>\n\n<div class=\"clearfix\"></div>\n\n<script>\n\n\tvar editorbody = '\\n\\n<h1>HELLO WORLD</h1>\\n\\n';\n\tvar editormodel = { mode: 'clientside', body: editorbody, keywords: 'WORLD' };\n\tvar editormirrored = { mode: 'clientside', body: editorbody };\n\n\tfunction editor_event(type, value) {\n\t\tswitch (type) {\n\t\t\tcase 'change':\n\t\t\t\tSETTER('#mirrored/change', value);\n\t\t\t\tbreak;\n\t\t\tcase 'cursor':\n\t\t\t\tSETTER('#mirrored/marker', 'Peter', value.from);\n\t\t\t\tbreak;\n\t\t}\n\t\tSETTER('!menu/hide');\n\t}\n\n\tfunction editor_contextmenu(obj) {\n\t\tvar opt = {};\n\t\topt.x = obj.x;\n\t\topt.y = obj.y;\n\t\topt.items = [];\n\t\topt.items.push({ id: '1', name: 'Test item 1' });\n\t\topt.items.push({ id: '2', name: 'Test item 2' });\n\t\topt.items.push({ id: '3', name: 'Test item 3' });\n\t\topt.items.push({ id: '4', name: 'Test item 4' });\n\t\topt.callback = function(selected) {\n\t\t\tconsole.log(selected);\n\t\t};\n\t\tSETTER('menu/show', opt);\n\t}\n\n</script>\n```"}],"knowledge":{"content_sha256":"ee6919e8a1a54ff3d6d06f60bc718ba6151478a6fd5f43b03202f78adfbf9e53","source_sha256":"27956c0142a16f324bba70042fcb3d16d5b8defcf8fa52f2d81427e0f64382a6","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","E0E0E0","HELLO","LAZY","MIT","PATH","SETTER","WORLD"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"LAZY menu\"></ui-component>\n\n<div style=\"width:600px;height:200px;border:1px solid #E0E0E0\">\n\t<ui-component name=\"cloudeditor\" path=\"editormodel\" config=\"contextmenu:editor_contextmenu;event:editor_event;realtime:1\"></ui-component>\n</div>\n<br />\n<div style=\"width:600px;height:200px;border:1px solid #E0E0E0\">\n\t<ui-component name=\"cloudeditor\" path=\"editormirrored\" config=\"readonly:1;$id:mirrored\"></ui-component>\n</div>\n\n<div class=\"clearfix\"></div>\n\n<script>\n\n\tvar editorbody = '\\n\\n<h1>HELLO WORLD</h1>\\n\\n';\n\tvar editormodel = { mode: 'clientside', body: editorbody, keywords: 'WORLD' };\n\tvar editormirrored = { mode: 'clientside', body: editorbody };\n\n\tfunction editor_event(type, value) {\n\t\tswitch (type) {\n\t\t\tcase 'change':\n\t\t\t\tSETTER('#mirrored/change', value);\n\t\t\t\tbreak;\n\t\t\tcase 'cursor':\n\t\t\t\tSETTER('#mirrored/marker', 'Peter', value.from);\n\t\t\t\tbreak;\n\t\t}\n\t\tSETTER('!menu/hide');\n\t}\n\n\tfunction editor_contextmenu(obj) {\n\t\tvar opt = {};\n\t\topt.x = obj.x;\n\t\topt.y = obj.y;\n\t\topt.items = [];\n\t\topt.items.push({ id: '1', name: 'Test item 1' });\n\t\topt.items.push({ id: '2', name: 'Test item 2' });\n\t\topt.items.push({ id: '3', name: 'Test item 3' });\n\t\topt.items.push({ id: '4', name: 'Test item 4' });\n\t\topt.callback = function(selected) {\n\t\t\tconsole.log(selected);\n\t\t};\n\t\tSETTER('menu/show', opt);\n\t}\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/cloudeditorsimple","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/cloudeditorsimple/","markdown_url":"https://forge.totaljs.es/en/components/cloudeditorsimple.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"698d1986788a4c5e3fff2e6546468f8ef3b16a482d652fc5c654ce65ee2bacf6","checked_at":"2026-10-20T12:20:15.407Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-CloudEditorSimple","description":"It's a simple alternative to j-CloudEditor. You can specify the main configuration in the config, and the value is {String} (in j-CloudEditor is configuration part of value).","messages":[{"role":"user","content":"j-CloudEditorSimple\n\nIt's a simple alternative to j-CloudEditor. You can specify the main configuration in the config, and the value is {String} (in j-CloudEditor is configuration part of value)."},{"role":"assistant","content":"It's a simple alternative to `j-CloudEditor`. You can specify the main configuration in the config, and the value is `{String}` (in `j-CloudEditor` is configuration part of value).\n\n- it works in the iframe\n- supports multiple cursors (`shift + cmd + arrow-down/up`)\n- supports finding selected text (`cmd + d`)\n- supports custom scrollbars\n- supports diff tracker\n- supports a simple emmet plugin\n- supports real-time collaboration\n- source-code: <https://github.com/totaljs/editor>\n- it uses editor: [CodeMirror](https://codemirror.net/)\n- very small code\n\n__Configuration__:\n\n- `parent {String}` a selector for the main container (default: `auto`)\n- `autosave {Number}` auto rebinds value from the editor to the component's path (in milliseconds, default: `500`)\n- `event {String}` a path to the `function(name, value)`\n\t- `click`\n\t- `errors`\n\t- `shortcut`\n\t- `cursor` contains cursor position\n\t- `change` contains partial changes for real-time collaboration (`config.realtime` must be `true`)\n- `contextmenu {String}` a path to the `function(opt)`\n- `realtime {Boolean}` enables realtime sending changes to the `event` method (event name is `change`), default: `true`\n- `keywords {String}` custom keywords in the form e.g. `ID,PATH`\n- `minheight {Number}` optional, a minimal height (default: `0`)\n- `margin {Number}` optional, a vertical margin (default: `0`)\n- `linenumbers {Boolean}` optional, enables line numbers (default: `true`)\n- `type {String}` optional, content-type (default: `clientside`)\n- __NEW__: `url {String}` optional, a link to the editor (default: `//cdn.componentator.com/editor/1.min.html`)\n\n__Method__:\n\n- `component.insert('some text')`\n- `component.replace('text', 'newvalue')`\n- `component.search('text', callback(output))`\n- `component.select(from, [to])`\n- `component.goto(pos)`\n- `component.focus()`\n- `component.save(callback(value))`\n- `component.marker(name, [from], [to], [color])` adds + modifies (with `from`) and removes (without `from` argument) marker\n- `component.change(changes)` for the real-time collaboration (`changes` can be generated in the `config.event` function)\n- `component.clear()` clears all changes\n- `component.replaceselections(selections)`\n- `component.replaceselection(selection)`\n- `component.replacerange(value, from, [to])`\n- `component.command(cmd)` executes `codemirror.execCommand()` method\n- `component.exec(a, [b], [c])` executes `document.execCommand(a, b, c)` method\n\n__IMPORTANT__:\n\n`from`, `to`, `pos` arguments must be in the form: `{ line: Number, ch: Number }`.\n\nUse it with `<ui-component name=\"cloudeditorsimple\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"LAZY menu\"></ui-component>\n\n<div style=\"width:600px;height:200px;border:1px solid #E0E0E0\">\n\t<ui-component name=\"cloudeditorsimple\" path=\"editorbody\" config=\"contextmenu:editor_contextmenu;realtime:1\"></ui-component>\n</div>\n<div class=\"clearfix\"></div>\n\n<script>\n\n\tvar editorbody = '\\n\\n<h1>HELLO WORLD</h1>\\n\\n';\n\n\tfunction editor_contextmenu(obj) {\n\t\tvar opt = {};\n\t\topt.x = obj.x;\n\t\topt.y = obj.y;\n\t\topt.items = [];\n\t\topt.items.push({ id: '1', name: 'Test item 1' });\n\t\topt.items.push({ id: '2', name: 'Test item 2' });\n\t\topt.items.push({ id: '3', name: 'Test item 3' });\n\t\topt.items.push({ id: '4', name: 'Test item 4' });\n\t\topt.callback = function(selected) {\n\t\t\tconsole.log(selected);\n\t\t};\n\t\tSETTER('menu/show', opt);\n\t}\n\n</script>\n```"}],"knowledge":{"content_sha256":"48d28d7160ba07748099e29681b4e02e9afbd6e174eeca0a4f13a541b22f878f","source_sha256":"698d1986788a4c5e3fff2e6546468f8ef3b16a482d652fc5c654ce65ee2bacf6","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","E0E0E0","HELLO","LAZY","MIT","PATH","SETTER","WORLD"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"LAZY menu\"></ui-component>\n\n<div style=\"width:600px;height:200px;border:1px solid #E0E0E0\">\n\t<ui-component name=\"cloudeditorsimple\" path=\"editorbody\" config=\"contextmenu:editor_contextmenu;realtime:1\"></ui-component>\n</div>\n<div class=\"clearfix\"></div>\n\n<script>\n\n\tvar editorbody = '\\n\\n<h1>HELLO WORLD</h1>\\n\\n';\n\n\tfunction editor_contextmenu(obj) {\n\t\tvar opt = {};\n\t\topt.x = obj.x;\n\t\topt.y = obj.y;\n\t\topt.items = [];\n\t\topt.items.push({ id: '1', name: 'Test item 1' });\n\t\topt.items.push({ id: '2', name: 'Test item 2' });\n\t\topt.items.push({ id: '3', name: 'Test item 3' });\n\t\topt.items.push({ id: '4', name: 'Test item 4' });\n\t\topt.callback = function(selected) {\n\t\t\tconsole.log(selected);\n\t\t};\n\t\tSETTER('menu/show', opt);\n\t}\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/code","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/code/","markdown_url":"https://forge.totaljs.es/en/components/code.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"ee4283caa1826cb7c3fef6d56c77bc2df7ac28adb2b96f741af9f18e456b665d","checked_at":"2026-10-20T12:20:16.141Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Code","description":"It's a simple alternative to j-CloudEditor. You can specify the main configuration in the config, and the value is {String} (in j-CloudEditor is configuration part of value). It's the same component a","messages":[{"role":"user","content":"j-Code\n\nIt's a simple alternative to j-CloudEditor. You can specify the main configuration in the config, and the value is {String} (in j-CloudEditor is configuration part of value). It's the same component a"},{"role":"assistant","content":"It's a simple alternative to `j-CloudEditor`. You can specify the main configuration in the config, and the value is `{String}` (in `j-CloudEditor` is configuration part of value). It's the same component as `j-CloudEditorSimple`.\n\n- it works in the iframe\n- supports multiple cursors (`shift + cmd + arrow-down/up`)\n- supports finding selected text (`cmd + d`)\n- supports custom scrollbars\n- supports diff tracker\n- supports a simple emmet plugin\n- supports real-time collaboration\n- source-code: <https://github.com/totaljs/editor>\n- it uses editor: [CodeMirror](https://codemirror.net/)\n- very small code\n\n__Configuration__:\n\n- `parent {String}` a selector for the main container (default: `auto`)\n- `autosave {Number}` auto rebinds value from the editor to the component's path (in milliseconds, default: `500`)\n- `event {String}` a path to the `function(obj)`\n\t- `obj.type {String}`\n\t\t- `click`\n\t\t- `errors`\n\t\t- `shortcut`\n\t\t- `cursor` contains cursor position\n\t\t- `change` contains partial changes for real-time collaboration (`config.realtime` must be `true`)\n\t- `obj.instance {Editor}`\n- `contextmenu {String}` a path to the `function(opt)`\n- `realtime {Boolean}` enables realtime sending changes to the `event` method (event name is `change`), default: `true`\n- `keywords {String}` custom keywords in the form e.g. `ID,PATH`\n- `minheight {Number}` optional, a minimal height (default: `0`)\n- `margin {Number}` optional, a vertical margin (default: `0`)\n- `linenumbers {Boolean}` optional, enables line numbers (default: `true`)\n- `type {String}` optional, content-type (default: `clientside`)\n- `url {String}` optional, a link to the editor (default: `//cdn.componentator.com/editor/1.min.html`)\n- __NEW__: `lint {Boolean}` optional, enables lint (default: `true`)\n\n__Method__:\n\n- `component.insert('some text')`\n- `component.replace('text', 'newvalue')`\n- `component.search('text', callback(output))`\n- `component.select(from, [to])`\n- `component.goto(pos)`\n- `component.focus()`\n- `component.save(callback(value))`\n- `component.marker(name, [from], [to], [color])` adds + modifies (with `from`) and removes (without `from` argument) marker\n- `component.change(changes)` for the real-time collaboration (`changes` can be generated in the `config.event` function)\n- `component.clear()` clears all changes\n- `component.replaceselections(selections)`\n- `component.replaceselection(selection)`\n- `component.replacerange(value, from, [to])`\n- `component.command(cmd)` executes `codemirror.execCommand()` method\n- `component.exec(a, [b], [c])` executes `document.execCommand(a, b, c)` method\n- __NEW__: `component.setoption(name, value)` sends `setOption` command to the CodeMirror\n\n__IMPORTANT__:\n\n`from`, `to`, `pos` arguments must be in the form: `{ line: Number, ch: Number }`.\n\nUse it with `<ui-component name=\"code\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"LAZY menu\"></ui-component>\n\n<div style=\"width:600px;height:200px;border:1px solid #E0E0E0\">\n\t<ui-component name=\"code\" path=\"editorbody\" config=\"contextmenu:editor_contextmenu\"></ui-component>\n</div>\n<div class=\"clearfix\"></div>\n\n<script>\n\n\tvar editorbody = '\\n\\n<h1>HELLO WORLD</h1>\\n\\n';\n\n\tfunction editor_contextmenu(obj) {\n\t\tvar opt = {};\n\t\topt.x = obj.x;\n\t\topt.y = obj.y;\n\t\topt.items = [];\n\t\topt.items.push({ id: '1', name: 'Test item 1' });\n\t\topt.items.push({ id: '2', name: 'Test item 2' });\n\t\topt.items.push({ id: '3', name: 'Test item 3' });\n\t\topt.items.push({ id: '4', name: 'Test item 4' });\n\t\topt.callback = function(selected) {\n\t\t\tconsole.log(selected);\n\t\t};\n\t\tSETTER('menu/show', opt);\n\t}\n\n</script>\n```"}],"knowledge":{"content_sha256":"1d189f35478c63e2c18b92c1a4934116e18f5d2d1c1265f68ea9cee85814027b","source_sha256":"ee4283caa1826cb7c3fef6d56c77bc2df7ac28adb2b96f741af9f18e456b665d","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","E0E0E0","HELLO","LAZY","MIT","PATH","SETTER","WORLD"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"LAZY menu\"></ui-component>\n\n<div style=\"width:600px;height:200px;border:1px solid #E0E0E0\">\n\t<ui-component name=\"code\" path=\"editorbody\" config=\"contextmenu:editor_contextmenu\"></ui-component>\n</div>\n<div class=\"clearfix\"></div>\n\n<script>\n\n\tvar editorbody = '\\n\\n<h1>HELLO WORLD</h1>\\n\\n';\n\n\tfunction editor_contextmenu(obj) {\n\t\tvar opt = {};\n\t\topt.x = obj.x;\n\t\topt.y = obj.y;\n\t\topt.items = [];\n\t\topt.items.push({ id: '1', name: 'Test item 1' });\n\t\topt.items.push({ id: '2', name: 'Test item 2' });\n\t\topt.items.push({ id: '3', name: 'Test item 3' });\n\t\topt.items.push({ id: '4', name: 'Test item 4' });\n\t\topt.callback = function(selected) {\n\t\t\tconsole.log(selected);\n\t\t};\n\t\tSETTER('menu/show', opt);\n\t}\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/codemirror","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/codemirror/","markdown_url":"https://forge.totaljs.es/en/components/codemirror.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"f400ffcd34f4b7b42b550b4e1f4e37818da4b4ca4912c631e6315fb65ea4fdec","checked_at":"2026-10-20T12:20:17.664Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-CodeMirror","description":"j-CodeMirror: supports multiple cursors (shift + cmd + arrow-down/up); supports finding selected text (cmd + d)","messages":[{"role":"user","content":"j-CodeMirror\n\nj-CodeMirror: supports multiple cursors (shift + cmd + arrow-down/up); supports finding selected text (cmd + d)"},{"role":"assistant","content":"- supports multiple cursors (`shift + cmd + arrow-down/up`)\n- supports finding selected text (`cmd + d`)\n- supports custom scrollbars\n\n__Configuration__:\n\n- `type {String}` type for syntax highlighting (default: `htmlmixed`)\n- `required {Boolean}` enables \"required\" (default: `false`)\n- `linenumbers {Boolean}` shows line numbers (default: `false`)\n- `icon {String}` icon for label without `fa-` e.g. `home`, `cog`, etc.\n- `height {Number}` control height (default: `200` px)\n- `label {String}` label (default is HTML content)\n- `disabled {Boolean}` disables this component\n- `tabs {Boolean}` enables indent with tabs (default: `false`)\n- `trim {Boolean}` performs auto-trim and removes empty begin/end lines (default: `false`)\n- `parent {String}` can set height by parent element\n- `margin {Number}` a margin for height\n- `minheight {Number}` a minimal height for `parent` option (default: `200`)\n\n__Method__:\n\n- `component.insert('some text')` inserts a text to the current position of the cursor\n\nUse it with `<ui-component name=\"codemirror\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"width:500px\">\n\t<ui-component name=\"codemirror\" path=\"html\" config=\"type:htmlmixed;required:true;height:200\">HTML editor</ui-component>\n</div>\n\n<script>\n\tvar html = '<h1>HELLO WORLD</h1>';\n</script>\n```"}],"knowledge":{"content_sha256":"14caca6469063f506170b25f135534f1bee7d131f3e93b4a49e1684f29b9acb7","source_sha256":"f400ffcd34f4b7b42b550b4e1f4e37818da4b4ca4912c631e6315fb65ea4fdec","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","HELLO","HTML","MIT","WORLD"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"width:500px\">\n\t<ui-component name=\"codemirror\" path=\"html\" config=\"type:htmlmixed;required:true;height:200\">HTML editor</ui-component>\n</div>\n\n<script>\n\tvar html = '<h1>HELLO WORLD</h1>';\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/colorpicker","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/colorpicker/","markdown_url":"https://forge.totaljs.es/en/components/colorpicker.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"387ca6640d9e2e9942c634c3bf28364483631379d4f893766f61db528c15e3ff","checked_at":"2026-10-20T12:20:18.401Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-ColorPicker","description":"j-ColorPicker: Component is singleton; Simple color picker","messages":[{"role":"user","content":"j-ColorPicker\n\nj-ColorPicker: Component is singleton; Simple color picker"},{"role":"assistant","content":"- Component is __singleton__\n- Simple color picker\n- Supports __Dark theme__\n\n\n## Usage\n\n```javascript\nvar opt = {};\n\n// opt.offsetX {Number}    : adds X offset (default: 0)\n// opt.offsetY {Number}    : adds Y offset (default: 0)\n// opt.align {String}      : align `left` (default), `center` or `right`\n// opt.position {String}   : position `top` (default) or `bottom`\n// opt.opacity {Number}    : converts hex color to RGBA with the opacity e.g. 0.3\n\nopt.element = YOUR_ELEMENT;\n// or if you want to use a fixed position:\n// opt.x {Number}          : `x` position\n// opt.y {Number}          : `y` position\n\nopt.callback = function(color) {\n\tconsole.log(color); // callback parameter has string with HEX color (like #bada55)\n};\n\nSETTER('colorpicker/show', opt);\n```\n\nUse it with `<ui-component name=\"colorpicker\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Denis Granec. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"colorpicker\"></ui-component>\n\n<div class=\"container\">\n\t<div class=\"row\">\n\t\t<div class=\"col-xs-4 col-xs-offset-4\" style=\"text-align:center\">\n\t\t\t<a href=\"javascript:void(0)\" style=\"color:black;font-size:30px\"><i class=\"ti ti-dropper\"></i></a>\n\t\t</div>\n\t</div>\n</div>\n\n<script>\n\t$(document).on('click', 'a', function() {\n\t\tvar opt = {};\n\t\topt.align = 'center';\n\t\topt.position = 'top';\n\t\topt.element = this;\n\t\topt.callback = function(color) {\n\t\t\talert(color);\n\t\t};\n\t\tSETTER('colorpicker/show', opt);\n\t});\n</script>\n```"}],"knowledge":{"content_sha256":"84ee07ce8fa88a978c47b0652aeeca48ac152ef9cb0e593b870aa82361df5844","source_sha256":"387ca6640d9e2e9942c634c3bf28364483631379d4f893766f61db528c15e3ff","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","HEX","MIT","RGBA","SETTER","YOUR_ELEMENT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"colorpicker\"></ui-component>\n\n<div class=\"container\">\n\t<div class=\"row\">\n\t\t<div class=\"col-xs-4 col-xs-offset-4\" style=\"text-align:center\">\n\t\t\t<a href=\"javascript:void(0)\" style=\"color:black;font-size:30px\"><i class=\"ti ti-dropper\"></i></a>\n\t\t</div>\n\t</div>\n</div>\n\n<script>\n\t$(document).on('click', 'a', function() {\n\t\tvar opt = {};\n\t\topt.align = 'center';\n\t\topt.position = 'top';\n\t\topt.element = this;\n\t\topt.callback = function(color) {\n\t\t\talert(color);\n\t\t};\n\t\tSETTER('colorpicker/show', opt);\n\t});\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/colorpickerbutton","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/colorpickerbutton/","markdown_url":"https://forge.totaljs.es/en/components/colorpickerbutton.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"b884630b5955c50fb5cc139e161590bb2873f9a0a6ac829ff7c1973941bf1fac","checked_at":"2026-10-20T12:20:19.141Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-ColorPickerButton","description":"j-ColorPickerButton: Component is singleton; Supports Dark theme","messages":[{"role":"user","content":"j-ColorPickerButton\n\nj-ColorPickerButton: Component is singleton; Supports Dark theme"},{"role":"assistant","content":"- Component is __singleton__\n- Supports __Dark theme__\n- It uses `j-ColorPicker`\n\n\n__Configuration__:\n\n- `default` a default color (default: `#FFFFFF`)\n- `align` align for j-ColorPicker (default: `left`)\n- `position` position for j-ColorPicker (default: `top`)\n- `offsetX` offset-x for j-ColorPicker (default: `0`)\n- `offsetY` offset-y for j-ColorPicker (default: `0`)\n- `disabled` can disabled this component (default: `false`)\n\nUse it with `<ui-component name=\"colorpickerbutton\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"colorpicker\"></ui-component>\n\n<div class=\"row\">\n\t<div class=\"col-xs-4\">\n\t\t<ui-component name=\"colorpickerbutton\" path=\"color\"></ui-component>\n\t</div>\n</div>\n\n<script>\n\tvar color = '#4F69FF';\n</script>\n```"}],"knowledge":{"content_sha256":"ec402d9c32fe305cebeda6d38e9b5dfca49cb7bff6e9ab26b5219e62ed14a88e","source_sha256":"b884630b5955c50fb5cc139e161590bb2873f9a0a6ac829ff7c1973941bf1fac","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","FFFFFF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"colorpicker\"></ui-component>\n\n<div class=\"row\">\n\t<div class=\"col-xs-4\">\n\t\t<ui-component name=\"colorpickerbutton\" path=\"color\"></ui-component>\n\t</div>\n</div>\n\n<script>\n\tvar color = '#4F69FF';\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/colorselector","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/colorselector/","markdown_url":"https://forge.totaljs.es/en/components/colorselector.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"ebc1a7c765e6f3713306b1157579df6d6052d8840e2bd8f6615921d5ba430bb2","checked_at":"2026-10-20T12:20:19.884Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-ColorSelector","description":"j-ColorSelector options: disabled, required","messages":[{"role":"user","content":"j-ColorSelector\n\nj-ColorSelector options: disabled, required"},{"role":"assistant","content":"__Configuration__:\n\n- `disabled {Boolean}` can disable this control\n- `required {Boolean}` (optional) enables \"required\" (default: `false`)\n\nUse it with `<ui-component name=\"colorselector\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"width:300px;margin:50px auto 0\">\n\t<ui-component name=\"colorselector\" path=\"mycolor\">Select color:</ui-component>\n</div>\n\n<script>\n\tvar mycolor = '#3BAFDA';\n</script>\n```"}],"knowledge":{"content_sha256":"6e21e13525dd080fc7dd2111d8ee47cff95164c0cb5d16a17265d6e1119c13e6","source_sha256":"ebc1a7c765e6f3713306b1157579df6d6052d8840e2bd8f6615921d5ba430bb2","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"width:300px;margin:50px auto 0\">\n\t<ui-component name=\"colorselector\" path=\"mycolor\">Select color:</ui-component>\n</div>\n\n<script>\n\tvar mycolor = '#3BAFDA';\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/columns","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/columns/","markdown_url":"https://forge.totaljs.es/en/components/columns.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"05a53061133e1802f0c04b766f62c3de9026489dce4534bd69d60d3d9fcac725","checked_at":"2026-10-20T12:20:20.615Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Columns","description":"The component Columns is a very simple alternative to j-Layout and j-Layout2. With this component you can set columns with 100% height by your needs.","messages":[{"role":"user","content":"j-Columns\n\nThe component Columns is a very simple alternative to j-Layout and j-Layout2. With this component you can set columns with 100% height by your needs."},{"role":"assistant","content":"The component `Columns` is a very simple alternative to `j-Layout` and `j-Layout2`. With this component you can set columns with 100% height by your needs.\n\n\n__Configuration__:\n\n- `parent {String}` can contain `parent`, `window` or jQuery selector for the `closest` method (default: `window`)\n- `margin {Number}` add margin to `height` (default: `0`)\n- `marginlg {Number}` adds margin to `height` for `LG` display (default: `margin`)\n- `marginmd {Number}` adds margin to `height` for `MD` display (default: `margin`)\n- `marginsm {Number}` adds margin to `height` for `SM` display (default: `margin`)\n- `marginxs {Number}` adds margin to `height` for `XS` display (default: `margin`)\n- `noborder {Boolean}` disables border (default: `false`)\n- `fontsize {Boolean}` enables percentual font-size according to the size of rows (default: `false`)\n\n__Methods__:\n\n- `SETTER('columns/refresh')` - reloads all `section` and sets sizes\n- `SETTER('columns/add', size, html_node)` - adds a new column and performs refresh\n\n__Good to know__:\n\nEach column must be wrappend in `<section>`, try to understand from example.\n\n__Attributes__:\n\n- `data-size` contains a size of section `LG,MD,SM,XS` --> in pixels or percentage, example: `200,200,200,0` (`0` means hidden)\n- section without `data-size` attribute will be counted from the residual value\n\nUse it with `<ui-component name=\"columns\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>body { padding: 0; margin: 0; }</style>\n\n<ui-component name=\"columns\" config=\"parent:window\" class=\"hidden invisible\">\n\t<section data-size=\"200\">Left panel</section>\n\t<section>Main</section>\n\t<section data-size=\"200\">Right panel</section>\n</ui-component>\n```"}],"knowledge":{"content_sha256":"df37b4402bd25c3de6e00764bd53aed681deaf6858ad5e886700fb16ccf7d4d9","source_sha256":"05a53061133e1802f0c04b766f62c3de9026489dce4534bd69d60d3d9fcac725","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","SETTER"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>body { padding: 0; margin: 0; }</style>\n\n<ui-component name=\"columns\" config=\"parent:window\" class=\"hidden invisible\">\n\t<section data-size=\"200\">Left panel</section>\n\t<section>Main</section>\n\t<section data-size=\"200\">Right panel</section>\n</ui-component>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/configuration","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/configuration/","markdown_url":"https://forge.totaljs.es/en/components/configuration.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"2b0aa05479c15f47845086b9e780c103e4893ee7e852db09cc00f157bc24cd2d","checked_at":"2026-10-20T12:20:21.498Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Configuration","description":"j-Configuration: supports dark mode; NEW: datasource {String} optional, a path to the data-source for config items","messages":[{"role":"user","content":"j-Configuration\n\nj-Configuration: supports dark mode; NEW: datasource {String} optional, a path to the data-source for config items"},{"role":"assistant","content":"- supports __dark mode__\n\n__Configuration__:\n\n- `dateformat {String}` a default date format (default: `DEF.dateformat`)\n\n### Declaration\n\n__Allowed properties__:\n\n- `type {String}` supports:\n\t- `string`\n\t- `number`\n\t- `date`\n\t- `boolean`\n\t- `dropdown`\n\t- `color`\n\t- `icon`\n\t- `multiline`\n\t- `selectable`\n- `text {String}` a text/title\n- `name {String}` a name of property in the model\n- `align {Number}` align for inputs (`0` left (default), `1` center, `2` right)\n- `icon {String}` optional, Total JS or font-awesome icon identifier\n- `summary {String}` optional, a summarization\n- `note {String}` optional, a note\n- `required {Boolean}` optional, is the property required?\n- `disable {String}` optional, declaration of arrow function in the form `value.interval === 100`\n- `visible {String}` optional, declaration of arrow function in the form `value.interval === 100`\n- `button {String}` optional, creates a small button\n- `click {String}` optional, a link to the method (depends on the `button`)\n- `maxlength {Number}` optional, supports only `string` type\n- `min {Number}` optional, supports only `number` type\n- `max {Number}` optional, supports only `number` type\n- `increment {Number}` optional, an increment count - supports only `number` type (default: `1`)\n- `multiple {Boolean}` optional, works with `selectable` type (a value will be array)\n- `monospace {Boolean}` optional, works with `string` and `number` types\n- `bold {Boolean}` optional, works with `string` and `number` types\n- `placeholder {String}` optional\n- `items {String/Object Array}` optional for `dropdown` and `selectable` in the form `[{ id: 'VALUE', name: 'TEXT' }]`\n\t - type `string` a path to the `Object Array` (works with scopes)\n\t - type `Object Array` fixed defined array\n- `dirplaceholder {String}` optional, placeholder for `j-Directory`\n- `dirsearch {Boolean}` optional, enables/disables `search` field in `j-Directory` (default: `true`)\n- __NEW__: `datasource {String}` optional, a path to the data-source for config items\n\nDeclaration must be defined in the component's element body wrapped in the `<script type=\"text/plain\">` element. Example:\n\n```js\n[\n\t{\n\t\tname: 'My group label',\n\t\ttype: 'group',\n\t\t// summary: 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Numquam, nostrum.',\n\t\t// icon: 'ti ti-home'\n\t},\n\t{\n\t\tid: 'name',\n\t\tname: 'Application name',\n\t\ttype: 'string',\n\t\ticon: 'ti ti-home',\n\t\tdisable: 'model => model.interval < 100',\n\t\tsummary: 'Lorem, ipsum dolor sit amet consectetur, adipisicing elit. Iste eos, illum voluptas assumenda sunt possimus necessitatibus nobis provident dicta deserunt.',\n\t\trequired: true\n\t},\n\t... other items\n]\n```\n\nUse it with `<ui-component name=\"configuration\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"LAZY colorpicker\"></ui-component>\n<ui-component name=\"LAZY datepicker\"></ui-component>\n<ui-component name=\"LAZY directory\"></ui-component>\n<ui-component name=\"LAZY icons\"></ui-component>\n\n<ui-component name=\"configuration\" path=\"config\" class=\"invisible\">\n\t<script type=\"name/plain\">\n\t\t[\n\t\t\t{\n\t\t\t\tname: 'My group',\n\t\t\t\ttype: 'group',\n\t\t\t\tsummary: 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Numquam, nostrum.',\n\t\t\t\ticon: 'ti ti-home'\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'name',\n\t\t\t\tname: 'Application name',\n\t\t\t\ttype: 'string',\n\t\t\t\ticon: 'ti ti-home',\n\t\t\t\tdisable: 'model => model.interval < 100',\n\t\t\t\tbutton: 'Verify',\n\t\t\t\tnote: 'ajldksasdljsalj',\n\t\t\t\tsummary: 'Lorem, ipsum dolor sit amet consectetur, adipisicing elit. Iste eos, illum voluptas assumenda sunt possimus necessitatibus nobis provident dicta deserunt.',\n\t\t\t\trequired: true\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'name',\n\t\t\t\tname: 'Application name',\n\t\t\t\ttype: 'string',\n\t\t\t\ticon: 'ti ti-home',\n\t\t\t\tnote: 'ajldksasdljsalj',\n\t\t\t\tsummary: 'Lorem, ipsum dolor sit amet consectetur, adipisicing elit. Iste eos, illum voluptas assumenda sunt possimus necessitatibus nobis provident dicta deserunt.',\n\t\t\t\tcamouflage: true,\n\t\t\t\trequired: true\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'date',\n\t\t\t\tname: 'Date',\n\t\t\t\ttype: 'date',\n\t\t\t\tdisable: 'model => model.interval < 100',\n\t\t\t\tplaceholder: 'Test',\n\t\t\t\tsummary: 'Lorem, ipsum dolor sit amet consectetur, adipisicing elit. Iste eos, illum voluptas assumenda sunt possimus necessitatibus nobis provident dicta deserunt.'\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'interval',\n\t\t\t\tname: 'Backup interval',\n\t\t\t\ttype: 'number',\n\t\t\t\tmin: 1,\n\t\t\t\tsummary: 'Lorem, ipsum dolor sit amet consectetur, adipisicing elit. Iste eos, illum voluptas assumenda sunt possimus necessitatibus nobis provident dicta deserunt.'\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'color',\n\t\t\t\tname: 'Color',\n\t\t\t\ttype: 'color',\n\t\t\t\tdisable: 'model => model.interval < 100',\n\t\t\t\tsummary: 'Lorem, ipsum dolor sit amet consectetur, adipisicing elit. Iste eos, illum voluptas assumenda sunt possimus necessitatibus nobis provident dicta deserunt.'\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'icon',\n\t\t\t\tname: 'Icon',\n\t\t\t\ttype: 'icon',\n\t\t\t\tdisable: 'model => model.interval < 100',\n\t\t\t\tsummary: 'Lorem, ipsum dolor sit amet consectetur, adipisicing elit. Iste eos, illum voluptas assumenda sunt possimus necessitatibus nobis provident dicta deserunt.'\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'interval',\n\t\t\t\tname: 'Backup interval',\n\t\t\t\ttype: 'number'\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'interval',\n\t\t\t\tdisable: 'model => model.interval < 100',\n\t\t\t\tname: 'Backup interval',\n\t\t\t\ttype: 'number'\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'city2',\n\t\t\t\titems: [{ id: 'bb', name: 'BB', icon: 'ti ti-map-pin' }, { id: 'ba', name: 'BA', icon: 'ti ti-map-pin' }, { id: 'zv', name: 'ZV', icon: 'ti ti-map-pin' }, { id: 'se', name: 'SE', icon: 'ti ti-map-pin' }],\n\t\t\t\tname: 'City',\n\t\t\t\tmultiple: true,\n\t\t\t\tdirempty: 'Empty',\n\t\t\t\ttype: 'selectable'\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'city',\n\t\t\t\titems: [{ id: 'bb', name: 'Banská Bystrica' }, { id: 'ba', name: 'Bratislava' }, { id: 'zv', name: 'Zvolen' }],\n\t\t\t\tname: 'City',\n\t\t\t\tnoborder: 1,\n\t\t\t\tdirempty: 'Empty',\n\t\t\t\tplaceholder: 'Choose city',\n\t\t\t\tdisable: 'model => model.interval < 100',\n\t\t\t\ttype: 'dropdown'\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'System',\n\t\t\t\ttype: 'group',\n\t\t\t\tsummary: 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Numquam, nostrum.',\n\t\t\t\ticon: 'ti ti-home'\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'notes',\n\t\t\t\tname: 'Multiline',\n\t\t\t\ttype: 'multiline',\n\t\t\t\tplaceholder: 'Test placeholder',\n\t\t\t\tsummary: 'Lorem, ipsum dolor sit amet consectetur, adipisicing elit. Iste eos, illum voluptas assumenda sunt possimus necessitatibus nobis provident dicta deserunt.'\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'newsletter',\n\t\t\t\tname: 'Allow newsletter',\n\t\t\t\ttype: 'boolean',\n\t\t\t\tdisable: 'model => model.interval < 100',\n\t\t\t\tsummary: 'Lorem, ipsum dolor sit amet consectetur, adipisicing elit. Iste eos, illum voluptas assumenda sunt possimus necessitatibus nobis provident dicta deserunt.'\n\t\t\t}\n\t\t]\n\t</script>\n</ui-component>\n\n<script>\n\tvar config = { interval: 100, icon: 'ti ti-home', color: '#A0A0A0', newsletter: false, name: 'Peter Širka', city: '' };\n</script>\n```"}],"knowledge":{"content_sha256":"e93c0ff449451c5bba8dafdccd0988c86f601e073a23db266833de9ba94913b9","source_sha256":"2b0aa05479c15f47845086b9e780c103e4893ee7e852db09cc00f157bc24cd2d","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["A0A0A0","CDN","DEF","LAZY","MIT","TEXT","VALUE"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"LAZY colorpicker\"></ui-component>\n<ui-component name=\"LAZY datepicker\"></ui-component>\n<ui-component name=\"LAZY directory\"></ui-component>\n<ui-component name=\"LAZY icons\"></ui-component>\n\n<ui-component name=\"configuration\" path=\"config\" class=\"invisible\">\n\t<script type=\"name/plain\">\n\t\t[\n\t\t\t{\n\t\t\t\tname: 'My group',\n\t\t\t\ttype: 'group',\n\t\t\t\tsummary: 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Numquam, nostrum.',\n\t\t\t\ticon: 'ti ti-home'\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'name',\n\t\t\t\tname: 'Application name',\n\t\t\t\ttype: 'string',\n\t\t\t\ticon: 'ti ti-home',\n\t\t\t\tdisable: 'model => model.interval < 100',\n\t\t\t\tbutton: 'Verify',\n\t\t\t\tnote: 'ajldksasdljsalj',\n\t\t\t\tsummary: 'Lorem, ipsum dolor sit amet consectetur, adipisicing elit. Iste eos, illum voluptas assumenda sunt possimus necessitatibus nobis provident dicta deserunt.',\n\t\t\t\trequired: true\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'name',\n\t\t\t\tname: 'Application name',\n\t\t\t\ttype: 'string',\n\t\t\t\ticon: 'ti ti-home',\n\t\t\t\tnote: 'ajldksasdljsalj',\n\t\t\t\tsummary: 'Lorem, ipsum dolor sit amet consectetur, adipisicing elit. Iste eos, illum voluptas assumenda sunt possimus necessitatibus nobis provident dicta deserunt.',\n\t\t\t\tcamouflage: true,\n\t\t\t\trequired: true\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'date',\n\t\t\t\tname: 'Date',\n\t\t\t\ttype: 'date',\n\t\t\t\tdisable: 'model => model.interval < 100',\n\t\t\t\tplaceholder: 'Test',\n\t\t\t\tsummary: 'Lorem, ipsum dolor sit amet consectetur, adipisicing elit. Iste eos, illum voluptas assumenda sunt possimus necessitatibus nobis provident dicta deserunt.'\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'interval',\n\t\t\t\tname: 'Backup interval',\n\t\t\t\ttype: 'number',\n\t\t\t\tmin: 1,\n\t\t\t\tsummary: 'Lorem, ipsum dolor sit amet consectetur, adipisicing elit. Iste eos, illum voluptas assumenda sunt possimus necessitatibus nobis provident dicta deserunt.'\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'color',\n\t\t\t\tname: 'Color',\n\t\t\t\ttype: 'color',\n\t\t\t\tdisable: 'model => model.interval < 100',\n\t\t\t\tsummary: 'Lorem, ipsum dolor sit amet consectetur, adipisicing elit. Iste eos, illum voluptas assumenda sunt possimus necessitatibus nobis provident dicta deserunt.'\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'icon',\n\t\t\t\tname: 'Icon',\n\t\t\t\ttype: 'icon',\n\t\t\t\tdisable: 'model => model.interval < 100',\n\t\t\t\tsummary: 'Lorem, ipsum dolor sit amet consectetur, adipisicing elit. Iste eos, illum voluptas assumenda sunt possimus necessitatibus nobis provident dicta deserunt.'\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'interval',\n\t\t\t\tname: 'Backup interval',\n\t\t\t\ttype: 'number'\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'interval',\n\t\t\t\tdisable: 'model => model.interval < 100',\n\t\t\t\tname: 'Backup interval',\n\t\t\t\ttype: 'number'\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'city2',\n\t\t\t\titems: [{ id: 'bb', name: 'BB', icon: 'ti ti-map-pin' }, { id: 'ba', name: 'BA', icon: 'ti ti-map-pin' }, { id: 'zv', name: 'ZV', icon: 'ti ti-map-pin' }, { id: 'se', name: 'SE', icon: 'ti ti-map-pin' }],\n\t\t\t\tname: 'City',\n\t\t\t\tmultiple: true,\n\t\t\t\tdirempty: 'Empty',\n\t\t\t\ttype: 'selectable'\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'city',\n\t\t\t\titems: [{ id: 'bb', name: 'Banská Bystrica' }, { id: 'ba', name: 'Bratislava' }, { id: 'zv', name: 'Zvolen' }],\n\t\t\t\tname: 'City',\n\t\t\t\tnoborder: 1,\n\t\t\t\tdirempty: 'Empty',\n\t\t\t\tplaceholder: 'Choose city',\n\t\t\t\tdisable: 'model => model.interval < 100',\n\t\t\t\ttype: 'dropdown'\n\t\t\t},\n\t\t\t{\n\t\t\t\tname: 'System',\n\t\t\t\ttype: 'group',\n\t\t\t\tsummary: 'Lorem ipsum dolor, sit amet consectetur adipisicing elit. Numquam, nostrum.',\n\t\t\t\ticon: 'ti ti-home'\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'notes',\n\t\t\t\tname: 'Multiline',\n\t\t\t\ttype: 'multiline',\n\t\t\t\tplaceholder: 'Test placeholder',\n\t\t\t\tsummary: 'Lorem, ipsum dolor sit amet consectetur, adipisicing elit. Iste eos, illum voluptas assumenda sunt possimus necessitatibus nobis provident dicta deserunt.'\n\t\t\t},\n\t\t\t{\n\t\t\t\tid: 'newsletter',\n\t\t\t\tname: 'Allow newsletter',\n\t\t\t\ttype: 'boolean',\n\t\t\t\tdisable: 'model => model.interval < 100',\n\t\t\t\tsummary: 'Lorem, ipsum dolor sit amet consectetur, adipisicing elit. Iste eos, illum voluptas assumenda sunt possimus necessitatibus nobis provident dicta deserunt.'\n\t\t\t}\n\t\t]\n\t</script>\n</ui-component>\n\n<script>\n\tvar config = { interval: 100, icon: 'ti ti-home', color: '#A0A0A0', newsletter: false, name: 'Peter Širka', city: '' };\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/confirm","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/confirm/","markdown_url":"https://forge.totaljs.es/en/components/confirm.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"dafbb4db79bff75b1c05283643598d3c68c1609d11823515944d55727aedd33a","checked_at":"2026-10-20T12:20:22.397Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Confirm","description":"j-Confirm: easy usage; singleton","messages":[{"role":"user","content":"j-Confirm\n\nj-Confirm: easy usage; singleton"},{"role":"assistant","content":"- singleton\n- works with Bootstrap\n- supports __dark mode__\n- modal\n\n\n__Usage__:\n\n```javascript\nSETTER('confirm/show', message, ['Yes', 'No'], function(index) {\n\t// @index {Number} a button index\n\n\t// In this case:\n\t// index == 0: Yes\n\t// index == 1: No\n});\n\nSETTER('confirm/show2', message, ['Yes', 'No'], function() {\n\t// In this case will be the callback executed if the user will click on \"yes\" button only\n});\n```\n\n## A simple styling of buttons\n\n```javascript\n// \"Remove\" button will be with Font-Awesome icon called: \"trash-o\"\nSETTER('confirm', 'show2', 'Are you sure you want to remove selected item?', ['\"trash\" Remove', 'Cancel'], REMOVE_FUNCTION);\n\n// \"Confirm\" button will be \"green\" with Font-Awesome icon called: \"checked-circle\"\nSETTER('confirm', 'show2', 'Are you sure you want to confirm selected items?', ['\"checked-circle\" Confirm #2BA433', 'Cancel'], REMOVE_FUNCTION);\n```\n\nUse it with `<ui-component name=\"confirm\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"confirm\"></ui-component>\n\n<script>\n\tSETTER(true, 'confirm/show2', 'Do you want to remove React/Vue/Angular.js from your device?', ['\"check-circle\" OK #2BA433', 'Cancel'], function() {\n\t\talert('Thank you for using of jComponent library');\n\t});\n</script>\n```"}],"knowledge":{"content_sha256":"f766b24d177d455133038c658236ff68f2d0e46a20a4f506db0257c9cc6a15ba","source_sha256":"dafbb4db79bff75b1c05283643598d3c68c1609d11823515944d55727aedd33a","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","REMOVE_FUNCTION","SETTER"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"confirm\"></ui-component>\n\n<script>\n\tSETTER(true, 'confirm/show2', 'Do you want to remove React/Vue/Angular.js from your device?', ['\"check-circle\" OK #2BA433', 'Cancel'], function() {\n\t\talert('Thank you for using of jComponent library');\n\t});\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/console","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/console/","markdown_url":"https://forge.totaljs.es/en/components/console.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"d280537a1c9f8a5f02de57f2693baa67dd31109c5dca66af9bedc2133fee304d","checked_at":"2026-10-20T12:20:23.134Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Console","description":"j-Console: easy usage; singleton","messages":[{"role":"user","content":"j-Console\n\nj-Console: easy usage; singleton"},{"role":"assistant","content":"- singleton\n- supports app tabs\n- supports __dark mode__\n\n__Configuration__:\n\n- `datasource {String}` a path to data-source (learn from example)\n- __NEW__ `clear {String}` clear logs, link to the `function(source)`\n\n__Good to know__:\n\n- `path` can be `{String}` with the `appid` due to focusing logs\n\nUse it with `<ui-component name=\"console\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\tbody { background-color: #D0D0D0; }\n</style>\n\n<ui-component name=\"console\" path=\"visible\" config=\"datasource:logs\"></ui-component>\n\n<script>\n\n\tvar visible = true;\n\tvar logs = {};\n\n\tlogs['app1'] = { icon: 'cog', name: 'Settings', encode: false, items: [{ type: 'info', body: 'Lorem <b>ipsum</b> dolor sit amet, consectetur adipisicing elit. Soluta, ea.' }, { type: 'error', body: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Soluta, ea.' }] };\n\tlogs['app2'] = { icon: 'cloud', items: [{ type: 'warning', body: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Soluta, ea.' }, { type: 'success', body: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Soluta, ea.' }] };\n\n\t// 2 seconds delay\n\tPUSH('logs.app1.items', { type: 'warning', body: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Hic, tempora.' }, 2000);\n\n</script>\n```"}],"knowledge":{"content_sha256":"6fa2d1fc1e00c079f77751851d7e29f10ea1e9cfdd8ecd359265a2454f46b960","source_sha256":"d280537a1c9f8a5f02de57f2693baa67dd31109c5dca66af9bedc2133fee304d","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","D0D0D0","DEF","MIT","PUSH"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\tbody { background-color: #D0D0D0; }\n</style>\n\n<ui-component name=\"console\" path=\"visible\" config=\"datasource:logs\"></ui-component>\n\n<script>\n\n\tvar visible = true;\n\tvar logs = {};\n\n\tlogs['app1'] = { icon: 'cog', name: 'Settings', encode: false, items: [{ type: 'info', body: 'Lorem <b>ipsum</b> dolor sit amet, consectetur adipisicing elit. Soluta, ea.' }, { type: 'error', body: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Soluta, ea.' }] };\n\tlogs['app2'] = { icon: 'cloud', items: [{ type: 'warning', body: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Soluta, ea.' }, { type: 'success', body: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Soluta, ea.' }] };\n\n\t// 2 seconds delay\n\tPUSH('logs.app1.items', { type: 'warning', body: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Hic, tempora.' }, 2000);\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/console2","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/console2/","markdown_url":"https://forge.totaljs.es/en/components/console2.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"6e496ff7a1ee800be8a6c48f4d81517afcc4cd35237b15ce1d4a4632896ec67e","checked_at":"2026-10-20T12:20:23.870Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Console2","description":"j-Console2: easy usage; supports dark mode","messages":[{"role":"user","content":"j-Console2\n\nj-Console2: easy usage; supports dark mode"},{"role":"assistant","content":"- supports __dark mode__\n\n__Configuration__:\n\n- `icon {String}` a label icon (default: `ti ti-script`)\n- `name {String}` a label (default: `Console`)\n- `datasource {String}` __required__ a path to a data-source `Array`\n- `clear {String_LinkToFunction/Boolean}` adds a clear button (default: `true`)\n- `autoclose {Boolean}` console automatically closes after clearing (default: `true`)\n- `autoshow {Boolean}` console automatically opens after new logs (default: `true`)\n- `height {Number}` console height in pixels (default: `150`)\n\nUse it with `<ui-component name=\"console2\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\tbody { background-color: #D0D0D0; }\n</style>\n\n<ui-component name=\"console2\" path=\"visible\" config=\"datasource:logs\"></ui-component>\n\n<script>\n\n\tvar visible = true;\n\tvar logs = [];\n\n\tlogs.push({ type: 'info', body: 'Lorem <b>ipsum</b> dolor sit amet, consectetur adipisicing elit. Soluta, ea.' });\n\tlogs.push({ type: 'error', body: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Soluta, ea.' });\n\n\t// 2 seconds delay\n\tsetTimeout(() => PUSH('logs', { type: 'warning', body: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Hic, tempora.' }), 2000);\n\n</script>\n```"}],"knowledge":{"content_sha256":"14ec42d6f701ca031ba95aaf10e9aae5704ddb690361abd7ce3f217a9c2ce580","source_sha256":"6e496ff7a1ee800be8a6c48f4d81517afcc4cd35237b15ce1d4a4632896ec67e","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","D0D0D0","DEF","MIT","PUSH"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\tbody { background-color: #D0D0D0; }\n</style>\n\n<ui-component name=\"console2\" path=\"visible\" config=\"datasource:logs\"></ui-component>\n\n<script>\n\n\tvar visible = true;\n\tvar logs = [];\n\n\tlogs.push({ type: 'info', body: 'Lorem <b>ipsum</b> dolor sit amet, consectetur adipisicing elit. Soluta, ea.' });\n\tlogs.push({ type: 'error', body: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Soluta, ea.' });\n\n\t// 2 seconds delay\n\tsetTimeout(() => PUSH('logs', { type: 'warning', body: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Hic, tempora.' }), 2000);\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/contenteditable","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/contenteditable/","markdown_url":"https://forge.totaljs.es/en/components/contenteditable.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"117b2c2b4741cdf3489a5a0109a8c43b7fab57e9e1cea3ffd02c13b7ad66e388","checked_at":"2026-10-20T12:20:24.754Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-ContentEditable","description":"j-ContentEditable: easy usage; supports great features","messages":[{"role":"user","content":"j-ContentEditable\n\nj-ContentEditable: easy usage; supports great features"},{"role":"assistant","content":"- supports great features\n- works with Bootstrap Grid System\n- easy for customization\n- supports basic shortcuts `CMD+B` __bold__, `CMD+U` __underline__, `CMD+I` __italic__ and `CMD+L` __creates a link__\n\n__Attributes__:\n- `data-required=\"true\"` optional, default `false`\n- `data-clipboard=\"text/plain\"` optional, default `text/plain` (for support of `html` use `text/html`)\n\n__Methods__:\n- `component.getNode()` returns a select `node` in text `{Object}`\n- `component.getSelection()` returns a current selection `{String}`\n- `component.insert(value, [encode])` inserts a custom HTML or PLAIN text to the current cursor position, `encode` (optional) is by default `false`\n- `component.exec('Bold', false, null)` has same functionality as `document.execCommand()`\n\n__Handlers__:\n`component.event(type, value)` handles all events from the editor.\n\n```html\ntype = bold          - when a text is bolded (value is boolean)\ntype = italic        - when a text is italic (value is boolean)\ntype = underline     - when a text is underlined (value is boolean)\ntype = link          - when a link is created (value is a temporary URL)\ntype = current       - when a current element is changed in the text (value is NODE)\ntype = paste         - when the clipboard is used (value is a clipboard value)\ntype = select        - when a text is selected (value is selected text)\ntype = focus         - editor is focused (value is undefined)\ntype = blur          - editor is not focused (value is undefined)\ntype = click         - click on the specific element in the text (value is NODE)\n```\n\n__Default declaration__:\n\n```javascript\nself.event = function(type, value) {\n    if (type === 'paste')\n        self.insert(value, true);\n};\n```\n\nUse it with `<ui-component name=\"contenteditable\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div class=\"container\">\n\t<div class=\"row\">\n\t\t<div class=\"col-md-6\">\n\t\t\t<ui-component name=\"contenteditable\" path=\"myhtml\"></ui-component>\n\t\t</div>\n\t</div>\n</div>\n```"}],"knowledge":{"content_sha256":"5e4a42aa60e661bcabf702e258a5acf50ba893ddf2e673b6914984750f02662d","source_sha256":"117b2c2b4741cdf3489a5a0109a8c43b7fab57e9e1cea3ffd02c13b7ad66e388","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","CMD","DEF","HTML","MIT","NODE","PLAIN","URL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div class=\"container\">\n\t<div class=\"row\">\n\t\t<div class=\"col-md-6\">\n\t\t\t<ui-component name=\"contenteditable\" path=\"myhtml\"></ui-component>\n\t\t</div>\n\t</div>\n</div>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/contentslider","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/contentslider/","markdown_url":"https://forge.totaljs.es/en/components/contentslider.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"cd4e640a0ba93c0e99a71064b40d12bc9ca487abde7d88945826c3cf63331b13","checked_at":"2026-10-20T12:20:25.639Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-ContentSlider","description":"The code below solves a problem with rendering component when the the component is dynamically displayed.","messages":[{"role":"user","content":"j-ContentSlider\n\nThe code below solves a problem with rendering component when the the component is dynamically displayed."},{"role":"assistant","content":"- supports great features\n- works with Bootstrap Grid System\n\n\n__Configuration__:\n\n- `selector {String}` Optional, default `a`\n- `interval {Number}` Optional, refresh interval (default `3000`)\n- `cache {String}` Optional, when it's filled then the component saves a current state into the `localStorage` - so when the user refreshes the page then the component continues from the last state\n\n__Redraw__:\nThe code below solves a problem with rendering component when the the component is dynamically displayed.\n\n```javascript\nSETTER('contentslider/refresh');\n```\n\nUse it with `<ui-component name=\"contentslider\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div class=\"row\">\n\t<div class=\"col-md-4 col-md-offset-4\">\n\t\t<ui-component name=\"contentslider\" config=\"cache:id-for-caching-latest-state\" class=\"hidden\">\n\t\t\t<a href=\"javascript:void(0)\" style=\"background-color:red;height:50px;display:block;color:white\">A</a>\n\t\t\t<a href=\"javascript:void(0)\" style=\"background-color:blue;height:50px;display:block;color:white\">B</a>\n\t\t\t<a href=\"javascript:void(0)\" style=\"background-color:green;height:50px;display:block;color:white\">C</a>\n\t\t\t<a href=\"javascript:void(0)\" style=\"background-color:gray;height:50px;display:block;color:white\">D</a>\n\t\t\t<a href=\"javascript:void(0)\" style=\"background-color:black;height:50px;display:block;color:white\">E</a>\n\t\t</ui-component>\n\t</div>\n</div>\n```"}],"knowledge":{"content_sha256":"785dc8ee948c35edfb6082748a6f3a757d61b310fdf471a712f7acd9468825d6","source_sha256":"cd4e640a0ba93c0e99a71064b40d12bc9ca487abde7d88945826c3cf63331b13","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","SETTER"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div class=\"row\">\n\t<div class=\"col-md-4 col-md-offset-4\">\n\t\t<ui-component name=\"contentslider\" config=\"cache:id-for-caching-latest-state\" class=\"hidden\">\n\t\t\t<a href=\"javascript:void(0)\" style=\"background-color:red;height:50px;display:block;color:white\">A</a>\n\t\t\t<a href=\"javascript:void(0)\" style=\"background-color:blue;height:50px;display:block;color:white\">B</a>\n\t\t\t<a href=\"javascript:void(0)\" style=\"background-color:green;height:50px;display:block;color:white\">C</a>\n\t\t\t<a href=\"javascript:void(0)\" style=\"background-color:gray;height:50px;display:block;color:white\">D</a>\n\t\t\t<a href=\"javascript:void(0)\" style=\"background-color:black;height:50px;display:block;color:white\">E</a>\n\t\t</ui-component>\n\t</div>\n</div>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/contextmenu","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/contextmenu/","markdown_url":"https://forge.totaljs.es/en/components/contextmenu.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"08c1222a84dd444329525b378c1f024c6ef5f74a6ba184343909d9ae71708548","checked_at":"2026-10-20T12:20:26.380Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-ContextMenu","description":"If the value isn't defined then the component uses name as value.","messages":[{"role":"user","content":"j-ContextMenu\n\nIf the value isn't defined then the component uses name as value."},{"role":"assistant","content":"- supports 3 orientations `left`, `center` and `right`\n- singleton\n- works with Bootstrap Grid System\n- works with touches\n\n\n__Methods__:\n\nMethod: `component.show(orientation, targetElement, [items], clickCallback, [offsetX], [offsetY])`\n- `orientation {String}` can be `left`, `center` or `right`\n- `targetElement {Selector/jQuery/Element}` a target where the component will be visible\n- `items {Array}` optional and must have this structure: `[{ name: String, value: String, icon: String }]` otherwise will be items read from the target's `data-options` attribute.\n- `callback(selectedValue) {Function}` is triggered when the user clicks on the item\n- `offsetX {Number}` optional can change offset X\n- `offsetY {Number}` optional can change offset Y\n\nMethod: `component.hide()`\n\n__Attributes__:\n`targetElement` can contain `data-options=\"Name 1|fa-building|Value 1;Name 2|fa-github|Value 2;\"` attribut with all items of the context menu.\n\nIf the `value` isn't defined then the component uses `name` as `value`.\n\n__Global events__:\n\n```javascript\nON('contextmenu', function(visible, component, target) {\n    console.log('ContextMenu is', visible ? 'visible' : 'hidden');\n});\n```\n\nUse it with `<ui-component name=\"contextmenu\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\tbody { background-color: #F0F0F0; }\n</style>\n\n<ui-component name=\"contextmenu\"></ui-component>\n\n<div class=\"container\">\n\t<a href=\"javascript:void(0)\">Edit</a>\n</div>\n\n<script>\n\t$(document).on('click', 'a', function() {\n\t\tSETTER('contextmenu/show', 'left', this, [{ name: 'Print', icon: 'ti-print', value: 'print' }, { name: 'Copy', icon: 'ti-copy', value: 'copy' }, { name: 'Remove', icon: 'ti-times-circle', value: 'remove' }], function(item) {\n\t\t\tconsole.log('CLICK', item);\n\t\t});\n\t});\n</script>\n```"}],"knowledge":{"content_sha256":"955e3a56eee2f17f84285a1096e6dee94784e5884f6771a37ccc016f7c95079b","source_sha256":"08c1222a84dd444329525b378c1f024c6ef5f74a6ba184343909d9ae71708548","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","CLICK","DEF","F0F0F0","MIT","SETTER"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\tbody { background-color: #F0F0F0; }\n</style>\n\n<ui-component name=\"contextmenu\"></ui-component>\n\n<div class=\"container\">\n\t<a href=\"javascript:void(0)\">Edit</a>\n</div>\n\n<script>\n\t$(document).on('click', 'a', function() {\n\t\tSETTER('contextmenu/show', 'left', this, [{ name: 'Print', icon: 'ti-print', value: 'print' }, { name: 'Copy', icon: 'ti-copy', value: 'copy' }, { name: 'Remove', icon: 'ti-times-circle', value: 'remove' }], function(item) {\n\t\t\tconsole.log('CLICK', item);\n\t\t});\n\t});\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/controls","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/controls/","markdown_url":"https://forge.totaljs.es/en/components/controls.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"31611423f9821f76e03fe75baf6102129077cc525a83b0bed2ff52d67be8bfca","checked_at":"2026-10-20T12:20:27.125Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Controls","description":"j-Controls: singleton","messages":[{"role":"user","content":"j-Controls\n\nj-Controls: singleton"},{"role":"assistant","content":"- singleton\n\n## Usage\n\n```javascript\nvar opt = {};\n\n// opt.offsetX {Number}    : adds X offset (default: 0)\n// opt.offsetY {Number}    : adds Y offset (default: 0)\n// opt.align {String}      : align `left` (default), `center` or `right`\n// opt.position {String}   : position `top` (default) or `bottom`\n// opt.hide {Function}     : NEW: is executed when the menu is hidden\n// opt.classname {String}  : NEW: adds class to the parent\n// opt.large {Boolean}     : NEW: it'll make menu bigger\n\nopt.element = YOUR_ELEMENT;\n// or if you want to use a fixed position:\n// opt.x {Number}          : `x` position\n// opt.y {Number}          : `y` position\n\nopt.items = ['Label', { name: 'Item 1', icon: 'print', classname: 'custom_class_name' }, { name: 'Item 2', icon: 'home', title: 'A tooltip' }, '-', { name: 'Item 3', icon: 'far fa-envelope', children: [{ name: 'Submenu item 1', icon: 'cog' }, { name: 'Submenu item 2', icon: 'home' }] }, { name: 'Selected', icon: 'check-circle', selected: true }, { name: 'Item 4', icon: 'gear', disabled: true }];\n// Item properties\n// name {String}           : item name\n// icon {String}           : icon\n// classname {String}      : class to be added to li element\n// title {String}          : tooltip\n// selected {Boolean}      : item will be highlighted\n// disabled {Boolean}      : item will be disabled(not clickable)\n// children {Array}        : array of subitems\n\nopt.callback = function(item) {\n\tconsole.log(item);\n};\n\nSETTER('controls/show', opt);\n```\n\nUse it with `<ui-component name=\"controls\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\tbody { background-color: #E0E0E0; }\n</style>\n\n<ui-component name=\"controls\"></ui-component>\n\n<div class=\"container\">\n\t<div class=\"row\">\n\t\t<div class=\"col-xs-4 col-xs-offset-4\" style=\"text-align:center\">\n\t\t\t<a href=\"javascript:void(0)\" style=\"color:black;font-size:20px\"><i class=\"ti ti-ellipsis-v\"></i></a>\n\t\t</div>\n\t</div>\n</div>\n\n<script>\n\t$(document).on('click', 'a', function() {\n\n\t\tvar opt = {};\n\t\topt.items = ['Label', { icon: 'print', classname: 'custom_class_name' }, { icon: 'home', disabled: true, title: 'Home' }, '-', { icon: '!ti ti-envelope', children: [{ name: 'Submenu item 1', icon: 'cog' }, { name: 'Submenu item 2', icon: 'home' }] }, { name: 'Selected', icon: 'check-circle', selected: true }];\n\t\topt.align = 'center';\n\t\topt.element = this;\n\t\t// opt.large = true;\n\t\topt.callback = function(selected) {\n\t\t\tconsole.log(selected);\n\t\t};\n\t\tSETTER('controls/show', opt);\n\t});\n\n</script>\n```"}],"knowledge":{"content_sha256":"10418a41b16f48c256974faaab6a5b8315c0e495deafdf6d3ba2ee79a12f5933","source_sha256":"31611423f9821f76e03fe75baf6102129077cc525a83b0bed2ff52d67be8bfca","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","E0E0E0","MIT","NEW","SETTER","YOUR_ELEMENT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\tbody { background-color: #E0E0E0; }\n</style>\n\n<ui-component name=\"controls\"></ui-component>\n\n<div class=\"container\">\n\t<div class=\"row\">\n\t\t<div class=\"col-xs-4 col-xs-offset-4\" style=\"text-align:center\">\n\t\t\t<a href=\"javascript:void(0)\" style=\"color:black;font-size:20px\"><i class=\"ti ti-ellipsis-v\"></i></a>\n\t\t</div>\n\t</div>\n</div>\n\n<script>\n\t$(document).on('click', 'a', function() {\n\n\t\tvar opt = {};\n\t\topt.items = ['Label', { icon: 'print', classname: 'custom_class_name' }, { icon: 'home', disabled: true, title: 'Home' }, '-', { icon: '!ti ti-envelope', children: [{ name: 'Submenu item 1', icon: 'cog' }, { name: 'Submenu item 2', icon: 'home' }] }, { name: 'Selected', icon: 'check-circle', selected: true }];\n\t\topt.align = 'center';\n\t\topt.element = this;\n\t\t// opt.large = true;\n\t\topt.callback = function(selected) {\n\t\t\tconsole.log(selected);\n\t\t};\n\t\tSETTER('controls/show', opt);\n\t});\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/cookie","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/cookie/","markdown_url":"https://forge.totaljs.es/en/components/cookie.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"c4d93484225e9433de58030a5d2f9ed66a5a8ffa36918f0e6182b85b85b27ecd","checked_at":"2026-10-20T12:20:27.862Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Cookie","description":"j-Cookie: singleton; uses localStorage","messages":[{"role":"user","content":"j-Cookie\n\nj-Cookie: singleton; uses localStorage"},{"role":"assistant","content":"- singleton\n- uses __localStorage__\n- supports `cancel` operation (removes cookies, localStorage and redirects to `about:blank`)\n\n__Configuration__\n\n- `agree` {String} a label for the agree button (default: `OK`)\n- `cancel` {String} a label for the cancel button (default: `Cancel`)\n- `redirect` {String} URL to redirect if the user pressed cancel button (default: `about:blank`)\n\nUse it with `<ui-component name=\"cookie\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"cookie\" class=\"hidden\">This site uses cookies for visitor statistics. By continuing to browse the site you are agreeing to our use of cookies. <a href=\"/privacy/\">Privacy Policy</a></ui-component>\n\n<div>CONTENT</div>\n<div>CONTENT</div>\n<div>CONTENT</div>\n<div>CONTENT</div>\n<div>CONTENT</div>\n<div>CONTENT</div>\n<div>CONTENT</div>\n```"}],"knowledge":{"content_sha256":"4772d6641378900c435c00f73ecb7e40579c40381bbfda031b231289f9f71b33","source_sha256":"c4d93484225e9433de58030a5d2f9ed66a5a8ffa36918f0e6182b85b85b27ecd","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","CONTENT","DEF","MIT","URL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"cookie\" class=\"hidden\">This site uses cookies for visitor statistics. By continuing to browse the site you are agreeing to our use of cookies. <a href=\"/privacy/\">Privacy Policy</a></ui-component>\n\n<div>CONTENT</div>\n<div>CONTENT</div>\n<div>CONTENT</div>\n<div>CONTENT</div>\n<div>CONTENT</div>\n<div>CONTENT</div>\n<div>CONTENT</div>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/cookie2","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/cookie2/","markdown_url":"https://forge.totaljs.es/en/components/cookie2.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"e030cbdae143d61969d459d21c8546cb123c5169b4bd370f148f6ddaa15636ea","checked_at":"2026-10-20T12:20:28.606Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Cookie2","description":"j-Cookie2: singleton; uses localStorage","messages":[{"role":"user","content":"j-Cookie2\n\nj-Cookie2: singleton; uses localStorage"},{"role":"assistant","content":"- singleton\n- uses __localStorage__\n- supports `cancel` operation (removes cookies, localStorage and redirects to `about:blank`)\n\n__Configuration__\n\n- `agree` {String} a label for the agree button (default: `OK`)\n- `cancel` {String} a label for the cancel button (default: `Cancel`)\n- `redirect` {String} URL to redirect if the user pressed cancel button (default: `about:blank`)\n\nUse it with `<ui-component name=\"cookie2\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"cookie2\" class=\"hidden\">This site uses cookies for visitor statistics. By continuing to browse the site you are agreeing to our use of cookies. <a href=\"/privacy/\">Privacy Policy</a></ui-component>\n\n<div>CONTENT</div>\n<div>CONTENT</div>\n<div>CONTENT</div>\n<div>CONTENT</div>\n<div>CONTENT</div>\n<div>CONTENT</div>\n<div>CONTENT</div>\n```"}],"knowledge":{"content_sha256":"70707a85b6c0394c0ff53589f3b16c7d97ed3eb30255a781d463deac355ebaba","source_sha256":"e030cbdae143d61969d459d21c8546cb123c5169b4bd370f148f6ddaa15636ea","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","CONTENT","DEF","MIT","URL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"cookie2\" class=\"hidden\">This site uses cookies for visitor statistics. By continuing to browse the site you are agreeing to our use of cookies. <a href=\"/privacy/\">Privacy Policy</a></ui-component>\n\n<div>CONTENT</div>\n<div>CONTENT</div>\n<div>CONTENT</div>\n<div>CONTENT</div>\n<div>CONTENT</div>\n<div>CONTENT</div>\n<div>CONTENT</div>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/countdown","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/countdown/","markdown_url":"https://forge.totaljs.es/en/components/countdown.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"4c4b5800dda667bce6a5bee4cb26c568bbc626eb0e8ae218b4e7d9442ed8192c","checked_at":"2026-10-20T12:20:29.495Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-CountDown","description":"The component can show an action when time is expired.","messages":[{"role":"user","content":"j-CountDown\n\nThe component can show an action when time is expired."},{"role":"assistant","content":"The component can show an action when time is expired.\n\n\n__Configuration__\n\n- `text {String}` optional, a text for countdown\n\nUse it with `<ui-component name=\"countdown\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Tomáš Novák. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"countdown\" path=\"expiredate\" class=\"invisible\">\n\t<script type=\"text/html\">You can send SMS again: {0}</script>\n\t<a href=\"#\">Send SMS again</a>\n</ui-component>\n\n<ui-component name=\"countdown\" path=\"expiredate\" config=\"text:You can send SMS again\\: {0}\" class=\"invisible\">\n\t<a href=\"#\">Send SMS again</a>\n</ui-component>\n\n<script>\n\tvar expiredate = NOW.add('10 seconds');\n</script>\n```"}],"knowledge":{"content_sha256":"e1ca9c20dcd2485cdddec71d4cd11a66fc5a804aa2bef5a3a51021bc725373ad","source_sha256":"4c4b5800dda667bce6a5bee4cb26c568bbc626eb0e8ae218b4e7d9442ed8192c","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","NOW","SMS"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"countdown\" path=\"expiredate\" class=\"invisible\">\n\t<script type=\"text/html\">You can send SMS again: {0}</script>\n\t<a href=\"#\">Send SMS again</a>\n</ui-component>\n\n<ui-component name=\"countdown\" path=\"expiredate\" config=\"text:You can send SMS again\\: {0}\" class=\"invisible\">\n\t<a href=\"#\">Send SMS again</a>\n</ui-component>\n\n<script>\n\tvar expiredate = NOW.add('10 seconds');\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/crop","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/crop/","markdown_url":"https://forge.totaljs.es/en/components/crop.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"3e2b219161fb21e58cb5ad44320543593db99833bcf2e4f205955efce2f31c5d","checked_at":"2026-10-20T12:20:30.395Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Crop","description":"j-Crop: zoom; drag and drop","messages":[{"role":"user","content":"j-Crop\n\nj-Crop: zoom; drag and drop"},{"role":"assistant","content":"__Supports__:\n\n- zoom\n- drag and drop\n- local files\n- responsive\n- automatically rotates image according to the EXIF information\n\n__Configuration__:\n\n- `width {Number}` target width, required\n- `height {Number}` target height, required\n- `dragdrop {Boolean}` enables drag&drop (default: `true`)\n- `background {String}` a background color (default: transparent)\n- `format {String}` a format for URL file generator (default: `{0}`)\n\n__Attributes__\n- `path=\"path-with-url-to-image\"` - must contain URL to picture\n\n__Methods__:\n\n```js\n// e.g.\nvar component = FIND('croper');\n\n// Resize canvas\ncomponent.reconfigure('width:500;height:200');\n\n// Get cropped image\n// component.output([type], [return_object])\nvar png = component.output('image/png');\nvar jpg = component.output('image/jpeg');\n\n// Below method automatically detects the transparency and sets the output\nvar data = component.output();\n\n// var data = component.output(true);\n// data.filename {String}\n// data.data {String} base64\n\n// New method for clearing of canvas\ncomponent.clear();\n```\n\nUse it with `<ui-component name=\"crop\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"width:500px\">\n\t<ui-component name=\"crop\" path=\"model.picture\" config=\"width:200;height:200\"></ui-component>\n</div>\n\n<script>\n\tvar model = {};\n\tmodel.picture = 'https://avatars0.githubusercontent.com/u/2414252?v=3&s=460';\n</script>\n```"}],"knowledge":{"content_sha256":"1ecbea80c498d4266f39259a180aa38ff9562bd83bbf9035ff30e0b1aa732222","source_sha256":"3e2b219161fb21e58cb5ad44320543593db99833bcf2e4f205955efce2f31c5d","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","EXIF","FIND","MIT","URL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"width:500px\">\n\t<ui-component name=\"crop\" path=\"model.picture\" config=\"width:200;height:200\"></ui-component>\n</div>\n\n<script>\n\tvar model = {};\n\tmodel.picture = 'https://avatars0.githubusercontent.com/u/2414252?v=3&s=460';\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/dashboard","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/dashboard/","markdown_url":"https://forge.totaljs.es/en/components/dashboard.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"3f9e6c1f8db0b9d82f8db9b141a04b01f19d264564a89f0969ea682fadaeb001","checked_at":"2026-10-20T12:20:31.284Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Dashboard","description":"The component expects Array of objects.","messages":[{"role":"user","content":"j-Dashboard\n\nThe component expects Array of objects."},{"role":"assistant","content":"- Supports mobile devices\n- Supports touches\n- Movable\n- Draggable\n- Resizable\n\nThe component expects `Array` of objects.\n\n__Configuration__:\n\n- `axisX {Number}` a size of grid of X axis (default: `12`)\n- `axisY {Number}` a size of grid of Y axis (default: `144`)\n- `padding {Number}` a padding between widgets (default: `10`)\n- `delay {Number}` init delay (default: `200`)\n- `serviceinterval {Number}` a service interval (default: `5000`)\n- `ondrop {String}` a path to method `function(meta) {}`, is executed if someone drops some element into area\n\t- `meta.x` X grid position\n\t- `meta.y` Y grid position\n\t- `meta.el` dragged element\n\t- `meta.pageX`\n\t- `meta.pageY`\n\t- `meta.offsetX`\n\t- `meta.offsetY`\n\t- `meta.target`\n\t- `meta.d` a display mode `xs`, `sm`, `md` or `lg`\n- `noemitresize` disables executing of `resize` method in all nested component (default: `false`)\n- `parent {String}` a parent container which sets a minimal height (default: `null`)\n- `margin {Number}` a top/bottom margin for the `parent` option (default: `0`)\n- `minsizexs {Number}` a minimal column/row size for extra small displays (default: `2`)\n- `minsizesm {Number}` a minimal column/row size for small displays (default: `2`)\n- `minsizemd {Number}` a minimal column/row size for mediun displays (default: `1`)\n- `minsizelg {Number}` a minimal column/row size for large displays (default: `1`)\n- `iconsettings {String}` settings icon\n- `iconremove {String}` remove icon\n- `change {String}` a path to the method `function(type, id)` (executed if the dashboard captures a change `move` or `remove`)\n- `animation {Number}` style of animation:\n\t- `1` opacity\n\t- `2` scale from big to normal\n\t- `3` scale from small to normal (__default__)\n\t- `4` rotation Z\n\t- `5` changing of X position\n\t- `6` changing of Y position\n- `grid {Boolean}` renders background grid (default: `false`)\n- __NEW__: `reanimate {Boolean}` enables animation for changing widgets (default: `false`)\n\n```js\n// Component declaration\nvar component = {\n\n\t// Component ID\n\tid: 'COMPONENT_ID',\n\n\t// Positioning\n\t// You can define specific display size and if the display size is not specified then the component tries to find a size for larger display\n\toffset: {\n\t\txs: { x: 1, y: 1, width: 6, height: 6 }, // Number means the position in the grid, so e.g. width \"2\" takes \"2\" columns in X axis\n\t\tlg: { x: 1, y: 1, width: 6, height: 6 }\n\t},\n\n\thtml: 'THE CONTENT OF COMPONENT', // or can be raw HTMLElement\n\n\t// Default settings\n\tactions: { move: true, remove: true, resize: true, resizeX: true, resizeY: true, resizeP: false, settings: true },\n\t// resizeP means: resize \"proportionally\"\n\n\t// Can disable header (default: true)\n\theader: true,\n\n\t// Can delay displaying (in \"ms\", default: config.delay)\n\tdelay: 0,\n\n\t// A component title\n\ttitle: 'Title for the component',\n\n\t// Custom properties which will be assigned to the instance\n\ttemplate: Object,\n\tconfig: Object,\n\t// reset: true, // NEW: reloads the component again\n\t// class: String // adds a class to the item\n\n\tmake: function(exports, config, el) {\n\n\t\t// @exports {Object}\n\t\t// @config {Object}\n\t\t// @el {jQuery} a content of the component\n\n\t\t// Handlers (optional):\n\t\texports.destroy = function() {\n\t\t\t// @el {jQuery} a content of the component\n\t\t\t// @this {Instance}\n\n\t\t\t// is executed when the component is destroyed\n\t\t};\n\n\t\texports.data = function(data, el) {\n\t\t\t// @data {Object}\n\t\t\t// @this {Instance}\n\t\t\t// @el {jQuery} a content element of the component\n\t\t\t// It can be executed via SETTER('dashboard/send', ....)\n\t\t};\n\n\t\texports.service = function(counter, el) {\n\t\t\t// @counter {Number} count of calls\n\t\t\t// @el {jQuery} a content of the component\n\t\t\t// @this {Instance}\n\t\t\t// dashboard will execute this handler each 5 seconds\n\t\t};\n\n\t\texports.focus = function() {\n\t\t\t// Optional, when the \"window\" is focused the delegate will be executed\n\t\t};\n\n\t\texports.resize = function(width, height, el, display) {\n\t\t\t// @width {Number}\n\t\t\t// @height {Number}\n\t\t\t// @el {jQuery} a content of the component\n\t\t\t// @display {String} a current display size \"xs\", \"sm\", \"md\" or \"lg\"\n\t\t\t// @this {Instance}\n\t\t\t// is executed when the component is resized\n\t\t\t// @this {Instance}\n\t\t};\n\n\t\texports.settings = function(config, el, elbtn) {\n\t\t\t// @config {Object}\n\t\t\t// @el {jQuery} a content element of the component\n\t\t\t// @elbtn {jQuery} button element\n\t\t\t// is executed when the users clicks on the settings icon\n\t\t};\n\n\t\texports.remove = function(remove) {\n\t\t\t// remove {Function} removes that widget\n\t\t\tremove();\n\t\t};\n\n\t\texports.configure = function() {\n\t\t\t// is executed when the configuration is changed\n\t\t};\n\n\t\t// this.meta {Object} a reference to component object\n\t\t// this.element {jQuery} a content element of the component\n\t\t// this.container {jQuery} a main content element of the component\n\t\t// this.main {Object} a reference to Dashboard component\n\t\t// this.width {Number} a width of \"this.element\"\n\t\t// this.height {Number} a height of \"this.element\"\n\t\t// this.display {String} a current display size \"xs\", \"sm\", \"md\" or \"lg\"\n\n\t\t// is executed when the component is making\n\t}\n};\n\nPUSH('components', component);\n```\n\n__Methods__:\n\n- `SETTER('dashboard/send', id, body)` sends data to all widgets which contain `data` delegate\n\t- `id {String}`\n\t\t- `null` sends to all components\n\t\t- `@component` sends to the specific components according to the component name\n\t\t- `string` sends to a specific component according to the identifier\n- `SETTER('dashboard/call', method_name, [a], [b], [c], [d])` executes a method in each widget\n- `RETURN('dashboard/pixel')` returns a size of the pixel (square)\n- `RETURN('dashboard/display')` returns a current display width\n\n__Good to know__:\n\n- component adds classes to component's body:\n\t- `d_colNUMBER` determins count of taken columns e.g. `d_col2`\n\t- `d_rowNUMBER` determins count of taken rows e.g. `d_row3`\n\t- `d_square` the component has same width/height\n\t- `d_vertical` the component has only 1 step of width and more than 1 steps of height\n\t- `d_horizontal` the component has only 1 step of height and more than 1 steps of width\n\nUse it with `<ui-component name=\"dashboard\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"dashboard\" path=\"components\"></ui-component>\n<script>\n\n\tvar components = [];\n\n\tcomponents.push({\n\t\tid: 'A',\n\t\toffset: { lg: { x: 0, y: 0, width: 4, height: 4 }},\n\t\ttitle: 'A',\n\t\thtml: '<div class=\"padding\">Lorem ipsum dolor sit amet.</div>',\n\t\tactions: { settings: true, remove: true, move: true, resize: true },\n\t\tmake: function(exports, config, el) {\n\n\t\t\t// If you change some action \"exports.actions\" then execute:\n\t\t\t// exports.refresh();\n\n\t\t\texports.data = function(data) {\n\n\t\t\t};\n\n\t\t\texports.configure = function() {\n\n\t\t\t};\n\n\t\t}\n\t});\n\n\tcomponents.push({\n\t\tid: 'B',\n\t\toffset: { lg: { x: 4, y: 0, width: 4, height: 4 }},\n\t\ttitle: 'B',\n\t\thtml: '<div class=\"padding\">Lorem ipsum dolor sit amet.</div>',\n\t\tactions: { settings: true, remove: true, move: true, resize: true }\n\t});\n</script>\n```"}],"knowledge":{"content_sha256":"e0a4c8735d0c2c4a3c58a78273cbd1deb6bf6c13290d4a4175bdf11b1f294da5","source_sha256":"3f9e6c1f8db0b9d82f8db9b141a04b01f19d264564a89f0969ea682fadaeb001","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","COMPONENT","COMPONENT_ID","CONTENT","DEF","HTMLE","MIT","NEW","PUSH","RETURN","SETTER","THE"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"dashboard\" path=\"components\"></ui-component>\n<script>\n\n\tvar components = [];\n\n\tcomponents.push({\n\t\tid: 'A',\n\t\toffset: { lg: { x: 0, y: 0, width: 4, height: 4 }},\n\t\ttitle: 'A',\n\t\thtml: '<div class=\"padding\">Lorem ipsum dolor sit amet.</div>',\n\t\tactions: { settings: true, remove: true, move: true, resize: true },\n\t\tmake: function(exports, config, el) {\n\n\t\t\t// If you change some action \"exports.actions\" then execute:\n\t\t\t// exports.refresh();\n\n\t\t\texports.data = function(data) {\n\n\t\t\t};\n\n\t\t\texports.configure = function() {\n\n\t\t\t};\n\n\t\t}\n\t});\n\n\tcomponents.push({\n\t\tid: 'B',\n\t\toffset: { lg: { x: 4, y: 0, width: 4, height: 4 }},\n\t\ttitle: 'B',\n\t\thtml: '<div class=\"padding\">Lorem ipsum dolor sit amet.</div>',\n\t\tactions: { settings: true, remove: true, move: true, resize: true }\n\t});\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/datagrid","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/datagrid/","markdown_url":"https://forge.totaljs.es/en/components/datagrid.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"6a746d9a04ab43aec9b9fab67aaebb0d5fb196d6f53da55520a533d1e567ac62","checked_at":"2026-10-20T12:20:32.167Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-DataGrid","description":"This grid was created for single page applications and enterprise applications.","messages":[{"role":"user","content":"j-DataGrid\n\nThis grid was created for single page applications and enterprise applications."},{"role":"assistant","content":"This grid was created for single page applications and enterprise applications.\n\n- needed `bootstrap` grid system\n- you can render more than 30 000 rows without any problem\n- supports drag&drop columns\n- supports sorting\n- supports resizing of columns\n- supports vertical/horizontal scrolling\n- supports filters\n- supports checkboxes\n- custom scrollbars independent on OS\n- responsive\n- supports __dark mode__\n- __IMPORTANT__ column options filter needs `j-Directory` component\n\n__TO-DO__:\n\n- multisort\n- resizing columns for touch devices\n- re-ordering columns for touch devices\n\n__Data-source__ needs to contain:\n\n```js\n{\n\tschema: 'schema_id', // NEW: optional, DataGrid supports multiple schemas (it means: multiple declaration of columns)\n    items: [{ name: 'Row 1' }, { name: 'Row 2' }, ...] // items\n    page: 1,    // current pages\n    pages: 30,  // count of pages\n    limit: 5,   // items limit per page\n    count: 150  // count of items in DB\n}\n```\n\nor raw `Array` but you can't use pagination and external filters:\n\n```js\n[{ name: 'Row 1' }, { name: 'Row 2' }];\n```\n\n__Configuration__:\n\n- `filterlabel {String}` a default placeholder for all filters (optional)\n- `rememberfilter {Boolean}` with true it remembers filters for every schema (default: `true`)\n- `schema {String}` a default schema (default: `default`)\n- `pluralizepages {String}` pluralization for pages (optional, default: `# pages,# page,# pages,# pages`)\n- `pluralizeitems {String}` pluralization for items (optional, default: `# items,# item,# items,# items`)\n- `height {Number/String}` height of grid, supported values: `auto` (default), __NEW__ `fluid`, `parent` (or `parent1`, `parent2`) or `NUMBER` as height or `selector` for jQuery.closest()\n- `minheight {Number}` minimal height of grid, works with `fluid` and `auto` height (default: `200`)\n- `parent {String}` selector for `auto` height option (default: `window`)\n- `margin {Number}` a top margin for height (optional, default: `0`)\n- `boolean {String}` a values for filtering of boolean values (optional, default: `true|on|yes`)\n- `resize {Boolean}` enables resizing of columns (optional, default: `true`)\n- `reorder {Boolean}` enables re-ordering of columns (optional, default: `true`)\n- `sorting {Boolean}` enables sorting (optional, default `true`)\n- `remember {Boolean}` remembers re-ordering and resizing columns (optional, default `true`)\n- `checkbox {Boolean}` enables checkboxes\n- `colwidth {Number}` a default column width in pixels (optional, default `150`)\n- `rowheight {Number}` a default row height in pixels (optional, default: `24`) + it depends on CSS\n- `alignheader {String/Number}` align for header `right` (or `2`) or `center` (or `1`) (optional, default: `left`)\n- `alignfilter {String/Number}` align for filter input `right` (or `2`) or `center` (or `1`) (optional, default: `left`)\n- `align {String/Number}` align for a column value `right` (or `2`) or `center` (or `1`) (optional, default: `left`)\n- `click {String}` a link to a path or `function(row, grid, row_el)`, a record will be assigned/executed if the user clicks on a row\n- `clickid {String}` an identificator/property name for backwards redrawing (for previous row highlight), default: `id`\n- `highlight {Boolean}` each selected row (after `click`) will be highlighted (default: `false`)\n- `unhighlight {Boolean}` enables `undo highlighting` of selected row (default: `true`)\n- `checked {String}` `path` to a variable or path to `function(rows, grid)` is executed if the user selected/checked some rows\n- `autoselect {Boolean}` enables auto-select of first row in grid, it performs `EXEC(config.click)`, default: `false`\n- `limit {Number}` a cluster limit, default: `80`\n- `numbering {Boolean}`, optional default `false`\n- `allowtitles {Boolean}`, enables titles for all row values, optional default `false`\n- `button {String}` a link to `function(btn_name, row, btn_element, event, name)` is executed if the user clicks on a __button__ in the row\n- `exec {String}` a link to `function(type, filter, sort, page)` for server-side operations only (it disables client-side sorting & filtering), supported types: `refresh` or `page`\n- `changed {String}` `path` to a variable or path to `function(rows, grid)` is executed if the user changed some rows\n- `change {String/Boolean}` boolean enables internal editing or string must contain a `path` to a variable or path to `function(meta, next(meta))` is executed if the user double clicks on a column (`meta` can be `null` if the grid is refreshed) + `next(null)` replaces previous content again\n- `columns {String}` a path to definition of `columns {Array}`\n- `dblclick {String}` a link to `function(row, grid, row_el)` method, it's executed if the user double-clicks on a row\n- `noborder {Boolean}` can disable a border around the grid (default: `false`)\n- `clusterize {Boolean}` can disable clustered scrolling (default: `true`)\n- `contextmenu {String}` a link to `function(e, grid)` when the user raises context menu\n- `autoformat {Boolean}` enables auto-format types like `email`, `phone` and `number` (default: `true`)\n- `controls {Boolean}` enables controls defined in `columns` (default: `true`)\n- `hfunc {String}` a link to `function(el)` method, it's executed if the user clicks on the icon added to very first header row. usefull to integrate other component with DataGrid. e.g. j-filter\n- `hfuncicon {String}` hfunc icon e.g. home, cog, etc.\n- `pagination {Boolean}` enables/disables pagination (default: `true`)\n\t- `config.exec` will be executed if the scrollbar is at the end, and you can use as the raw response `array` of rows\n- `ovalue {String}` a default key for obtaining of `value` from `column.options` (default: `id`)\n- `otext {String}` a default key for obtaining of `text` from `column.options` (default: `name`)\n\n__Column properties__:\n\n- `name {String}` a name of field in the row object\n- `text {String}` a column label, text with `.ti ti-home` will render Total icon\n- `title {String}` a column tooltip (optional)\n- `width {Number}` a column width (optional, default `config.colwidth`)\n- `filter {String/Boolean}` a placeholder for the filter or `boolean` can disable filter for this column (optional)\n- `filtervalue {Object}` optional, a preddefined filter value (default: `undefined`)\n- `filtertype {Number/String}`, `1` disables smart searching (default: `0` - it means enabled smart searching)\n- `align {String}` can be `center` or `right` (optional, default: `left`)\n- `template {String}` can be a Tangular template and the model is the entire object of row\n- `sorting {Boolean}` enables sorting (optional, default: `true`)\n- `search {Boolean/String}` `true` will filter a value according to the `template` result or `String` can be a Tangular template which will be used as a value for search\n- `format {String/Number}` can be used for date and numbers (count of decimals) field (optional), e.g. `dd.MM.yyyy`\n- `hide {Boolean}` hides a column\n- `listcolumn {Boolean}` default:true, false for removing column from columns list\n- `hidden {String}` as an arrow function `column => true` --> column will be hidden (e.g. for restricting user)\n- `options {Object Array}` optional, a custom filter for example `[{ text: 'yes', value: true }, { text: 'no', value: false }]` o {String}r link to data-source\n- `otext {String}` optional, a key for `text` field in `options`, default `config.otext`\n- `ovalue {String}` optional, a key for `value` field in `options`, default `config.ovalue`\n- `buttonapply {String}` optional, a label for `Apply` button in columns, default: `Apply`\n- `class {String}` optional, a custom column class name\n- `empty {String/Boolean}` optional, can rewrite empty value with the value defined in empty field (`true` value uses `---` as a default value)\n- `min {Number}` __editable only__ a min. number value\n- `max {Number}` __editable only__ a max. number value\n- `required {Boolean}` __editable only__\n- `dirsearch {String/Boolean}` a placeholder for `j-Directory` search, boolean with `false` will disable search\n- `type {String}` optional, can contain a data-type (`string`, `boolean`, `date`, `number`, `email`, `phone` or `url`) for the field\n- `editable {Boolean}` enables editing\n- `currency {String}` a currency name (must be defined in `DEF.currencies`)\n- `editable {String}` A path to custom editable `function(meta)`, important: `meta.next(new_value)` or `meta.cancel()`\n- `colorize {Boolean}` enables a colorization of the value\n- `monospace {Boolean}` enables monospace font type\n- __NEW__ `raw {Boolean}` enables/disables HTML escaping in column filters (default: `false`)\n- __NEW__ `pair {Boolean}` enables/disables auto value pairing with `options` configuration (default: `false`)\n\n__Row controls (NEW)__:\n\n- can be defined as column type `controls`\n- must defined column `template` (Tangular, `row` is a model for Tangular for each row)\n- look to example\n- button `click` event is captured to `config.button`\n\n__Filtering__:\n\n- `numbers`: (1) `number` searches an exact number, (2) `number - number` is an interval between numbers\n- `strings`: (1) `string` searches an exact string, (2) `string1, string2, string3` is a multiple search criterium\n- `dates`: (1) year `2017` searches all dates in this year, (2) `2017-02-12 - 2017-03-13` <--> `02.12.2017 - 13.03.2017` is an interval between dates, (3) `12-20 - 12-31` <--> `20.12 - 31.12` an interval between dates in the current year ir (4) `2017-01 - 2018-05` <--> `01.2017 - 05.2018` or (5) `-5 days` <--> `-1 week` <--> `-10 years`\n\n__Methods__:\n\n- `component.redraw([reselect_again])` can redraw rows again (only for modifications, if you will remove some row you need to update the entire model)\n- `component.resetfilter()` can reset a filter\n- `component.exportrows(page_from or true from the current page, pages_count, callback(rows, internal_options), [reset_to_page or true for the current page])` can export rows with server-side rendering\n- `component.appendrow(row_object, [scrolldown], [prepend])` appends and render row\n- `component.redrawrow(row_object, [new_object_row])` redraws row or __NEW__: replaces+redraws `row_object` with a new `new_object_row`\n- `component.clear()` clears all changes\n- `component.select(row)` selects `row` must be the same object as in data-source\n- `component.editcolumn(row_index, col_index)` executes `config.change` internally (only for advanced usage)\n- `component.applyfilter(obj)` can apply a custom filter `{ name: 'Peter', age: '20 - 50' }`\n- `component.resetcolumns()` resets columns\n- `component.readfilter()` returns a current filter\n- `component.rebind(schemaname__or__columnsdeclaration)` the method rebinds the schema\n- `component.reload()` executes `config.exec` with the current filter and sorting\n- `component.empty()` empties all rows\n- `component.redrawcolumns()` redraw columns without reset them\n- __NEW__: `component.schema(name)` changes a schema and refreshes data\n\n__Properties__:\n\n- `component.meta` returns internal meta info about filters, columns and rows\n\n__Good to know__:\n\n- `NULL('link.to.data')` evaluates `config.exec`\n- `SET('link.to.data', data, 'noscroll')` --> `noscroll` type disables reseting of scrolling\n\n__How to extend a class of row?__\n\n- look to the `{{ if active }}` line, it extends a class of row by adding `dg-active-class` if the `active` will be valid\n\n```javascript\n[\n\t'{{ if active }} dg-active-class{{ fi }}',\n\t{ name: 'name', text: 'Name', width: 200 },\n\t// other columns ...\n]\n```\n\n__Multiple schemas__:\n\n```html\n<ui-component name=\"datagrid\" path=\"obj\">\n\n\t<script type=\"text/plain\" data-id=\"SCHEMA_A\">\n\t\t[\n\t\t\t{ name: 'name', text: 'Name', width: 200 },\n\t\t\t// ...\n\t\t]\n\t</script>\n\n\t<script type=\"text/plain\" data-id=\"SCHEMA_B\">\n\t\t[\n\t\t\t{ name: 'price', text: 'Price', width: 100 },\n\t\t\t// ...\n\t\t]\n\t</script>\n\n</ui-component>\n\n<script>\n\t// Usage\n\tvar obj = {};\n\tobj.schema = 'SCHEMA_A';\n\tobj.items = [];\n\n\t// Changing of schema\n\tsetTimeout(function() {\n\t\tobj.schema = 'SCHEMA_B';\n\t\tobj.items = [];\n\t\tUPD('obj');\n\t}, 5000);\n</script>\n```\n\nUse it with `<ui-component name=\"datagrid\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"LAZY directory\"></ui-component>\n<ui-component name=\"datagrid\" path=\"items\" config=\"margin:22;button:clicked\">\n\t<script type=\"text/plain\">\n\t\t[\n\t\t\t{ name: 'name', text: 'Name', width: 200 },\n\t\t\t{ name: 'price', text: 'Price', align: 1, alignfilter: 1 },\n\t\t\t{ name: 'published', text: 'Public', align: 1, options: [{ name: 'yes', id: true }, { name: 'no', id: false }], type: 'boolean' },\n\t\t\t{ name: 'description', text: 'Description', filter: 'Search in description' },\n\t\t\t{ name: 'created', text: 'Created', template: '{{ created | format(\\'dd.MM.yyyy\\') }}', align: 2, alignfilter: 2, alignheader: 2 },\n\t\t\t{ type: 'controls', template: '<button name=\"show\"><i class=\"ti ti-search\"></i><span>Show</span></button><button name=\"edit\"><i class=\"ti ti-pencil\"></i></button><button name=\"remove\"><i class=\"ti ti-trash red\"></i></button>' }\n\t\t]\n\t</script>\n</ui-component>\n\n<script>\n\n\tvar items = [];\n\tfor (var i = 1; i < 90; i++)\n\t\titems.push({ name: '{0} Lorem ipsum dolor'.format(i), published: i % 5 !== 0, description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consectetur, mollitia!', price: i + 100, created: new Date().add('-5 days') });\n\n\tfunction clicked(btnname, row, button, e) {\n\t\tconsole.log(btnname, row);\n\t}\n\n</script>\n```"}],"knowledge":{"content_sha256":"a1870ef33788ac6c75a248614700c48166bdcfe6939c2f5a12110ad40316270c","source_sha256":"6a746d9a04ab43aec9b9fab67aaebb0d5fb196d6f53da55520a533d1e567ac62","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","CSS","DEF","DO__","EXEC","HTML","LAZY","MIT","NEW","NULL","NUMBER","SCHEMA_A","SCHEMA_B","SET","UPD"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"LAZY directory\"></ui-component>\n<ui-component name=\"datagrid\" path=\"items\" config=\"margin:22;button:clicked\">\n\t<script type=\"text/plain\">\n\t\t[\n\t\t\t{ name: 'name', text: 'Name', width: 200 },\n\t\t\t{ name: 'price', text: 'Price', align: 1, alignfilter: 1 },\n\t\t\t{ name: 'published', text: 'Public', align: 1, options: [{ name: 'yes', id: true }, { name: 'no', id: false }], type: 'boolean' },\n\t\t\t{ name: 'description', text: 'Description', filter: 'Search in description' },\n\t\t\t{ name: 'created', text: 'Created', template: '{{ created | format(\\'dd.MM.yyyy\\') }}', align: 2, alignfilter: 2, alignheader: 2 },\n\t\t\t{ type: 'controls', template: '<button name=\"show\"><i class=\"ti ti-search\"></i><span>Show</span></button><button name=\"edit\"><i class=\"ti ti-pencil\"></i></button><button name=\"remove\"><i class=\"ti ti-trash red\"></i></button>' }\n\t\t]\n\t</script>\n</ui-component>\n\n<script>\n\n\tvar items = [];\n\tfor (var i = 1; i < 90; i++)\n\t\titems.push({ name: '{0} Lorem ipsum dolor'.format(i), published: i % 5 !== 0, description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consectetur, mollitia!', price: i + 100, created: new Date().add('-5 days') });\n\n\tfunction clicked(btnname, row, button, e) {\n\t\tconsole.log(btnname, row);\n\t}\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/datasource","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/datasource/","markdown_url":"https://forge.totaljs.es/en/components/datasource.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"b9e0d85d0000e2e56b8df49bda4a58ed9714889e754cfb4c5d3e01bbdaa508dd","checked_at":"2026-10-20T12:20:32.902Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-DataSource","description":"The component loads data from the external source via AJAX() method and it binds the value according to the config.path.","messages":[{"role":"user","content":"j-DataSource\n\nThe component loads data from the external source via AJAX() method and it binds the value according to the config.path."},{"role":"assistant","content":"The component loads data from the external source via `AJAX()` method and it binds the value according to the `config.path`.\n\n\n__Configuration__:\n\n- `path {String}` __required__ a path where the value will be set\n- `url {String}` optional, a URL address for obtaining data\n- `delay {Number}` optional, a delay (in milliseconds) for obtaining data (defaul: `0`)\n\n__Good to know__:\n\n- `config.path = '#codelist'` sets a value in code lists `DEF.cl.codelist`\n- `config.path = '%codelist'` sets a value in as temporary\n- `config.path = '?.path` sets a value according to the plugin path\n- `config.path = 'absolute.path` sets a value according to the path\n\n__Can I use dynamic URL address?__\n\nYes, you can use `<ui-component name=\"datasource\" path=\"...\"` for dynamic obtaining of data.\n\n```html\n<ui-component name=\"datasource\" path=\"?.urlcities\" config=\"path:#cities\"></ui-component>\n```\n\n__How can I modify data before binding?__\n\n```html\n<ui-component name=\"datasource\" config=\"url:https://www.yourdomain.com/api/countries/;path:#countries\">\n\t<script type=\"text/plain\">\n\t\tvar output = [];\n\t\tfor (var item of value)\n\t\t\toutput.push({ id: item.Id, name: item.Name });\n\t\treturn output;\n\t</script>\n</ui-component>\n```\n\nUse it with `<ui-component name=\"datasource\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"datasource\" config=\"url:https://www.totaljs.com/dashboard.json;path:#mycodelist\">\n\t<script type=\"text/plain\">\n\t\treturn value.children;\n\t</script>\n</ui-component>\n\n<ui-bind path=\"#mycodelist\" config=\"html pre:JSON.stringify(value, null, '  ')\" class=\"block\">\n\t<pre></pre>\n</ui-bind>\n```"}],"knowledge":{"content_sha256":"9f6c58aecb899aeb714d265b23ace97db8e569e98b0aa09f29f47187b0087efb","source_sha256":"b9e0d85d0000e2e56b8df49bda4a58ed9714889e754cfb4c5d3e01bbdaa508dd","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["AJAX","CDN","DEF","JSON","MIT","URL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"datasource\" config=\"url:https://www.totaljs.com/dashboard.json;path:#mycodelist\">\n\t<script type=\"text/plain\">\n\t\treturn value.children;\n\t</script>\n</ui-component>\n\n<ui-bind path=\"#mycodelist\" config=\"html pre:JSON.stringify(value, null, '  ')\" class=\"block\">\n\t<pre></pre>\n</ui-bind>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/datatable","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/datatable/","markdown_url":"https://forge.totaljs.es/en/components/datatable.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"9fe7752d03a2100cc154ec3c0ade2996f64f4ee09bf584e6199b8c2b766ea3f0","checked_at":"2026-10-20T12:20:33.799Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-DataTable","description":"BETA: This component is targeted for SPA and server-side listing.","messages":[{"role":"user","content":"j-DataTable\n\nBETA: This component is targeted for SPA and server-side listing."},{"role":"assistant","content":"__BETA__: This component is targeted for SPA and server-side listing.\n\n- supports pagination\n- needed `bootstrap` grid system\n- supports sorting\n- supports vertical/horizontal scrolling\n- responsive\n- supports __dark mode__\n\n__Data-source__ needs to contain:\n\n```js\n{\n\tcols: [{ name: 'id', ... }], // optional, can be defined inline in HTML\n    items: [{ name: 'Row 1' }, { name: 'Row 2' }, ...] // items\n    page: 1,    // current pages\n    pages: 30,  // count of pages\n    limit: 5,   // items limit per page\n    count: 150  // count of items in DB\n}\n```\n\n__Configuration__:\n\n- `pluralizepages {String}` pluralization for pages (optional, default: `# pages,# page,# pages,# pages`)\n- `pluralizeitems {String}` pluralization for items (optional, default: `# items,# item,# items,# items`)\n- `height {Number/String}` height of grid, supported values: `auto` (default), __NEW__ `fluid`, `parent` or `NUMBER` as height or `selector` for jQuery.closest()\n- __NEW__ `minheight {Number}` minimal height of grid, works with `fluid` and `auto` height (default: `300`)\n- __NEW__ `parent {String}` selector for `auto` height option (default: `window`)\n- `margin {Number}` a top margin for height (optional, default: `0`)\n- `click {String}` a link to `function(row, grid, row_el)`, it's executed if the user clicks on a row\n- `highlight {Boolean}` each selected row (after `click`) will be highlighted (default: `false`)\n- `unhighlight {Boolean}` enables `undo highlighting` of selected row (default: `true`)\n- `checked {String}` `path` to a variable or path to `function(rows, grid)` is executed if the user selected/checked some rows\n- `autoselect {Boolean}` enables auto-select of first row in grid, it performs `EXEC(config.click)`, default: `false`\n- `exec {String}` a link to `function(type, page, sort)` for server-side operations only (it disables client-side sorting & filtering), supported types: `refresh` or `page`\n- `changed {String}` `path` to a variable or path to `function(rows, grid)` is executed if the user changed some rows\n- `dblclick {String}` a link to `function(row, grid, row_el)` method, it's executed if the user double-clicks on a row\n- `noborder {Boolean}` can disable a border around the grid (default: `false`)\n- `colwidth {Number}` a default width for columns (default: `150`)\n- `editable {String}` a link to `function(opt)` when the user performs editing\n- `contextmenu {String}` a link to `function(e, grid)` when the user raises context menu\n\n__Column properties__:\n\n- `name {String}` a name of field in the row object\n- `text {String}` a column label, text with `.fa fa-home` will render FontAwesome icon\n- `width {Number}` a column width (optional, default `config.colwidth`)\n- `align {String}` can be `center` or `right` (optional, default: `left`)\n- `template {String}` can be a Tangular template and the model is the entire object of row\n- `sort {Boolean}` enables sorting (optional, default: `false`)\n- `format {String/Number}` can be used for date and numbers (count of decimals) field (optional), e.g. `dd.MM.yyyy`\n- `currency {String}` a currency formatter, just add a currency\n- `class {String}` optional, a custom column class name\n- `empty {String/Boolean}` optional, can rewrite empty value with the value defined in empty field (`true` value uses `---` as a default value)\n- `type {String}` optional, can contain a data-type (`boolean`, `date` or `number`) for the field\n- `editable {Boolean}` enables editing\n\n__Methods__:\n\n- `component.redrawrow(row_object/index)` redraws row or __NEW__: replaces+redraws `row_object` with a new `new_object_row`\n- `component.clear()` clears all changes\n- `component.select(row)` selects `row` must be the same object as in data-source\n- `component.rebind(columnsdeclaration)` the method rebinds the schema\n- __NEW__: `component.reload()` executes `config.exec` with the current filter and sorting\n- __NEW__: `component.empty()` empties all rows\n\n__Good to know__:\n\n- `SET('link.to.data', data, 'noscroll')` --> `noscroll` type disables reseting of scrolling\n\n__How to extend a class of row?__\n\n- look to the `{{ if active` line, it extends a class of row by adding `dg-active-class` if the `active` will be valid\n\n```js\n[\n\t'{{ if active }} dg-active-class{{ fi }}',\n\t{ name: 'name', text: 'Name', width: 200 },\n\t// other columns ...\n]\n```\n\nUse it with `<ui-component name=\"datatable\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"width:500px;height:400px\">\n\t<ui-component name=\"datatable\" path=\"data\" config=\"highlight:1;exec:paginate;editable:editable;checked:checked;margin:22\">\n\t\t<script type=\"text/html\">\n\t\t\t[\n\t\t\t\t{ name: 'name', text: 'Opportunity name', width: 200, sort: 1, editable: 1 },\n\t\t\t\t{ name: 'value', text: 'Value', width: 100, sort: 1, type: 'number', format: 2 },\n\t\t\t\t{ name: 'status', text: 'Status', width: 100, editable: 1 },\n\t\t\t\t{ name: 'owner', text: 'Owner', width: 200 },\n\t\t\t\t{ name: 'newsletter', text: 'Newsletter', width: 100, type: 'boolean' },\n\t\t\t\t{ name: 'date', text: 'Date', width: 200, sort: 1, type: 'date', format: 'yyyy-MM-dd' }\n\t\t\t]\n\t\t</script>\n\t</ui-component>\n</div>\n\n<script>\n\n\tfunction checked(rows) {\n\t\tconsole.log(rows);\n\t}\n\n\tfunction editable(opt) {\n\t\t// opt.row\n\t\t// opt.col\n\t\t// opt.value\n\t\t// opt.next(NEW_VALUE);\n\t\topt.next(opt.value + ' (modified)');\n\t}\n\n\tfunction paginate(type, page, sort) {\n\t\tconsole.log('Page:', page, 'sort:', sort);\n\t}\n\n\t\tvar data = {};\n\n\t\tdata.items = [];\n\t\tdata.pages = 53;\n\t\tdata.page = 3;\n\n\t\t// data.cols.push({ name: 'name', text: 'Opportunity name', width: 200, sort: true });\n\t\t// data.cols.push({ name: 'value', text: 'Value', width: 100, sort: true });\n\t\t// data.cols.push({ name: 'status', text: 'Status', width: 100 });\n\t\t// data.cols.push({ name: 'owner', text: 'Owner', width: 200 });\n\t\t// data.cols.push({ name: 'date', text: 'Date', width: 200, sort: true });\n\n\t\tfor (var i = 0; i < 50; i++)\n\t\t\tdata.items.push({ name: GUID(20), value: Math.random() * 1000 >> 0, status: GUID(10), owner: 'Peter Širka', newsletter: i % 3 === 0, date: NOW });\n</script>\n```"}],"knowledge":{"content_sha256":"07b9c16945d8a7effe1760ca889312a66c92c690828035a97358d42bc9132576","source_sha256":"9fe7752d03a2100cc154ec3c0ade2996f64f4ee09bf584e6199b8c2b766ea3f0","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","EXEC","GUID","HTML","MIT","NEW_VALUE","NOW","NUMBER","SET","SPA"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"width:500px;height:400px\">\n\t<ui-component name=\"datatable\" path=\"data\" config=\"highlight:1;exec:paginate;editable:editable;checked:checked;margin:22\">\n\t\t<script type=\"text/html\">\n\t\t\t[\n\t\t\t\t{ name: 'name', text: 'Opportunity name', width: 200, sort: 1, editable: 1 },\n\t\t\t\t{ name: 'value', text: 'Value', width: 100, sort: 1, type: 'number', format: 2 },\n\t\t\t\t{ name: 'status', text: 'Status', width: 100, editable: 1 },\n\t\t\t\t{ name: 'owner', text: 'Owner', width: 200 },\n\t\t\t\t{ name: 'newsletter', text: 'Newsletter', width: 100, type: 'boolean' },\n\t\t\t\t{ name: 'date', text: 'Date', width: 200, sort: 1, type: 'date', format: 'yyyy-MM-dd' }\n\t\t\t]\n\t\t</script>\n\t</ui-component>\n</div>\n\n<script>\n\n\tfunction checked(rows) {\n\t\tconsole.log(rows);\n\t}\n\n\tfunction editable(opt) {\n\t\t// opt.row\n\t\t// opt.col\n\t\t// opt.value\n\t\t// opt.next(NEW_VALUE);\n\t\topt.next(opt.value + ' (modified)');\n\t}\n\n\tfunction paginate(type, page, sort) {\n\t\tconsole.log('Page:', page, 'sort:', sort);\n\t}\n\n\t\tvar data = {};\n\n\t\tdata.items = [];\n\t\tdata.pages = 53;\n\t\tdata.page = 3;\n\n\t\t// data.cols.push({ name: 'name', text: 'Opportunity name', width: 200, sort: true });\n\t\t// data.cols.push({ name: 'value', text: 'Value', width: 100, sort: true });\n\t\t// data.cols.push({ name: 'status', text: 'Status', width: 100 });\n\t\t// data.cols.push({ name: 'owner', text: 'Owner', width: 200 });\n\t\t// data.cols.push({ name: 'date', text: 'Date', width: 200, sort: true });\n\n\t\tfor (var i = 0; i < 50; i++)\n\t\t\tdata.items.push({ name: GUID(20), value: Math.random() * 1000 >> 0, status: GUID(10), owner: 'Peter Širka', newsletter: i % 3 === 0, date: NOW });\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/datepicker","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/datepicker/","markdown_url":"https://forge.totaljs.es/en/components/datepicker.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"1a96beead2b693752d9c677c035f374b5a5d4aa50dc4ec47519a644ee75f109a","checked_at":"2026-10-20T12:20:34.753Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-DatePicker","description":"The component must be called manually or works with j-Input component automatically. The component is a singleton.","messages":[{"role":"user","content":"j-DatePicker\n\nThe component must be called manually or works with j-Input component automatically. The component is a singleton."},{"role":"assistant","content":"- it's improved clone of `j-Calendar`\n- supports __dark mode__\n- supports __touch gestures (swipe)__ and fixed full size on mobile\n\n__Configuration__:\n\n- `firstday {Number}` first day in the week (default: `DEF.firstdayofweek`)\n- `today {String}` a label for the `Set today` button\n- `clear {String}` a label for the `Clear` button\n- `days {String}` days e.g. `SU,MO,TU,WE,TH,FR,SA` (default).\n- `months {String}` months e.g. `January,February,...` (default).\n\nThe component must be called manually or works with `j-Input` component automatically. The component is a singleton.\n\n- __important__ add the calendar component under `<body>` element\n\n## Usage\n\n```javascript\nvar opt = {};\nopt.element = YOUR_ELEMENT;\nopt.value = new Date();\n// or opt.value = 'path.to.date'; --> then \"opt.callback\" is disabled\n// opt.align {String}      : align \"left\" (default), \"center\" or \"right\"\n// opt.position {String}   : position \"top\" (default) or \"bottom\"\n// NEW: opt.clock {Boolean} : enables time (default: false)\n// NEW: opt.ampm {Boolean} : enables AM/PM (default: false)\n\nopt.clear = false; // Removes \"clear\" button from datepicker. If this property is not specified as \"false\" - \"clear\" button will be shown\n\nopt.callback = function(newdate) {\n\tconsole.log('date has been changed');\n};\n// IMPORTANT - opt.callback() can receive \"null\" value as a parameter, so this check should be taken into consideration when callback function is being created.\n\n// opt.offsetX {Number} \"x\" offset\n// opt.offsetY {Number} \"y\" offset\n// opt.close {Function} optional, this function can determine closing of DatePicker\n\n// NEW and OPTIONAL\n// It's callend when the DatePicker changes year/month\nopt.badges = function(date, append) {\n\t// Appends small and red badges to the current view/month\n\tappend([date1, date2, date3]);\n};\n\nSETTER('datepicker/show', opt);\n```\n\nUse it with `<ui-component name=\"datepicker\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<a href=\"#datepicker\">SHOW DATEPICKER: <b><ui-bind path=\"form.date\" config=\"html:value?value.format('dd.MM.yyyy'):DEF.empty\"></ui-bind></b></a>\n<ui-component name=\"datepicker\" config=\"today:Set today;firstday:0\"></ui-component>\n\n<script>\n\tvar form = {};\n\tform.date = new Date();\n\n\t$('a[href=\"#datepicker\"]').on('click', function(e) {\n\t\tvar opt = {};\n\t\topt.value = 'form.date';\n\t\topt.element = this;\n\n\t\t// Append badges\n\t\topt.badges = function(date, append) {\n\t\t\tsetTimeout(function() {\n\t\t\t\tappend([date.add('-5 days'), date.add('-2 days'), date.add('3 days'), date.add('8 days')])\n\t\t\t}, 1000);\n\t\t};\n\n\t\tSETTER('datepicker/show', opt);\n\t});\n</script>\n```"}],"knowledge":{"content_sha256":"d0e5f28172d2d2671add1db9166395c4528153d8b805d5c2e201824783d48f99","source_sha256":"1a96beead2b693752d9c677c035f374b5a5d4aa50dc4ec47519a644ee75f109a","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DATEPICKER","DEF","IMPORTANT","MIT","NEW","OPTIONAL","SETTER","SHOW","YOUR_ELEMENT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<a href=\"#datepicker\">SHOW DATEPICKER: <b><ui-bind path=\"form.date\" config=\"html:value?value.format('dd.MM.yyyy'):DEF.empty\"></ui-bind></b></a>\n<ui-component name=\"datepicker\" config=\"today:Set today;firstday:0\"></ui-component>\n\n<script>\n\tvar form = {};\n\tform.date = new Date();\n\n\t$('a[href=\"#datepicker\"]').on('click', function(e) {\n\t\tvar opt = {};\n\t\topt.value = 'form.date';\n\t\topt.element = this;\n\n\t\t// Append badges\n\t\topt.badges = function(date, append) {\n\t\t\tsetTimeout(function() {\n\t\t\t\tappend([date.add('-5 days'), date.add('-2 days'), date.add('3 days'), date.add('8 days')])\n\t\t\t}, 1000);\n\t\t};\n\n\t\tSETTER('datepicker/show', opt);\n\t});\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/detach","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/detach/","markdown_url":"https://forge.totaljs.es/en/components/detach.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"517eab1a49c96707dcc09d18bf425c104beb3722211a560461b7117c7c9ffdc4","checked_at":"2026-10-20T12:20:35.745Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Detach","description":"The component can detach/attach all children (HTML elements). For example, if you have elements inside a panel, you can move the elements e.g., to the j-Windows.","messages":[{"role":"user","content":"j-Detach\n\nThe component can detach/attach all children (HTML elements). For example, if you have elements inside a panel, you can move the elements e.g., to the j-Windows."},{"role":"assistant","content":"The component can detach/attach all children (HTML elements). For example, if you have elements inside a panel, you can move the elements e.g., to the `j-Windows`.\n\nThe component `watch` path `{Boolean}` and if the value contains `true`, elements will be detached otherwise attached.\n\n__Configuration__:\n\n- `detach {String}` a link to the `function(children, element)`\n- `attach {String}` a link to the `function(children, element)`\n- `remove {String}` a link to the `function(children)`\n\n---\n\n- `detach` is executed when the content is detached\n- `attach` is executed when the content must be returned back\n- `remove` is executed when the component (or a parent element) is removed\n\nUse it with `<ui-component name=\"detach\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\tbody { padding: 0; margin: 0; overflow: hidden; }\n</style>\n\n<ui-component name=\"windows\" path=\"common.windows\"></ui-component>\n\n<div class=\"center padding\"><button onclick=\"togglewindow()\">Toggle windowed mode</button></div>\n\n<div style=\"margin:30px auto;border:1px solid #E0E0E0;max-width:500px;width:100%;min-height:100px;\">\n\t<ui-component name=\"detach\" path=\"common.detach\" config=\"detach:el_detach;attach:el_attach;remove:el_remove\">\n\t\t<div class=\"padding\">\n\t\t\t<h2>Your content</h2>\n\t\t\t<div><img src=\"https://cdn.componentator.com/logo.svg\" width=\"200\" /></div>\n\t\t</div>\n\t</ui-component>\n</div>\n\n<script>\n\n\tvar common = {};\n\n\tcommon.detach = false;\n\tcommon.windows = [];\n\n\tfunction el_detach(children, element) {\n\n\t\tvar id = 'mywin';\n\t\tvar div = document.createElement('DIV');\n\n\t\tvar win = {};\n\t\twin.id = id;\n\t\twin.cachekey = id;\n\t\twin.offset = { x: 200, y: 100, width: 840, height: 500, minwidth: 450, minheight: 300 };\n\t\twin.actions = { move: true, close: true, resize: true, maximize: true, minimize: false, autosave: true };\n\t\twin.title = 'Example';\n\t\twin.html = div;\n\n\t\twin.close = function(next) {\n\t\t\tSET('common.detach', false);\n\t\t\tnext();\n\t\t};\n\n\t\tfor (var m of children)\n\t\t\tdiv.appendChild(m);\n\n\t\tPUSH('common.windows', win);\n\t}\n\n\tfunction el_attach(children, element) {\n\n\t\tvar id = 'mywin';\n\n\t\tfor (var m of children)\n\t\t\telement[0].appendChild(m);\n\n\t\tcommon.windows.splice(common.windows.findIndex('id', id), 1);\n\t\tUPD('common.windows');\n\t}\n\n\tfunction el_remove(children) {\n\t\tvar id = 'mywin';\n\t\tvar index = common.windows.findIndex('id', id);\n\t\tif (index !== -1) {\n\t\t\tcommon.windows.splice(index, 1);\n\t\t\tUPD('common.windows');\n\t\t}\n\t}\n\n\tfunction togglewindow() {\n\t\tvar path = 'common.detach';\n\t\tvar val = GET(path);\n\t\tSET(path, !val);\n\t}\n\n</script>\n```"}],"knowledge":{"content_sha256":"c3f319cecfeb2447eff10067bfb8f1e06f226641865cff05a7f5cdf2fdbd2e8c","source_sha256":"517eab1a49c96707dcc09d18bf425c104beb3722211a560461b7117c7c9ffdc4","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","DIV","E0E0E0","GET","HTML","MIT","PUSH","SET","UPD"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\tbody { padding: 0; margin: 0; overflow: hidden; }\n</style>\n\n<ui-component name=\"windows\" path=\"common.windows\"></ui-component>\n\n<div class=\"center padding\"><button onclick=\"togglewindow()\">Toggle windowed mode</button></div>\n\n<div style=\"margin:30px auto;border:1px solid #E0E0E0;max-width:500px;width:100%;min-height:100px;\">\n\t<ui-component name=\"detach\" path=\"common.detach\" config=\"detach:el_detach;attach:el_attach;remove:el_remove\">\n\t\t<div class=\"padding\">\n\t\t\t<h2>Your content</h2>\n\t\t\t<div><img src=\"https://cdn.componentator.com/logo.svg\" width=\"200\" /></div>\n\t\t</div>\n\t</ui-component>\n</div>\n\n<script>\n\n\tvar common = {};\n\n\tcommon.detach = false;\n\tcommon.windows = [];\n\n\tfunction el_detach(children, element) {\n\n\t\tvar id = 'mywin';\n\t\tvar div = document.createElement('DIV');\n\n\t\tvar win = {};\n\t\twin.id = id;\n\t\twin.cachekey = id;\n\t\twin.offset = { x: 200, y: 100, width: 840, height: 500, minwidth: 450, minheight: 300 };\n\t\twin.actions = { move: true, close: true, resize: true, maximize: true, minimize: false, autosave: true };\n\t\twin.title = 'Example';\n\t\twin.html = div;\n\n\t\twin.close = function(next) {\n\t\t\tSET('common.detach', false);\n\t\t\tnext();\n\t\t};\n\n\t\tfor (var m of children)\n\t\t\tdiv.appendChild(m);\n\n\t\tPUSH('common.windows', win);\n\t}\n\n\tfunction el_attach(children, element) {\n\n\t\tvar id = 'mywin';\n\n\t\tfor (var m of children)\n\t\t\telement[0].appendChild(m);\n\n\t\tcommon.windows.splice(common.windows.findIndex('id', id), 1);\n\t\tUPD('common.windows');\n\t}\n\n\tfunction el_remove(children) {\n\t\tvar id = 'mywin';\n\t\tvar index = common.windows.findIndex('id', id);\n\t\tif (index !== -1) {\n\t\t\tcommon.windows.splice(index, 1);\n\t\t\tUPD('common.windows');\n\t\t}\n\t}\n\n\tfunction togglewindow() {\n\t\tvar path = 'common.detach';\n\t\tvar val = GET(path);\n\t\tSET(path, !val);\n\t}\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/detail","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/detail/","markdown_url":"https://forge.totaljs.es/en/components/detail.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"d5afb3d2c3011a15a3a7331494d8ca2026722cb9b350b1ad4250278b0a49b8f7","checked_at":"2026-10-20T12:20:36.491Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Detail","description":"This component can render values from model. Supports inline mapping - look into the source-code of the example.","messages":[{"role":"user","content":"j-Detail\n\nThis component can render values from model. Supports inline mapping - look into the source-code of the example."},{"role":"assistant","content":"This component can render values from model. Supports inline mapping - look into the source-code of the example.\n\n\n__Configuration__:\n\n- `dateformat {String}` a date format (default: `DEF.dateformat`)\n- `datetimeformat {String}` a date/time format (default: `DEF.dateformat + ' ' + DEF.timeformat`)\n- `numberformat {Number}` a default number format (default: empty)\n- `small {Boolean}` enables smaller design (default: `false`)\n- `style {Number}` component style (default: `1`), supports style `2`, `3` (minimal) and `4` (modern)\n- `defaultgroup {String}` a default group name (default: `Default`)\n- `notnull {Boolean}` skips nullable values (default: `false`)\n- `track {String}` can contain paths divided by comma, this property will watch only changes in trakced keys\n\n__Data declaration__:\n\n```javascript\nvar model = { firstname: 'Peter', lastname: 'Širka', age: 33 };\nvar items = [{ id: 2, name: 'Banská Bystrica' }, { id: 3, name: 'Bratislava' }];\nvar form = [];\n\n// The below values will be binded automatically from the \"model\" (according to the \"path\" field) because they contain \"path\" field\nform.push({ group: 'Personal', label: 'First name', path: 'firstname', type: 'string', value: model, note: 'This is first name' });\nform.push({ group: 'Personal', label: 'Last name', path: 'lastname', type: 'string', value: model, placeholder: 'String' });\nform.push({ group: 'Personal', label: 'Age', path: 'age', type: 'number', value: model });\n\n// The below values will be binded from \"value\" key\nform.push({ group: 'Personal', label: 'Birth date', type: 'date', value: NOW });\nform.push({ group: 'Additional', label: 'Newsletter', type: 'bool', value: true });\nform.push({ group: 'Additional', label: 'City', type: 'list', value: 2, items: 'items' });\nform.push({ group: 'Additional', label: 'Color', type: 'color', value: '#e73323' });\nform.push({ group: 'Additional', label: 'Icon', type: 'fontawesome', value: 'fas fa-home' });\n```\n\n__Data properties in data declaration__:\n\n- `group {String}` a group name (required)\n- `label {String}` a label (required)\n- `type {String}` a type (required)\n- `note {String}` a small note under value\n- `format {String}` number e.g. `2` or date format `dd.MM.yyyy`\n- `detail {String}` URL address for obtaining of data for `list` type, example: `/users/{0}/` and `{0}` will be replaced with the value from list\n- `items {Object Array/String}` items for `list` type only, string can contain a path to `Array` or `URL address` to search\n- `value {Object}` a value or model with values (then `path` is required)\n- `path {String}` a path to value in model (`value` must contain a model)\n- `template {String}` A Tangular template (works only with `type:'template'` and `{{ value }}` contains a raw value, and `{{ item }}` contains the entire item)\n- `empty {String}` An empty value when the value is nullable (default: `---`)\n- `show {String}` the conditional function `value.type === 'company'` then displays if the evaluated condition is `true`\n- __NEW__ `autoformat {Boolean}` enables auto-format for `email`, or `phones`, and only for `string` type (default: `false`)\n- __NEW__ `colorize {Boolean}` colorizes the value with except `list`, `color` and `icon` type\n- __NEW__ `plus {String}` adds a value to end of rendered value for `number` and `string` types\n\n__Allowed types in data__:\n\n- `string`\n- `bool`\n- `number`\n- `date`\n- `datetime`\n- `list`\n- `color`\n- `emoji`\n- `fontawesome`\n- `password`\n- `template`\n\n__Good to know__:\n\n- if group contains only the one char then the name of group is not rendered\n\nUse it with `<ui-component name=\"detail\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"width:500px\">\n\t<ui-component name=\"detail\" path=\"form\"></ui-component>\n\t<br />\n\t<div>-- OR --</div>\n\t<br />\n\t<ui-component name=\"detail\" path=\"model\" config=\"style:2\">\n\t\t<script type=\"text/plain\">\n\t\t\t[\n\t\t\t\t{ name: 'First name', path: 'firstname', type: 'string' },\n\t\t\t\t{ name: 'Last name', path: 'lastname', type: 'string' },\n\t\t\t\t{ name: 'Age', path: 'age', type: 'number' }\n\t\t\t]\n\t\t</script>\n\t</ui-component>\n\t<br />\n\t<div>-- OR FILTERED --</div>\n\t<br />\n\t<ui-component name=\"detail\" path=\"model\" config=\"style:3\">\n\t\t<script type=\"text/plain\">\n\t\t\t[\n\t\t\t\t// Will be displayed, look to show condition (\"value\" means \"model\")\n\t\t\t\t{ name: 'First name', path: 'firstname', type: 'string', show: 'value.type==1' },\n\t\t\t\t{ name: 'Last name', path: 'lastname', type: 'string', show: 'value.type==1' },\n\t\t\t\t// Won't be displayed, look to show condition (\"value\" means \"model\")\n\t\t\t\t{ name: 'Age', path: 'age', type: 'number', show: 'value.type==2' }\n\t\t\t]\n\t\t</script>\n\t</ui-component>\n\t<br />\n\t<ui-component name=\"detail\" path=\"model\" config=\"style:4\">\n\t\t<script type=\"text/plain\">\n\t\t\t[\n\t\t\t\t// Will be displayed, look to show condition (\"value\" means \"model\")\n\t\t\t\t{ name: 'First name', path: 'firstname', type: 'string', show: 'value.type==1' },\n\t\t\t\t{ name: 'Last name', path: 'lastname', type: 'string', show: 'value.type==1' },\n\t\t\t\t// Won't be displayed, look to show condition (\"value\" means \"model\")\n\t\t\t\t{ name: 'Age', path: 'age', type: 'number', show: 'value.type==2' }\n\t\t\t]\n\t\t</script>\n\t</ui-component>\n</div>\n\n<script>\n\tvar model = { firstname: 'Peter', lastname: 'Širka', age: 33, type: 1 };\n\tvar items = [{ id: 2, name: 'Banská Bystrica' }, { id: 3, name: 'Bratislava' }];\n\tvar form = [];\n\n\t// The below values will be binded automatically from the \"model\" (according to the \"path\" field) because they contain \"path\" field\n\tform.push({ group: 'Personal', label: 'First name', path: 'firstname', type: 'string', value: model, note: 'This is first name' });\n\tform.push({ group: 'Personal', label: 'Last name', path: 'lastname', type: 'string', value: model, placeholder: 'String' });\n\tform.push({ group: 'Personal', label: 'Age', path: 'age', type: 'number', value: model });\n\n\t// The below values will be binded from \"value\" key\n\tform.push({ group: 'Personal', label: 'Birth date', type: 'date', value: NOW });\n\tform.push({ group: 'Additional', label: 'Newsletter', type: 'bool', value: true });\n\tform.push({ group: 'Additional', label: 'City', type: 'list', value: 2, items: 'items' });\n\tform.push({ group: 'Additional', label: 'Color', type: 'color', value: '#e73323' });\n\tform.push({ group: 'Additional', label: 'Icon', type: 'fontawesome', value: 'ti ti-home' });\n</script>\n```"}],"knowledge":{"content_sha256":"72120fd4080e6ef594d8283420e7f7622eab2675414e881b22289806e2b6cf83","source_sha256":"d5afb3d2c3011a15a3a7331494d8ca2026722cb9b350b1ad4250278b0a49b8f7","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","FILTERED","MIT","NOW","URL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"width:500px\">\n\t<ui-component name=\"detail\" path=\"form\"></ui-component>\n\t<br />\n\t<div>-- OR --</div>\n\t<br />\n\t<ui-component name=\"detail\" path=\"model\" config=\"style:2\">\n\t\t<script type=\"text/plain\">\n\t\t\t[\n\t\t\t\t{ name: 'First name', path: 'firstname', type: 'string' },\n\t\t\t\t{ name: 'Last name', path: 'lastname', type: 'string' },\n\t\t\t\t{ name: 'Age', path: 'age', type: 'number' }\n\t\t\t]\n\t\t</script>\n\t</ui-component>\n\t<br />\n\t<div>-- OR FILTERED --</div>\n\t<br />\n\t<ui-component name=\"detail\" path=\"model\" config=\"style:3\">\n\t\t<script type=\"text/plain\">\n\t\t\t[\n\t\t\t\t// Will be displayed, look to show condition (\"value\" means \"model\")\n\t\t\t\t{ name: 'First name', path: 'firstname', type: 'string', show: 'value.type==1' },\n\t\t\t\t{ name: 'Last name', path: 'lastname', type: 'string', show: 'value.type==1' },\n\t\t\t\t// Won't be displayed, look to show condition (\"value\" means \"model\")\n\t\t\t\t{ name: 'Age', path: 'age', type: 'number', show: 'value.type==2' }\n\t\t\t]\n\t\t</script>\n\t</ui-component>\n\t<br />\n\t<ui-component name=\"detail\" path=\"model\" config=\"style:4\">\n\t\t<script type=\"text/plain\">\n\t\t\t[\n\t\t\t\t// Will be displayed, look to show condition (\"value\" means \"model\")\n\t\t\t\t{ name: 'First name', path: 'firstname', type: 'string', show: 'value.type==1' },\n\t\t\t\t{ name: 'Last name', path: 'lastname', type: 'string', show: 'value.type==1' },\n\t\t\t\t// Won't be displayed, look to show condition (\"value\" means \"model\")\n\t\t\t\t{ name: 'Age', path: 'age', type: 'number', show: 'value.type==2' }\n\t\t\t]\n\t\t</script>\n\t</ui-component>\n</div>\n\n<script>\n\tvar model = { firstname: 'Peter', lastname: 'Širka', age: 33, type: 1 };\n\tvar items = [{ id: 2, name: 'Banská Bystrica' }, { id: 3, name: 'Bratislava' }];\n\tvar form = [];\n\n\t// The below values will be binded automatically from the \"model\" (according to the \"path\" field) because they contain \"path\" field\n\tform.push({ group: 'Personal', label: 'First name', path: 'firstname', type: 'string', value: model, note: 'This is first name' });\n\tform.push({ group: 'Personal', label: 'Last name', path: 'lastname', type: 'string', value: model, placeholder: 'String' });\n\tform.push({ group: 'Personal', label: 'Age', path: 'age', type: 'number', value: model });\n\n\t// The below values will be binded from \"value\" key\n\tform.push({ group: 'Personal', label: 'Birth date', type: 'date', value: NOW });\n\tform.push({ group: 'Additional', label: 'Newsletter', type: 'bool', value: true });\n\tform.push({ group: 'Additional', label: 'City', type: 'list', value: 2, items: 'items' });\n\tform.push({ group: 'Additional', label: 'Color', type: 'color', value: '#e73323' });\n\tform.push({ group: 'Additional', label: 'Icon', type: 'fontawesome', value: 'ti ti-home' });\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/developer","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/developer/","markdown_url":"https://forge.totaljs.es/en/components/developer.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"24fa8b8080d2c68b9a69a582dffa7972c2ff8b7df80950672c6976e9f8d35c2a","checked_at":"2026-10-20T12:20:37.236Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Developer","description":"This component monitors jComponent single page application. Just include component under the <body> element. It works only with jComponent +v18.","messages":[{"role":"user","content":"j-Developer\n\nThis component monitors jComponent single page application. Just include component under the <body> element. It works only with jComponent +v18."},{"role":"assistant","content":"This component monitors jComponent single page application. Just include component under the `<body>` element. It works only with jComponent `+v18`.\n\n- `time` in brackets means a delay between execution\n- `↑NUMBER` means a maximum count of calls\n- `+NUMBER` means added items/operations\n- `-NUMBER` means removed items/operations\n- `NUMBERx` means a count of items/operations/calls\n- memory consumption works only in Chromium browsers\n\nUse it with `<ui-component name=\"developer\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"developer\"></ui-component>\n\n<script>\n\n\tsetTimeout(function() {\n\t\tSET('something', 1);\n\t\tSET('something', 2);\n\t\tSET('something', 3);\n\t\tGET('something');\n\t}, 2000);\n\n</script>\n```"}],"knowledge":{"content_sha256":"693348639089929f1292c32d4ef02950b9f26b4ec851d5cf42a8b7599e3e463a","source_sha256":"24fa8b8080d2c68b9a69a582dffa7972c2ff8b7df80950672c6976e9f8d35c2a","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","GET","MIT","NUMBER","SET"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"developer\"></ui-component>\n\n<script>\n\n\tsetTimeout(function() {\n\t\tSET('something', 1);\n\t\tSET('something', 2);\n\t\tSET('something', 3);\n\t\tGET('something');\n\t}, 2000);\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/diagram","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/diagram/","markdown_url":"https://forge.totaljs.es/en/components/diagram.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"4f309688f0a916b819ca012d5311eeac9fd26fb79922d469f7b1c1a340fca93c","checked_at":"2026-10-20T12:20:38.120Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Diagram","description":"j-Diagram options: parent, margin, marginxs, marginsm, marginmd, marginlg, reverse","messages":[{"role":"user","content":"j-Diagram\n\nj-Diagram options: parent, margin, marginxs, marginsm, marginmd, marginlg, reverse"},{"role":"assistant","content":"__Configuration__:\n\n- `parent {String}` optional, a container with fixed height, can be `window`. Default value: `parent` element.\n- `margin {Number}` optional, a top/bottom margin together (default: `0`)\n- `marginxs {Number}` optional, a top/bottom margin together for `xs` screen width\n- `marginsm` optional, a top/bottom margin together for `sm` screen width\n- `marginmd` optional, a top/bottom margin together for `md` screen width\n- `marginlg` optional, a top/bottom margin together for `lg` screen width\n- `reverse {Boolean}`, reverses diagram\n\nUse it with `<ui-component name=\"diagram\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"width:600px;height:400px;border:1px solid #E0E0E0\">\n\t<ui-component name=\"diagram\" path=\"items\" class=\"invisible\">\n\t\t<script type=\"text/html\">\n\t\t\t<div style=\"padding:10px;{{ if id === 'AA' }}width:200px;height:150px{{ fi }}{{ if id === 'BC' }}height:100px{{ fi }}\">{{ id }}</div>\n\t\t</script>\n\t</ui-component>\n</div>\n\n<script>\n\n\tvar items = [];\n\titems.push({ id: 'A', items: [{ id: 'AA', items: [{ id: 'AAA' }] }] });\n\titems.push({ id: 'B', items: [{ id: 'BA' }, { id: 'BB' }, { id: 'BC', items: [{ id: 'BCA' }] }] });\n\titems.push({ id: 'C', items: [{ id: 'CA', items: [{ id: 'CAA' }] }, { id: 'CB' }, { id: 'CC' }, { id: 'CD', items: [{ id: 'CDA' }] }] });\n\n</script>\n```"}],"knowledge":{"content_sha256":"81d2acf25ecc10cba25072602d611ba768daf7ea061d10a03a326a89d2d15bf4","source_sha256":"4f309688f0a916b819ca012d5311eeac9fd26fb79922d469f7b1c1a340fca93c","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["AAA","BCA","CAA","CDA","CDN","DEF","E0E0E0","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"width:600px;height:400px;border:1px solid #E0E0E0\">\n\t<ui-component name=\"diagram\" path=\"items\" class=\"invisible\">\n\t\t<script type=\"text/html\">\n\t\t\t<div style=\"padding:10px;{{ if id === 'AA' }}width:200px;height:150px{{ fi }}{{ if id === 'BC' }}height:100px{{ fi }}\">{{ id }}</div>\n\t\t</script>\n\t</ui-component>\n</div>\n\n<script>\n\n\tvar items = [];\n\titems.push({ id: 'A', items: [{ id: 'AA', items: [{ id: 'AAA' }] }] });\n\titems.push({ id: 'B', items: [{ id: 'BA' }, { id: 'BB' }, { id: 'BC', items: [{ id: 'BCA' }] }] });\n\titems.push({ id: 'C', items: [{ id: 'CA', items: [{ id: 'CAA' }] }, { id: 'CB' }, { id: 'CC' }, { id: 'CD', items: [{ id: 'CDA' }] }] });\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/directory","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/directory/","markdown_url":"https://forge.totaljs.es/en/components/directory.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"19419468b66eba7f7e8ce95edc0f2568092b1d470364dfddc9f3340caba7a9d0","checked_at":"2026-10-20T12:20:39.009Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Directory","description":"It's similar component like j-Suggestion, but has improved design and supports more handy features.","messages":[{"role":"user","content":"j-Directory\n\nIt's similar component like j-Suggestion, but has improved design and supports more handy features."},{"role":"assistant","content":"It's similar component like `j-Suggestion`, but has improved design and supports more handy features.\n\n- singleton\n- works with touches\n- supports __dark mode__\n\n> __IMPORTANT__: by default this component renders a value as a text from `name` property.\n\n\n__Methods__:\n\nMethod: `component.show(options)`\n\n- `options.element {Element/jQuery Element}`\n- `options.items {Array/String/Function}` a list of items (`String` type means a link to data-source)\n- `options.offsetX {Number}` optional, `x` offset (default: `0`)\n- `options.offsetY {Number}` optional, `y` offset (default: `0`)\n- `options.offsetWidth {Number}` optional, `width` offset (default: `0`)\n- `options.placeholder {String}` optional, a placeholder for search field\n- __NEW__ `options.height {Number}` optional, a height of the container (default: `180`)\n- `options.render {Function}` optional, a function for customized render of item `function(item, text)` must return `HTML`\n- `options.custom {Boolean/String}` optional, enables returning a value not defined in `options.items`, default: `false`\n- `options.minwidth {Number}` optional, a minimal width, default `200`\n- `options.maxwidth {Number}` optional, a maximal width, default `infinite`\n- `options.callback(selected_item, element, iscustom, e) {Function}` is triggered when the user clicks on the item\n- `options.key {String}` optional, a default `key` for `text` value (default: `name`)\n- `options.empty {String}` optional, adds empty field\n- `options.exclude {Function}` optional, can exclude items and must return a boolean `function(item) { return item.id !== 100 }`\n- `options.close {Function}` optional, can determine closing of panel\n- `options.search {Boolean/String}` optional, can hide search field (default: `true`) or `{String}` (key name) can map a value for searching\n- `options.selected {Object}` optional, can contain a selected value (default: `undefined`)\n- `options.align {String}` optional, `left` (default), `right` or `center`\n- `options.position {String}` optional, `top` (default), `bottom`\n- `options.raw {Boolean}` optional, this option disables escaping of characters (in other words: HTML content will be allowed in labels of items, default: `false`)\n- `options.classname {String}` optional, a custom container class for all items\n- absolute positioning works only if `options.element` is not defined\n\t- `options.x {Number}` absolute position X\n\t- `options.y {Number}` absolute position y\n- `options.checkbox {Boolean}` optional, enables checkboxes\n- `options.checked {Array String}` optional, identifiers of checked items\n\t- targeted for `options.items {Function(q, next)}`\n\t- the callback returns only raw identifiers `Array {String}`\n- __NEW__ `options.template {String}` optional, a custom Tangular template e.g. `{{ name }}` (default: null)\n- __NEW__ `options.fuzzy {Boolean}` optional, it can disable fuzzy search (default: `true`)\n\nMethod: `component.hide()`\n\n__Configuration__:\n- `placeholder` - a placeholder for the search input\n- `create` - a placeholder for `Create` label (default: `Create`)\n\n__Supported item fields__:\n\n- `name {String}` a label name\n- `template {String}` optional, a custom Tangular template\n- `classname {String}` optional, adds additional classes into the `<li>` element\n- `disabled {Boolean}` optional, it can disable item\n- `selected {Boolean}` optional, it selects item\n\n__Formatting via Tangular__:\n\n```js\nvar opt = {};\nopt.items = [];\nopt.push({ name: 'Total.js', template: '<b>{{ name }}</b>' });\n```\n\n__Formatting via custom function__:\n\n```js\nvar opt = {};\nopt.items = [];\nopt.push({ name: 'Total.js' });\nopt.push({ name: 'Express.js' });\nopt.push({ name: 'Koa.js' });\nopt.render = function(item, name) {\n\treturn '<b>' + name + '</b>';\n};\n```\n\n__Adding of custom class__:\n\n```js\nvar opt = {};\nopt.items = [];\nopt.push({ name: 'Total.js', classname: 'your_class_name' });\n```\n\n__Server-side searching__:\n\n```js\n\nvar opt = {};\n\nopt.element = your_element;\n\nopt.items = function(search, next) {\n\tnext([{ name: 'Item 1' }, { name: 'Item 2' }]);\n};\n\nopt.callback = function(selected) {\n\tconsole.log(selected);\n};\n\nSETTER('directory/show', opt);\n```\n\n__Good to know__:\n\n- if `checkboxes:true` then the component extend items by adding a timestamp `selectedts {Number}` due to order\n\nUse it with `<ui-component name=\"directory\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\tbody { background-color: #F0F0F0; }\n</style>\n\n<ui-component name=\"directory\" config=\"placeholder:Search ...\"></ui-component>\n\n<div class=\"container\">\n\t<div><a href=\"javascript:void(0)\" data-name=\"classic\">Open directory - classic</a></div>\n\t<div><a href=\"javascript:void(0)\" data-name=\"checkboxes\">Open directory - checkboxes</a></div>\n</div>\n\n<script>\n\t$(document).on('click', 'a', function() {\n\n\n\t\tvar opt = {};\n\t\topt.element = $(this);\n\n\t\tif (opt.element.attrd('name') === 'checkboxes') {\n\t\t\topt.checkbox = true;\n\t\t\topt.items = [{ name: 'Total.js', selected: true }, { name: 'Express.js', selected: true }, { name: 'Meteor.js' }, { name: 'Koa.js' }, { name: 'ASP.NET' }, { name: 'PHP' }, { name: 'Python' }, { name: 'Node.js' }];\n\t\t} else\n\t\t\topt.items = [{ name: 'Total.js', template: '<b>{{ name }}</b>' }, { name: 'Express.js' }, { name: 'Meteor.js' }, { name: 'Koa.js' }, { name: 'ASP.NET' }, { name: 'PHP' }, { name: 'Python' }, { name: 'Node.js' }];\n\n\t\topt.callback = function(value) {\n\t\t\tif (opt.checkbox)\n\t\t\t\tconsole.log('SELECTED:', value);\n\t\t\telse\n\t\t\t\tconsole.log('CLICKED:', value);\n\t\t};\n\n\t\tSETTER('directory/show', opt);\n\t});\n</script>\n```"}],"knowledge":{"content_sha256":"8995b6b7f349e33de32c69128dbac4c5e14ad1b985207dcbc865d0b0956b39cc","source_sha256":"19419468b66eba7f7e8ce95edc0f2568092b1d470364dfddc9f3340caba7a9d0","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["ASP","CDN","CLICKED","DEF","F0F0F0","HTML","MIT","NET","PHP","SELECTED","SETTER","Total.js"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\tbody { background-color: #F0F0F0; }\n</style>\n\n<ui-component name=\"directory\" config=\"placeholder:Search ...\"></ui-component>\n\n<div class=\"container\">\n\t<div><a href=\"javascript:void(0)\" data-name=\"classic\">Open directory - classic</a></div>\n\t<div><a href=\"javascript:void(0)\" data-name=\"checkboxes\">Open directory - checkboxes</a></div>\n</div>\n\n<script>\n\t$(document).on('click', 'a', function() {\n\n\n\t\tvar opt = {};\n\t\topt.element = $(this);\n\n\t\tif (opt.element.attrd('name') === 'checkboxes') {\n\t\t\topt.checkbox = true;\n\t\t\topt.items = [{ name: 'Total.js', selected: true }, { name: 'Express.js', selected: true }, { name: 'Meteor.js' }, { name: 'Koa.js' }, { name: 'ASP.NET' }, { name: 'PHP' }, { name: 'Python' }, { name: 'Node.js' }];\n\t\t} else\n\t\t\topt.items = [{ name: 'Total.js', template: '<b>{{ name }}</b>' }, { name: 'Express.js' }, { name: 'Meteor.js' }, { name: 'Koa.js' }, { name: 'ASP.NET' }, { name: 'PHP' }, { name: 'Python' }, { name: 'Node.js' }];\n\n\t\topt.callback = function(value) {\n\t\t\tif (opt.checkbox)\n\t\t\t\tconsole.log('SELECTED:', value);\n\t\t\telse\n\t\t\t\tconsole.log('CLICKED:', value);\n\t\t};\n\n\t\tSETTER('directory/show', opt);\n\t});\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/display","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/display/","markdown_url":"https://forge.totaljs.es/en/components/display.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"38ecd8d78608d1e172a41e4541fbcc7d996c0efeda0c5124f3cbec43bd4c007e","checked_at":"2026-10-20T12:20:39.905Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Display","description":"The component calculates the interval between displays automatically. The default display type is lg.","messages":[{"role":"user","content":"j-Display\n\nThe component calculates the interval between displays automatically. The default display type is lg."},{"role":"assistant","content":"The component measures a display size according to the current element. It uses `WIDTH()` method and appends a CSS class in the form `jc-DISPLAY_SIZE`:\n\n- `jc-xs` extra small display (mobile phone)\n- `jc-sm` small display (tablet)\n- `jc-md` medium display (laptop)\n- `jc-lg` large display (desktop computer)\n\n__Configuration__:\n\n- `delay {Number}` a delay for obtaining of size (default: `100` ms)\n- __NEW__: `xs {Number}` a maximum width for the extra small display (default: `768`)\n- __NEW__: `sm {Number}` a maximum width for the small display (default: `992`)\n- __NEW__: `md {Number}` a maximum width for the medium display (default: `1200`)\n\nThe component calculates the interval between displays automatically. The default display type is `lg`.\n\nUse it with `<ui-component name=\"display\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"width:500px;padding:10px;border:1px solid #E0E0E0\">\n\t<ui-component name=\"display\" path=\"common.xs\">\n\t\t<ui-bind path=\"common.xs\" config=\"text\" class=\"block\"></ui-bind>\n\t</ui-component>\n</div>\n<br />\n<div style=\"width:800px;padding:10px;border:1px solid #E0E0E0\">\n\t<ui-component name=\"display\" path=\"common.sm\">\n\t\t<ui-bind path=\"common.sm\" config=\"text\" class=\"block\"></ui-bind>\n\t</ui-component>\n</div>\n<br />\n<div style=\"width:1080px;padding:10px;border:1px solid #E0E0E0\">\n\t<ui-component name=\"display\" path=\"common.md\">\n\t\t<ui-bind path=\"common.md\" config=\"text\" class=\"block\"></ui-bind>\n\t</ui-component>\n</div>\n<br />\n<div style=\"width:1380px;padding:10px;border:1px solid #E0E0E0\">\n\t<ui-component name=\"display\" path=\"common.lg\">\n\t\t<ui-bind path=\"common.lg\" config=\"text\" class=\"block\"></ui-bind>\n\t</ui-component>\n</div>\n<br />\n```"}],"knowledge":{"content_sha256":"1768e705ac6ce43d082080a400d9911dd3a890c2de7decca43d829ed73e7a554","source_sha256":"38ecd8d78608d1e172a41e4541fbcc7d996c0efeda0c5124f3cbec43bd4c007e","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","CSS","DEF","DISPLAY_SIZE","E0E0E0","MIT","WIDTH"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"width:500px;padding:10px;border:1px solid #E0E0E0\">\n\t<ui-component name=\"display\" path=\"common.xs\">\n\t\t<ui-bind path=\"common.xs\" config=\"text\" class=\"block\"></ui-bind>\n\t</ui-component>\n</div>\n<br />\n<div style=\"width:800px;padding:10px;border:1px solid #E0E0E0\">\n\t<ui-component name=\"display\" path=\"common.sm\">\n\t\t<ui-bind path=\"common.sm\" config=\"text\" class=\"block\"></ui-bind>\n\t</ui-component>\n</div>\n<br />\n<div style=\"width:1080px;padding:10px;border:1px solid #E0E0E0\">\n\t<ui-component name=\"display\" path=\"common.md\">\n\t\t<ui-bind path=\"common.md\" config=\"text\" class=\"block\"></ui-bind>\n\t</ui-component>\n</div>\n<br />\n<div style=\"width:1380px;padding:10px;border:1px solid #E0E0E0\">\n\t<ui-component name=\"display\" path=\"common.lg\">\n\t\t<ui-bind path=\"common.lg\" config=\"text\" class=\"block\"></ui-bind>\n\t</ui-component>\n</div>\n<br />"}],"test":null,"test_kind":"component-browser"}
{"id":"components/dockable","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/dockable/","markdown_url":"https://forge.totaljs.es/en/components/dockable.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"5d109dd1413a2481a0b92e5b5aa354f7aebeb3a12452e7b5b18c13730aca42ff","checked_at":"2026-10-20T12:20:40.648Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Dockable","description":"The dockable component expects Array of objects.","messages":[{"role":"user","content":"j-Dockable\n\nThe dockable component expects Array of objects."},{"role":"assistant","content":"- Works only with `+v18`\n- Supports touches\n- Movable\n- Resizable\n\n\n__Configuration__:\n\n- `style {Number}` `1` bottom panel will be fullwidth, `2` bottom panel will be between the left and right panel\n- `parent {String}` jquery selector due to container height (default: `window`)\n- `margin {Number}` a bottom margin (default: `0`)\n- __NEW__ `reoffsetresize {Boolean}` all floated panels will be re-offset according to `main` window dimension (default: `false`)\n\nThe `dockable` component expects `Array` of objects.\n\n```javascript\n// Component declaration\nvar panel = {\n\n\t// Component ID\n\tid: 'PANEL_ID',\n\n\t// Positioning\n\t// You can define specific display size and if the display size is not specified then the window tries to find a size for larger display\n\thtml: 'THE CONTENT OF PANEL', // or can be raw HTMLElement\n\n\toffset: { x: 200, y: 100, width: 600, height: 400, minwidth: 200, minheight: 200, maxwidth: 1000, maxheight: 1000, dockminwidth: 200, dockminheight: 200, dockmaxwidth: 400, dockmaxheight: 400 }, // minwidth, maxwidth, maxheight, minheight, dockminheight, dockminwidth, dockmaxheight, dockmaxwidth are optional\n\n\t// Default settings\n\tactions: { move: true, close: true, hide: true, resize: true, autosave: false, resizeX: true, resizeY: true },\n\t// GOOD TO KNOW:\n\t// \"undefined\" close or \"close:false\" and \"hide:true\" doesn't destroy the instance of panel (it will be hidden only)\n\n\t// A window title\n\ttitle: 'Title for the panel',\n\n\t// Handlers (optional):\n\tdestroy: function() {\n\t\t// @el {jQuery} a content of the window\n\t\t// @this {Instance}\n\n\t\t// is executed when the window is destroyed\n\t},\n\n\tmake: function(el) {\n\n\t\t// @el {jQuery} a content of the window\n\t\t// @this {Instance}\n\n\t\t// this.meta {Object} a reference to window object\n\t\t// this.element {jQuery} a content element of the window\n\t\t// this.container {jQuery} a main content element of the window\n\t\t// this.main {Object} a reference to \"Windows\" component\n\t\t// this.width {Number} a width of window\n\t\t// this.height {Number} a height of the content of the window\n\t\t// this.x {Number} offset X\n\t\t// this.y {Number} offset Y\n\n\t\t// Methods:\n\t\t// this.setsize(width, height);\n\t\t// this.setcommand(cmd); Allowed commands: \"close\", \"maximize\", \"resetmaximize\", \"togglemaximize\", \"minimize\", \"resetminimize\", \"toggleminimize\", \"resize\", \"move\", \"focus\"\n\t\t// this.setoffset(x, y);\n\n\t\t// is executed when the window is making\n\t},\n\n\tdata: function(type, data, el) {\n\n\t\t// @type {String}\n\t\t// @data {Object}\n\t\t// @el {jQuery} a content element of the window\n\t\t// @this {Instance}\n\n\t\t// is executed when data are sent via SETTER('windows', 'send', 'TYPE', 'DATA')\n\t},\n\n\tresize: function(width, height, el, x, y) {\n\n\t\t// @width {Number}\n\t\t// @height {Number}\n\t\t// @el {jQuery} a content of the window\n\t\t// @this {Instance}\n\n\t\t// is executed when the window is resized\n\t},\n\n\tmove: function(x, y, el) {\n\n\t\t// @x {Number}\n\t\t// @y {Number}\n\t\t// @el {jQuery} a content of the window\n\t\t// @this {Instance}\n\n\t\t// is executed when the window is moved\n\t},\n\n\tservice: function(counter, el) {\n\n\t\t// @counter {Number} count of calls\n\t\t// @el {jQuery} a content of the window\n\t\t// @this {Instance}\n\n\t\t// the component will execute this handler each 5 seconds\n\t}\n};\n\nPUSH('panels', panel);\n```\n\n__Methods__:\n\n- `component.finditem(panel_id)` finds the specific panel according to `ID`\n- __NEW__ `component.show(panel_id)` shows a panel\n- __NEW__ `component.hide(panel_id)` hides a panel\n- __NEW__ `component.toggle(panel_id)` toggles a panel\n\nUse it with `<ui-component name=\"dockable\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\tbody{padding:0;margin:0;overflow:hidden;}\n</style>\n\n<ui-component name=\"dockable\" path=\"mypanels\">\n\n\t<div class=\"padding\">\n\t\tYOUR CONTENT\n\t</div>\n\n</ui-component>\n\n<script>\n\n\tvar mypanels = [];\n\n\tmypanels.push({\n\t\tid: 'A',\n\t\toffset: { x: 200, y: 100, width: 250, height: 400, minwidth: 200, minheight: 200, docked: 'left' },\n\t\ttitle: 'Components',\n\t\thtml: '<div class=\"padding\">Lorem ipsum dolor sit amet.</div>',\n\t\tactions: { move: true, resize: true, close: false, dockable: 'left right bottom' }\n\t});\n\n\tmypanels.push({\n\t\tid: 'B',\n\t\toffset: { x: 600, y: 300, width: 250, height: 400, minwidth: 200, minheight: 200, docked: 'right' },\n\t\ttitle: 'Properties',\n\t\thtml: '<div class=\"padding\">Lorem ipsum dolor sit amet.</div>',\n\t\tactions: { move: true, resize: true, close: true, dockable: 'left right bottom' }\n\t});\n\n\tmypanels.push({\n\t\tid: 'C',\n\t\toffset: { x: 600, y: 300, width: 300, height: 200, minwidth: 200, minheight: 200, docked: 'bottom' },\n\t\ttitle: 'Console',\n\t\thtml: '<div class=\"padding\">Lorem ipsum dolor sit amet.</div>',\n\t\tactions: { move: true, resize: true, close: true, dockable: 'right bottom' }\n\t});\n\n</script>\n```"}],"knowledge":{"content_sha256":"f10964828307c6a7e2b210407fd64d649bd67202eaa0b782fe43ab419aa61341","source_sha256":"5d109dd1413a2481a0b92e5b5aa354f7aebeb3a12452e7b5b18c13730aca42ff","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","CONTENT","DATA","DEF","GOOD","HTMLE","KNOW","MIT","PANEL","PANEL_ID","PUSH","SETTER","THE","TYPE","YOUR"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\tbody{padding:0;margin:0;overflow:hidden;}\n</style>\n\n<ui-component name=\"dockable\" path=\"mypanels\">\n\n\t<div class=\"padding\">\n\t\tYOUR CONTENT\n\t</div>\n\n</ui-component>\n\n<script>\n\n\tvar mypanels = [];\n\n\tmypanels.push({\n\t\tid: 'A',\n\t\toffset: { x: 200, y: 100, width: 250, height: 400, minwidth: 200, minheight: 200, docked: 'left' },\n\t\ttitle: 'Components',\n\t\thtml: '<div class=\"padding\">Lorem ipsum dolor sit amet.</div>',\n\t\tactions: { move: true, resize: true, close: false, dockable: 'left right bottom' }\n\t});\n\n\tmypanels.push({\n\t\tid: 'B',\n\t\toffset: { x: 600, y: 300, width: 250, height: 400, minwidth: 200, minheight: 200, docked: 'right' },\n\t\ttitle: 'Properties',\n\t\thtml: '<div class=\"padding\">Lorem ipsum dolor sit amet.</div>',\n\t\tactions: { move: true, resize: true, close: true, dockable: 'left right bottom' }\n\t});\n\n\tmypanels.push({\n\t\tid: 'C',\n\t\toffset: { x: 600, y: 300, width: 300, height: 200, minwidth: 200, minheight: 200, docked: 'bottom' },\n\t\ttitle: 'Console',\n\t\thtml: '<div class=\"padding\">Lorem ipsum dolor sit amet.</div>',\n\t\tactions: { move: true, resize: true, close: true, dockable: 'right bottom' }\n\t});\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/donutchart","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/donutchart/","markdown_url":"https://forge.totaljs.es/en/components/donutchart.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"907b7d327650e5f0d59e4e0bb4c700e9806ab2b39a1c57856e9c3dc83c510a8a","checked_at":"2026-10-20T12:20:41.556Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-DonutChart","description":"j-DonutChart options: tooltip, format, presentation, select, redraw, animate, size, highlight","messages":[{"role":"user","content":"j-DonutChart\n\nj-DonutChart options: tooltip, format, presentation, select, redraw, animate, size, highlight"},{"role":"assistant","content":"__Configuration__:\n\n- `tooltip` {Boolean} enables tooltip (default: `true`)\n- `format` {String} a template (Tangular) for rendering tooltip (default: `{{ value | format(0) }}`)\n- `presentation` {Boolean} enables presentation mode (default: `true`)\n- `select` {String} a path to method if a `value` is selected\n- `redraw` {String} a path to method if the chart is redrawn\n- `animate` {Boolean} enables/disables animations (default: `false`)\n- `size` {Number/String} width/height of donut (default: `0` = auto) or `string` is meant as a parent element\n- `highlight` {Boolean} enables/disables highlight of pieces (default: `true`)\n- `margin` {Number} adds a margin to `size` (default: `0`)\n\nUse it with `<ui-component name=\"donutchart\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div class=\"container\">\n\t<div class=\"row\">\n\t\t<div class=\"col-xs-6\">\n\t\t\t<ui-component name=\"donutchart\" path=\"values\"></ui-component>\n\t\t</div>\n\t\t<div class=\"col-xs-6\">\n\t\t\t<div class=\"row\">\n\t\t\t\t<div class=\"col-xs-6\">\n\t\t\t\t\t<ui-component name=\"donutchart\" path=\"values\"></ui-component>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"col-xs-6\">\n\t\t\t\t\t<ui-component name=\"donutchart\" path=\"values\"></ui-component>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<br />\n\t\t\t<div class=\"row\">\n\t\t\t\t<div class=\"col-xs-6\">\n\t\t\t\t\t<ui-component name=\"donutchart\" path=\"values\"></ui-component>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"col-xs-6\">\n\t\t\t\t\t<ui-component name=\"donutchart\" path=\"values\" config=\"animate:false\"></ui-component>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</div>\n</div>\n\n<script>\n\tvar values = [{ name: 'A', value: 10 }, { name: 'B', value: 4 }, { name: 'C', value: 20 }, { name: 'D', value: 8 }];\n</script>\n```"}],"knowledge":{"content_sha256":"a85060c929120e55e55dc8d15d00663b21c8e2391e868f7c15e74aef9c85871a","source_sha256":"907b7d327650e5f0d59e4e0bb4c700e9806ab2b39a1c57856e9c3dc83c510a8a","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div class=\"container\">\n\t<div class=\"row\">\n\t\t<div class=\"col-xs-6\">\n\t\t\t<ui-component name=\"donutchart\" path=\"values\"></ui-component>\n\t\t</div>\n\t\t<div class=\"col-xs-6\">\n\t\t\t<div class=\"row\">\n\t\t\t\t<div class=\"col-xs-6\">\n\t\t\t\t\t<ui-component name=\"donutchart\" path=\"values\"></ui-component>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"col-xs-6\">\n\t\t\t\t\t<ui-component name=\"donutchart\" path=\"values\"></ui-component>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t\t<br />\n\t\t\t<div class=\"row\">\n\t\t\t\t<div class=\"col-xs-6\">\n\t\t\t\t\t<ui-component name=\"donutchart\" path=\"values\"></ui-component>\n\t\t\t\t</div>\n\t\t\t\t<div class=\"col-xs-6\">\n\t\t\t\t\t<ui-component name=\"donutchart\" path=\"values\" config=\"animate:false\"></ui-component>\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</div>\n\t</div>\n</div>\n\n<script>\n\tvar values = [{ name: 'A', value: 10 }, { name: 'B', value: 4 }, { name: 'C', value: 20 }, { name: 'D', value: 8 }];\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/dragdropelement","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/dragdropelement/","markdown_url":"https://forge.totaljs.es/en/components/dragdropelement.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"21369f7088567f817ea376dc7885b99bd2ef30490f6db58f637ab2bcb9b283d2","checked_at":"2026-10-20T12:20:42.439Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-DragDropElement","description":"On drag event displays modal inside given element with text Drop & drop files here. On moving dragged files away from element or dropping dragged files modal will hide. Also on drop event exec functio","messages":[{"role":"user","content":"j-DragDropElement\n\nOn drag event displays modal inside given element with text Drop & drop files here. On moving dragged files away from element or dropping dragged files modal will hide. Also on drop event exec functio"},{"role":"assistant","content":"On `drag` event displays modal inside given element with text `Drop & drop files here`. On moving dragged files away from element or dropping dragged files modal will hide. Also on `drop` event `exec` function is executed.\n\n__Configuration__\n\n- `exec {String}` the component executes this method if the files are dropped - `function(files, e, component)`\n- `text {String}` text to display (default `empty`)\n\n__Example__\n\n`<ui-component name=\"dragdropelement\" config=\"exec:console.log;\"></ui-component>`\n\n__Author__\n\n- Michal Capo <capo@dasolutions.sk> and Marek Dorotovič <marek@totaljs.com>\n- [License](https://www.totaljs.com/license/)\n\nUse it with `<ui-component name=\"dragdropelement\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Michal Capo. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"dragdropelement\" config=\"exec:console.log;\">\n\t<button>Upload (drag &amp; drop file)</button>\n</ui-component>\n```"}],"knowledge":{"content_sha256":"807981153ff3aa03435fd846f8ecd4c24e27b43515d6e84c0d196623f9e40e84","source_sha256":"21369f7088567f817ea376dc7885b99bd2ef30490f6db58f637ab2bcb9b283d2","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"dragdropelement\" config=\"exec:console.log;\">\n\t<button>Upload (drag &amp; drop file)</button>\n</ui-component>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/dragdropfiles","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/dragdropfiles/","markdown_url":"https://forge.totaljs.es/en/components/dragdropfiles.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"26ef8411aceaff4fa5c534dd8cb782308b3e02d00ebd57234578bc7a31a574a5","checked_at":"2026-10-20T12:20:43.328Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-DragDropFiles","description":"j-DragDropFiles options: exec, class, click, accept","messages":[{"role":"user","content":"j-DragDropFiles\n\nj-DragDropFiles options: exec, class, click, accept"},{"role":"assistant","content":"__Configuration__:\n\n- `exec {String}` - the component executes this method if the files are dropped\n- `class {String}` - toggles class if the drag over is emitted\n- `click {Boolean}` - causes open file dialog when the user click on the element (default: `true`)\n- `accept {String}` - change accept attribute, e.g to only accept pdfs set to `application/pdf` (default: `*/*`)\n\nUse it with `<ui-component name=\"dragdropfiles\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"dragdropfiles\" path=\"null\" config=\"exec:dragdropfiles_files;accept:application/pdf;\">\n\t<span>Drag &amp; Drop files here</span>\n</ui-component>\n\n<script>\n\tfunction dragdropfiles_files(files, evt) {\n\t\tconsole.log(files);\n\t\t/*\n\n\t\tvar data = new FormData();\n\t\tfor (var i = 0, length = files.length; i < length; i++)\n\t\t\tdata.append('file' + i, files[i]);\n\n\t\tUPLOAD('/api/upload/', data, function(response) {\n\t\t\tconsole.log(response);\n\t\t});\n\n\t\t*/\n\t}\n</script>\n```"}],"knowledge":{"content_sha256":"8c46d30337241db63e47997bf358136476873eecbb21e1aadb7c548d9d9feef6","source_sha256":"26ef8411aceaff4fa5c534dd8cb782308b3e02d00ebd57234578bc7a31a574a5","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","UPLOAD"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"dragdropfiles\" path=\"null\" config=\"exec:dragdropfiles_files;accept:application/pdf;\">\n\t<span>Drag &amp; Drop files here</span>\n</ui-component>\n\n<script>\n\tfunction dragdropfiles_files(files, evt) {\n\t\tconsole.log(files);\n\t\t/*\n\n\t\tvar data = new FormData();\n\t\tfor (var i = 0, length = files.length; i < length; i++)\n\t\t\tdata.append('file' + i, files[i]);\n\n\t\tUPLOAD('/api/upload/', data, function(response) {\n\t\t\tconsole.log(response);\n\t\t});\n\n\t\t*/\n\t}\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/draggable","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/draggable/","markdown_url":"https://forge.totaljs.es/en/components/draggable.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"75e7299456022139bba43cd9ecc727ae7618c28095a24c6e053f090c3297d84c","checked_at":"2026-10-20T12:20:44.067Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Draggable","description":"A simple component which can capture drag & drop operation.","messages":[{"role":"user","content":"j-Draggable\n\nA simple component which can capture drag & drop operation."},{"role":"assistant","content":"A simple component which can capture drag & drop operation.\n\n\n__Configuration__:\n\n- `selector {String}` jQuery selector for capturing of element for \"drag\"\n- `exec {String}` a path to method `function(meta, el)`\n\n```javascript\nfunction method(meta, el) {\n\t// meta.pageX;    {Number}\n\t// meta.pageY;    {Number}\n\t// meta.offsetX;  {Number}\n\t// meta.offsetY;  {Number}\n\t// meta.el;       {jQuery element} dragged element\n\t// meta.target;   {jQuery element} target (where the element has been dropped?)\n}\n```\n\nUse it with `<ui-component name=\"draggable\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ul>\n\t<li draggable=\"true\">Drag me: A</li>\n\t<li draggable=\"true\">Drag me: B</li>\n\t<li draggable=\"true\">Drag me: C</li>\n\t<li draggable=\"true\">Drag me: D</li>\n\t<li draggable=\"true\">Drag me: E</li>\n</ul>\n\n<ui-component name=\"draggable\" config=\"selector:li;exec:showinfo\" style=\"line-height:300px;background-color:#F0F0F0;text-align:center;\">\n\tOpen Web Developer console\n</ui-component>\n\n<script>\n\tfunction showinfo(meta, el) {\n\t\tconsole.log(meta);\n\t}\n</script>\n```"}],"knowledge":{"content_sha256":"8fca58b1186c96c0f6b49444c2e4e57370fc724c4caf273bb5c2bbb8b144c384","source_sha256":"75e7299456022139bba43cd9ecc727ae7618c28095a24c6e053f090c3297d84c","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","F0F0F0","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ul>\n\t<li draggable=\"true\">Drag me: A</li>\n\t<li draggable=\"true\">Drag me: B</li>\n\t<li draggable=\"true\">Drag me: C</li>\n\t<li draggable=\"true\">Drag me: D</li>\n\t<li draggable=\"true\">Drag me: E</li>\n</ul>\n\n<ui-component name=\"draggable\" config=\"selector:li;exec:showinfo\" style=\"line-height:300px;background-color:#F0F0F0;text-align:center;\">\n\tOpen Web Developer console\n</ui-component>\n\n<script>\n\tfunction showinfo(meta, el) {\n\t\tconsole.log(meta);\n\t}\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/drawer","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/drawer/","markdown_url":"https://forge.totaljs.es/en/components/drawer.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"ae5bec0ef6e64126f542ddb04d6df46f94fed285f420e51e366bd1dceb46c632","checked_at":"2026-10-20T12:20:44.936Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Drawer","description":"j-Drawer: NEW: width {Number} a default width (default: 250); NEW: widthlg {Number} optional, width for large displays","messages":[{"role":"user","content":"j-Drawer\n\nj-Drawer: NEW: width {Number} a default width (default: 250); NEW: widthlg {Number} optional, width for large displays"},{"role":"assistant","content":"- `if {String}` condition, it's compared with the value within of `path`\n- `align {String}` alignment (default: `left`)\n- __NEW__: `width {Number}` a default width (default: `250`)\n- __NEW__: `widthlg {Number}` optional, width for large displays\n- __NEW__: `widthmd {Number}` optional, width for medium displays\n- __NEW__: `widthsm {Number}` optional, width for small displays\n- __NEW__: `widthxs {Number}` optional, width for extra small displays\n\nUse it with `<ui-component name=\"drawer\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"drawer\" path=\"common.menu\" config=\"if:main;align:left\" class=\"hidden\">\n\t<button><i class=\"ti ti-inbox\"></i>Inbox</button>\n\t<button class=\"selected\"><i class=\"ti ti-star\"></i>Starred</button>\n\t<button><i class=\"ti ti-envelope\"></i>Send mail</button>\n\t<hr />\n\t<a href=\"#\"><i class=\"ti ti-trash\"></i>Trash</a>\n</ui-component>\n\n<script>\n\tvar common = { menu: 'main' };\n</script>\n```"}],"knowledge":{"content_sha256":"3c84e9e0cf1f4338f3e093d5a50d674f3a9f2e4238347d600254c2843a60a460","source_sha256":"ae5bec0ef6e64126f542ddb04d6df46f94fed285f420e51e366bd1dceb46c632","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"drawer\" path=\"common.menu\" config=\"if:main;align:left\" class=\"hidden\">\n\t<button><i class=\"ti ti-inbox\"></i>Inbox</button>\n\t<button class=\"selected\"><i class=\"ti ti-star\"></i>Starred</button>\n\t<button><i class=\"ti ti-envelope\"></i>Send mail</button>\n\t<hr />\n\t<a href=\"#\"><i class=\"ti ti-trash\"></i>Trash</a>\n</ui-component>\n\n<script>\n\tvar common = { menu: 'main' };\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/drawzone","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/drawzone/","markdown_url":"https://forge.totaljs.es/en/components/drawzone.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"4ea15667e6cb21c161df23ed32118cd684817b7a4ffd0f1adbfed6d6b1b450bc","checked_at":"2026-10-20T12:20:46.128Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-DrawZone","description":"The component can draw a zone (polygon). This component uses [OpenLayers](https://openlayers.org/) library with the [OpenStreetMap](https://www.openstreetmap.org/) map engine.","messages":[{"role":"user","content":"j-DrawZone\n\nThe component can draw a zone (polygon). This component uses [OpenLayers](https://openlayers.org/) library with the [OpenStreetMap](https://www.openstreetmap.org/) map engine."},{"role":"assistant","content":"The component can draw a zone (polygon). This component uses [OpenLayers](https://openlayers.org/) library with the [OpenStreetMap](https://www.openstreetmap.org/) map engine.\n\n\n__Configuration__:\n\n- `parent {String}` a parent element (default: `undefined`)\n- `height {Number}` a fixed (minimal) height (default: `200`)\n- `color {String}` a default color for polygon (default: `#fcba03`)\n- `zoom {Number}` a default zoom level (default: `11`)\n- `readonly {Boolean}` enables readonly mode (default: `false`)\n- `stroke {Number}` a stroke width (default: `2`)\n- `radius {Number}` a radius size of points in the polygon (default: `7`)\n- `center {String}` a default map position (default: `48.73702478789267,19.137712002562715`)\n- `opacity {Number}` determines the transparency of the polygon in percentage (default: `40`)\n- `required {Boolean}` optional (default: `false`)\n- `filter {String}` optional, CSS filter e.g. `filter:grayscale(90%) invert(100%)` (default: `null`)\n- __NEW__: `exec {String}` optional, a link to the `function(value, component)` (it will be executed when the value is changed)\n\n__Data-source__ needs to contain:\n\n```js\n{\n    points: [{ lat: 48.75152530164132, lng: 19.13902142954625 }, { lat: 48.726826662905296, lng: 19.123238694044034 }, ...] // polygon points\n    color: '#FD391A', // optional\n    zoom: 13 // optional\n}\n```\n\n__Methods__:\n\n- `component.clear([draw])` clears the polygon `@draw {Boolean}` (default: `false`)\n- `component.move(lat, lng)` moves the map to the specific position\n- `component.export(opt, callback(base64))` exports data into the picture\n    - `opt.width {Number}` required\n    - `opt.height {Number}` required\n    - `opt.zoom {Number}` (default: `13`)\n    - `opt.type {png|jpg}` (default: `png`)\n    - `opt.quality {Number}` only for JPEG\n    - `opt.points {Array Number}`\n    - `opt.color {String}` (default: `config.color`)\n    - `opt.radius {Number}` (default: `config.radius`)\n    - `opt.opacity {Number}` (default: `config.opacity`)\n\n__Output__:\n\n- is extended by `center { lat: Number, lng: Number }` property\n\n__Good to know__:\n\n- `double click` on vertices (created point) will delete selected point\n\nUse it with `<ui-component name=\"drawzone\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Sirka & Dodo Marton. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\tbody { margin: 0; padding: 0; }\n</style>\n\n<ui-component name=\"drawzone\" path=\"map\" config=\"parent:window;color:#FD391A;geolocation:0\"></ui-component>\n\n<script>\n\tvar map = { \n\t\tpoints: []\n\t};\n</script>\n```"}],"knowledge":{"content_sha256":"25636de0932ae5cd9b1bf1ba4bdc00241b7046615bf38ca8cfad6301d3a9b957","source_sha256":"4ea15667e6cb21c161df23ed32118cd684817b7a4ffd0f1adbfed6d6b1b450bc","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","CSS","DEF","FD391A","JPEG","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\tbody { margin: 0; padding: 0; }\n</style>\n\n<ui-component name=\"drawzone\" path=\"map\" config=\"parent:window;color:#FD391A;geolocation:0\"></ui-component>\n\n<script>\n\tvar map = { \n\t\tpoints: []\n\t};\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/dropdown","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/dropdown/","markdown_url":"https://forge.totaljs.es/en/components/dropdown.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"240f521ac6102407831e3345783a5b603273d0af1c9f0a7ace9e7a3e1ccebf07","checked_at":"2026-10-20T12:20:47.014Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Dropdown","description":"j-Dropdown options: required, items, icon, label, type, datasource, text, value","messages":[{"role":"user","content":"j-Dropdown\n\nj-Dropdown options: required, items, icon, label, type, datasource, text, value"},{"role":"assistant","content":"__Configuration__:\n\n- `required {Boolean}` (optional) enables \"required\" (default: `false`)\n- `items {String}` (optional) static items `key1|value1, key2 with value, key3|value`\n- `icon {String}` (optional) icon for label e.g. `home`, `cog`, etc.\n- `label {String}` (optional) label (default is HTML content)\n- `type {String}` (optional) can be `number` (converts `String` values to `Number`)\n- `datasource {String}` path to data-source (must be array)\n- `text {String}` determines a property name for text (in data-source), default: `name`\n- `value {String}` determines a property name for value (in data-source) default: `id`\n- `empty {String}` (optional) adds an empty field\n- `disabled {Boolean}` (optional) disables this component\n- `if {String}` (optional) arrow function as a condition for filtering of datasource, example: `if:n => !n.removed`\n\nUse it with `<ui-component name=\"dropdown\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"width:400px\">\n\t<ui-component name=\"dropdown\" path=\"form.idcategory\" config=\"datasource:datasource;empty:;required:true;type:number;icon:navicon\">Category</ui-component>\n\t<br />\n\t<ui-component name=\"dropdown\" path=\"form.idinline\" config=\"items:,A|1,B|2,C|3\">Inline options</ui-component>\n</div>\n\n<script>\n\tvar datasource = [{ name: 'A', id: 1 }, { name: 'B', id: 2 }, { name: 'C', id: 3 }];\n\tvar form = {};\n</script>\n```"}],"knowledge":{"content_sha256":"bd49cbc7b3fecf4d860c76177f51ccdc5308e5595fd79d4ec22592be00a7fda0","source_sha256":"240f521ac6102407831e3345783a5b603273d0af1c9f0a7ace9e7a3e1ccebf07","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","HTML","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"width:400px\">\n\t<ui-component name=\"dropdown\" path=\"form.idcategory\" config=\"datasource:datasource;empty:;required:true;type:number;icon:navicon\">Category</ui-component>\n\t<br />\n\t<ui-component name=\"dropdown\" path=\"form.idinline\" config=\"items:,A|1,B|2,C|3\">Inline options</ui-component>\n</div>\n\n<script>\n\tvar datasource = [{ name: 'A', id: 1 }, { name: 'B', id: 2 }, { name: 'C', id: 3 }];\n\tvar form = {};\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/dropdowncheckbox","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/dropdowncheckbox/","markdown_url":"https://forge.totaljs.es/en/components/dropdowncheckbox.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"2b04de3309746855e4811e426d09fde7bbca9cfd611c693d5b048d5156784979","checked_at":"2026-10-20T12:20:47.759Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-DropdownCheckbox","description":"j-DropdownCheckbox: NEW now supports dark mode","messages":[{"role":"user","content":"j-DropdownCheckbox\n\nj-DropdownCheckbox: NEW now supports dark mode"},{"role":"assistant","content":"- __NEW__ now supports dark mode\n\n\n__Configuration__:\n\n- `required {Boolean}` (optional) enables \"required\" (default: `false`)\n- `items {String}` (optional) static items `key1|value1, key2 with value, key3|value`\n- `icon {String}` (optional) icon for label without `ti-` e.g. `home`, `cog`, etc.\n- `checkicon {String}` (optional) check icon without `ti-`, (default: `check`).\n- `label {String}` (optional) label (default is HTML content)\n- `datasource {String}` path to data-source (must be array)\n- `text {String}` determines a property name for text (in data-source), default: `name`\n- `value {String}` determines a property name for value (in data-source) default :`id`\n- `disabled {Boolean}` (optional) disables this component\n- `cleaner {Boolean}` (optional) removes non-exist values (default: `true`)\n- `limit {Number}` (optional) sets a limit for selected items (default: `0`)\n- `visible {Number}` (optional) sets a maximum visible items then show `X selected` (default: `0`)\n- `selectedtext {String}` (optional) sets a message for maximum visible items e.g. `{0} selected from {1}` (default: `{0} selected`)\n- `alltext {String}` (optional) when all items are selected this is showed (default: `All selected` you can disable it with `null`)\n- `placeholder {String}` (optional) sets a text for placeholder\n\nUse it with `<ui-component name=\"dropdowncheckbox\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"width:200px\">\n\t<ui-component name=\"dropdowncheckbox\" path=\"selected\" config=\"required:true;type:number;items:Text 1|0,Text 2|1,Text 3|2,Text 4|3,Text 5|4,Text 6|5;visible:3\">My label</ui-component>\n</div>\n<div style=\"width:200px;margin-top:30px\">\n\t<ui-component name=\"dropdowncheckbox\" path=\"selected2\" config=\"placeholder:Placeholder;type:number;items:Text 1|0,Text 2|1,Text 3|2,Text 4|3,Text 5|4,Text 6|5;visible:3\">My label</ui-component>\n</div>\n```"}],"knowledge":{"content_sha256":"c4b6324f743c342871595181cafc79c894f3f64626e8ce80d3a1e1a28955c0cd","source_sha256":"2b04de3309746855e4811e426d09fde7bbca9cfd611c693d5b048d5156784979","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","HTML","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"width:200px\">\n\t<ui-component name=\"dropdowncheckbox\" path=\"selected\" config=\"required:true;type:number;items:Text 1|0,Text 2|1,Text 3|2,Text 4|3,Text 5|4,Text 6|5;visible:3\">My label</ui-component>\n</div>\n<div style=\"width:200px;margin-top:30px\">\n\t<ui-component name=\"dropdowncheckbox\" path=\"selected2\" config=\"placeholder:Placeholder;type:number;items:Text 1|0,Text 2|1,Text 3|2,Text 4|3,Text 5|4,Text 6|5;visible:3\">My label</ui-component>\n</div>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/dropdownlist","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/dropdownlist/","markdown_url":"https://forge.totaljs.es/en/components/dropdownlist.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"a435147f03b69683ee8f32868c6443faf19d6befe30e114bc000a34108f20160","checked_at":"2026-10-20T12:20:48.644Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-DropdownList","description":"j-DropdownList options: required, datasource, limit, disabled, dirplaceholder, dirsearch, check","messages":[{"role":"user","content":"j-DropdownList\n\nj-DropdownList options: required, datasource, limit, disabled, dirplaceholder, dirsearch, check"},{"role":"assistant","content":"__Configuration__:\n\n- `required {Boolean}` enables validation (default: `false`)\n- `datasource {String}` a link to the `path {Array}` or inline defined items `key1|value1,key2 with value,key3|value`\n- `limit {Number}` a maximum count of selected items (default: `0` disabled)\n- `disabled {Boolean}` disables component\n- `dirplaceholder {String}` placeholder for `j-Directory`\n- `dirsearch {Boolean}` disables searching in `j-Directory` (default: `false`)\n- `check {Boolean}` compares selected values with datasource (default: `false`)\n\nUse it with `<ui-component name=\"dropdownlist\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"LAZY directory\" config=\"placeholder:Search\"></ui-component>\n\n<div style=\"width:400px\">\n\t<ui-component name=\"dropdownlist\" path=\"form.categoryid\" config=\"datasource:datasource;required:true\">Category</ui-component>\n\t<br />\n\t<ui-component name=\"dropdownlist\" path=\"form.inlineid\" config=\"datasource:a|A,b|B,c|C\">Inline options</ui-component>\n</div>\n\n<script>\n\tvar datasource = [{ name: 'A', id: 1 }, { name: 'B', id: 2 }, { name: 'C', id: 3 }];\n\tvar form = {};\n</script>\n```"}],"knowledge":{"content_sha256":"1db79c7af8640781c39b6ac3e24f56923b5e6d9f2573a25b397d802063937a70","source_sha256":"a435147f03b69683ee8f32868c6443faf19d6befe30e114bc000a34108f20160","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","LAZY","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"LAZY directory\" config=\"placeholder:Search\"></ui-component>\n\n<div style=\"width:400px\">\n\t<ui-component name=\"dropdownlist\" path=\"form.categoryid\" config=\"datasource:datasource;required:true\">Category</ui-component>\n\t<br />\n\t<ui-component name=\"dropdownlist\" path=\"form.inlineid\" config=\"datasource:a|A,b|B,c|C\">Inline options</ui-component>\n</div>\n\n<script>\n\tvar datasource = [{ name: 'A', id: 1 }, { name: 'B', id: 2 }, { name: 'C', id: 3 }];\n\tvar form = {};\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/dropfiles","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/dropfiles/","markdown_url":"https://forge.totaljs.es/en/components/dropfiles.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"9c61a7eebd10069a57917db1ff85b61450e5696734027e0ea044d359ca2c070a","checked_at":"2026-10-20T12:20:49.520Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-DropFiles","description":"j-DropFiles: singleton; captures all dropped files into the window","messages":[{"role":"user","content":"j-DropFiles\n\nj-DropFiles: singleton; captures all dropped files into the window"},{"role":"assistant","content":"- singleton\n- captures all dropped files into the `window`\n- must be located under `<body>` element due to positioning\n\n__Configuration__:\n\n- `exec {String}` a link to the `function(files, e)`\n- __NEW__: `check {String}` optional, a link to the `function(e)` that must return `true` (allows drag&drop) or `false` (disallow)\n\nUse it with `<ui-component name=\"dropfiles\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"dropfiles\" config=\"exec:dropfiles\" class=\"hidden\">\n\tDrag &amp; Drop files here\n</ui-component>\n\n<script>\n\tfunction dropfiles(files, evt) {\n\n\t\tconsole.log(files, evt);\n\n\t\t/*\n\n\t\tvar data = new FormData();\n\t\tfor (var i = 0, length = files.length; i < length; i++)\n\t\t\tdata.append('file' + i, files[i]);\n\n\t\tUPLOAD('/api/upload/', data, function(response) {\n\t\t\tconsole.log(response);\n\t\t});\n\n\t\t*/\n\t}\n</script>\n```"}],"knowledge":{"content_sha256":"8d44e9b9333463962ac80fae9ad54e6343facd02fc223b621298302d10da2402","source_sha256":"9c61a7eebd10069a57917db1ff85b61450e5696734027e0ea044d359ca2c070a","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","UPLOAD"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"dropfiles\" config=\"exec:dropfiles\" class=\"hidden\">\n\tDrag &amp; Drop files here\n</ui-component>\n\n<script>\n\tfunction dropfiles(files, evt) {\n\n\t\tconsole.log(files, evt);\n\n\t\t/*\n\n\t\tvar data = new FormData();\n\t\tfor (var i = 0, length = files.length; i < length; i++)\n\t\t\tdata.append('file' + i, files[i]);\n\n\t\tUPLOAD('/api/upload/', data, function(response) {\n\t\t\tconsole.log(response);\n\t\t});\n\n\t\t*/\n\t}\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/duplicator","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/duplicator/","markdown_url":"https://forge.totaljs.es/en/components/duplicator.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"f76ac8f87805bbe790815af5f7b7194c6e3c043e5057cdd1b7a671c0a8cc2b67","checked_at":"2026-10-20T12:20:50.403Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Duplicator","description":"BETA VERSION. This component is a bit special component because it can duplicate same content several times with diffferent data-source. Imaging some text editor with tabs which each tab contains diff","messages":[{"role":"user","content":"j-Duplicator\n\nBETA VERSION. This component is a bit special component because it can duplicate same content several times with diffferent data-source. Imaging some text editor with tabs which each tab contains diff"},{"role":"assistant","content":"__BETA VERSION__. This component is a bit special component because it can duplicate same content several times with diffferent data-source. Imaging some text editor with tabs which each tab contains different content. For this case is targeted `j-Duplicator`. Follow example.\n\n__Configuration__:\n\n- `datasource` {Array} must contain array of content for duplicating\n- `url` {String} optional, can contain a link to external `template` __without `JavaScript`__\n\n__Good to know__:\nThis component appends `.scopename()` function to each item of Array for handling of scope and each content is wrapped in to the independent `scope` with direct reference to main source.\n\n### Advanced templating\n\n```html\n<ui-component name=\"duplicator\" path=\"mytab\" config=\"datasource:tabs\" class=\"invisible\">\n\n\t<script type=\"text/html\" data-id=\"PRODUCT\">\n\t\t<ui-component name=\"input\" path=\"?.name\">Name</ui-component>\n\t</script>\n\n\t<script type=\"text/html\" data-id=\"USER\">\n\t\t<ui-component name=\"input\" path=\"?.age\">Age</ui-component>\n\t</script>\n\n\t<!-- DYNAMIC -->\n\t<script type=\"text/html\" data-id=\"ORDER\" data-url=\"/templates/orders.html\"></script>\n</ui-component>\n\n<script>\n\n\t// All tabs\n\tvar tabs = [];\n\ttabs.push({ template: 'PRODUCT', name: 'iPhone X' });\n\ttabs.push({ template: 'USER', age: 30 });\n\ttabs.push({ template: 'ORDER', price: 324 });\n\n\t// Choose a default open tab\n\tvar mytab = tabs[0];\n\n</script>\n```\n\nUse it with `<ui-component name=\"duplicator\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-bind path=\"tabs\" config=\"template;click span:choosetab\" class=\"block\">\n\t<script type=\"text/html\">\n\t\t{{ foreach m in value }}\n\t\t\t<span data-index=\"{{ $index }}\" class=\"mr5 link\">{{ $index + 1 }}</span>\n\t\t{{ end }}\n\t</script>\n</ui-bind>\n\n<h2>Duplicator</h2>\n\n<ui-component name=\"duplicator\" path=\"mytab\" config=\"datasource:tabs\" class=\"invisible\">\n\t<script type=\"text/html\">\n\t\t<ui-component name=\"input\" path=\"?.name\">Name</ui-component>\n\t</script>\n</ui-component>\n\n<script>\n\n\tvar tabs = [];\n\ttabs.push({ name: 'Peter' });\n\ttabs.push({ name: 'Lucka' });\n\ttabs.push({ name: 'Betka' });\n\n\tfunction choosetab(el) {\n\t\tvar index = +el.attrd('index');\n\t\tSET('mytab', tabs[index]);\n\t}\n\n\t// Default tab\n\tSET('mytab', tabs[0]);\n\n</script>\n```"}],"knowledge":{"content_sha256":"7046aec278139d27347129333f907c179e69165264a31901d002256e9dc66fc8","source_sha256":"f76ac8f87805bbe790815af5f7b7194c6e3c043e5057cdd1b7a671c0a8cc2b67","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","DYNAMIC","MIT","ORDER","PRODUCT","SET","USER","VERSION__"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-bind path=\"tabs\" config=\"template;click span:choosetab\" class=\"block\">\n\t<script type=\"text/html\">\n\t\t{{ foreach m in value }}\n\t\t\t<span data-index=\"{{ $index }}\" class=\"mr5 link\">{{ $index + 1 }}</span>\n\t\t{{ end }}\n\t</script>\n</ui-bind>\n\n<h2>Duplicator</h2>\n\n<ui-component name=\"duplicator\" path=\"mytab\" config=\"datasource:tabs\" class=\"invisible\">\n\t<script type=\"text/html\">\n\t\t<ui-component name=\"input\" path=\"?.name\">Name</ui-component>\n\t</script>\n</ui-component>\n\n<script>\n\n\tvar tabs = [];\n\ttabs.push({ name: 'Peter' });\n\ttabs.push({ name: 'Lucka' });\n\ttabs.push({ name: 'Betka' });\n\n\tfunction choosetab(el) {\n\t\tvar index = +el.attrd('index');\n\t\tSET('mytab', tabs[index]);\n\t}\n\n\t// Default tab\n\tSET('mytab', tabs[0]);\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/dynamicvalue","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/dynamicvalue/","markdown_url":"https://forge.totaljs.es/en/components/dynamicvalue.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"0df586d9a046a9da357ace162ff1301835ce30d5e9233c8738ffe43d3e0edb38","checked_at":"2026-10-20T12:20:51.149Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-DynamicValue","description":"This component is very special component. It can keep a value for example Number and at the same it can render another readable text. This component is optimized for dynamic usage, so it needs to be l","messages":[{"role":"user","content":"j-DynamicValue\n\nThis component is very special component. It can keep a value for example Number and at the same it can render another readable text. This component is optimized for dynamic usage, so it needs to be l"},{"role":"assistant","content":"This component is very special component. It can keep a value for example `Number` and at the same it can render another readable `text`. This component is optimized for dynamic usage, so it needs to be linked with another component or part.\n\n\n- `icon {String}` icon for label e.g. home, cog, etc.\n- `label {String}` label (default is HTML content)\n- `icon2 {String}` icon in the right box e.g. home, cog, etc. (default: `angle-down`)\n- `placeholder {String}` adds a placeholder text\n- `disabled {Boolean}` disables this component\n- `find {String}` __important__ link to `function(el, next(value))` for binding a new value\n- `read {String}` a link to `function(value, next(response))` for binding a readable `text`\n- `url {String}` tries to bind a value via `AJAX()`, argument `{0}` in URL is replaced by the value\n- `html {String}` Tangular template for rendering a value (default: `{{ name }}`)\n- `remap {String}` a remap function (default: `null`), example: `value.length ? value[0] : null`\n- `required {Boolean}` enables \"required\" (default: `false`)\n- `bind {String}` a path to method or variable where will be binded loaded value (optional)\n- `dirsource {String}` a link to method `function(search, next(items_arr))` or __NEW__: can contain URL for search in fhe form `GET /api/partners/?q={0}`\n- `dircustom {String/Boolean}` can contain a path to function(val, next(new_val)) or can be Boolean. This option can enable adding a custom value (value not defined in data-source)\n- `dirrender {String}` a path to `function(item, text)` (must return HTML for j-Directory), this function can affect list of items in j-Directory\n- `dirminwidth {Number}` a minimum width for j-Directory, (default: `200`)\n- `dirmaxwidth {Number}` a maximum width for j-Directory\n- `dirplaceholder {String}` a placeholder for j-Directory\n- `dirempty {String}` adds an empty field for j-Directory\n- `dirkey {String}` a key name for reading of text in dirsource (default: `name`)\n- `dirvalue {String}` a key name for reading of value in dirsource (default: `id`)\n- `onresponse {String}` a path to property or `function(response)`, executed if the response is processed (default: `undefined`)\n- __NEW__ `tapi {Boolean}` enables calling `TAPI()` insteadof `AJAX()` (default: false)\n\n### AJAX usage\n\nIf the `value` will be changed then the component performs `AJAX` call automatically.\n\n```html\n<ui-component name=\"dynamicvalue\" path=\"path.to.property\" config=\"url:/users/{0}/;dirsource:GET /users/?search={0}\"></ui-component>\n```\n\n### Inline usage\n\nIf the `value` will be changed then the component performs `config.exec` for obtaining a readable text of a value.\n\n```html\n<ui-component name=\"dynamicvalue\" path=\"path.to.property\" config=\"exec:my_function\"></ui-component>\n\n<script>\n\tfunction my_function(value, next) {\n\t\tnext({ name: 'This text will be displayed as a value '});\n\t}\n</script>\n```\n\n__Good to know__:\n\n- `component.response` contains a raw response from the result\n\nUse it with `<ui-component name=\"dynamicvalue\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"width:300px\">\n\t<ui-component name=\"dynamicvalue\" path=\"userid\" config=\"exec:getuser;click:finduser;placeholder:Choose a user\">User</ui-component>\n</div>\n\n<script>\n\tvar userid = 1;\n\n\tfunction getuser(value, next) {\n\t\t// Here you can read a readable text\n\t\t// Value is binded according to the \"config.html\" Tangular template\n\t\tnext({ name: 'Peter' });\n\t}\n\n\tfunction finduser(element, next, current_value) {\n\t\t// \"next\" must received a new value which will be binded from e.g. j-Suggestion or some j-Form\n\t\t// a value will be automatically binded according to the component \"path\"\n\t\tnext(1);\n\t}\n\n</script>\n```"}],"knowledge":{"content_sha256":"aa1dc328680acd2bcb639e4e721cd6f8601b95d12052c8983cd0f54265efc04e","source_sha256":"0df586d9a046a9da357ace162ff1301835ce30d5e9233c8738ffe43d3e0edb38","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["AJAX","CDN","DEF","GET","HTML","MIT","TAPI","URL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"width:300px\">\n\t<ui-component name=\"dynamicvalue\" path=\"userid\" config=\"exec:getuser;click:finduser;placeholder:Choose a user\">User</ui-component>\n</div>\n\n<script>\n\tvar userid = 1;\n\n\tfunction getuser(value, next) {\n\t\t// Here you can read a readable text\n\t\t// Value is binded according to the \"config.html\" Tangular template\n\t\tnext({ name: 'Peter' });\n\t}\n\n\tfunction finduser(element, next, current_value) {\n\t\t// \"next\" must received a new value which will be binded from e.g. j-Suggestion or some j-Form\n\t\t// a value will be automatically binded according to the component \"path\"\n\t\tnext(1);\n\t}\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/dynamicvaluelist","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/dynamicvaluelist/","markdown_url":"https://forge.totaljs.es/en/components/dynamicvaluelist.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"e241f736b3b013f1ff82dc8e6458cb625c83222cc29bac5a070bbd7ed1b8862a","checked_at":"2026-10-20T12:20:51.886Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-DynamicValueList","description":"This component is very special component. It can keep values for example Number and at the same it can render another readable text. This component is optimized for dynamic usage, so it needs to be li","messages":[{"role":"user","content":"j-DynamicValueList\n\nThis component is very special component. It can keep values for example Number and at the same it can render another readable text. This component is optimized for dynamic usage, so it needs to be li"},{"role":"assistant","content":"This component is very special component. It can keep values for example `Number` and at the same it can render another readable `text`. This component is optimized for dynamic usage, so it needs to be linked with another component or part. This component works with array.\n\n\n__Configuration__:\n\n- `icon {String}` icon for label e.g. home, cog, etc.\n- `label {String}` label (default is HTML content)\n- `icon2 {String}` icon in the right box e.g. home, cog, etc. (default: `angle-down`)\n- `placeholder {String}` adds a placeholder text\n- `disabled {Boolean}` disables this component\n- `find {String}` __important__ link to `function(el, next(value))` for binding a new value\n- `read {String}` a link to `function(array_of_values, next(array_response))` for binding a readable `text`\n- `url {String}` tries to bind a value via `AJAX()`, argument `{0}` in URL is replaced by the value\n- `html {String}` Tangular template for rendering a value (default: `{{ name }}`)\n- `remap {String}` a remap function (default: `null`), example: `value.length ? value[0] : null`\n- `required {Boolean}` enables \"required\" (default: `false`)\n- `bind {String}` a path to method or variable where will be binded loaded value (optional)\n- `dirsource {String}` a link to method `function(search_string, next(items_arr))` or __NEW__: can contain URL for search in fhe form `GET /api/partners/?q={0}`\n- `dircustom {String/Boolean}` can contain a path to function(val, next(new_val)) or can be Boolean. This option can enable adding a custom value (value not defined in data-source)\n- `dirrender {String}` a path to `function(item, text)` (must return HTML for j-Directory), this function can affect list of items in j-Directory\n- `dirminwidth {Number}` a minimum width for j-Directory, (default: `200`)\n- `dirmaxwidth {Number}` a maximum width for j-Directory\n- `dirplaceholder {String}` a placeholder for j-Directory\n- `dirempty {String}` adds an empty field for j-Directory\n- `dirkey {String}` a key name for reading of text in dirsource (default: `name`)\n- `dirvalue {String}` a key name for reading of value in dirsource (default: `id`)\n- `key {String}` a primary key name (default: a value from `dirvalue`)\n- __NEW__ `tapi {Boolean}` enables calling `TAPI()` insteadof `AJAX()` (default: false)\n\n### AJAX usage\n\nIf the `value` will be changed then the component performs `AJAX` call automatically. Server must response with `Array`.\n\n```html\n<ui-component name=\"dynamicvaluelist\" path=\"path.to.property\" config=\"url:/users/?id={0};dirsource:GET /users/?search={0}\"></ui-component>\n```\n\n### Inline usage\n\nIf the `value` will be changed then the component performs `config.exec` for obtaining a readable text of a value.\n\n```html\n<ui-component name=\"dynamicvaluelist\" path=\"path.to.property\" config=\"read:my_function\"></ui-component>\n\n<script>\n\tfunction my_function(arr, next) {\n\t\t// the component automatically pairs \"response\" with \"arr\"\n\t\tnext([{ id: 1, name: 'This text will be displayed as a value '}]);\n\t}\n</script>\n```\n\nUse it with `<ui-component name=\"dynamicvaluelist\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"directory\"></ui-component>\n\n<div class=\"m\" style=\"width:300px\">\n\t<ui-component name=\"dynamicvaluelist\" path=\"common.users\" config=\"read:getusers;dirsource:findusers;placeholder:Add user\">Users</ui-component>\n</div>\n\n<script>\n\tvar names = [];\n\tnames.push({ id: '1', name: 'Denis' });\n\tnames.push({ id: '2', name: 'Peter' });\n\tnames.push({ id: '3', name: 'Tomáš' });\n\tnames.push({ id: '4', name: 'Lucka' });\n\tnames.push({ id: '5', name: 'Lenka' });\n\n\tfunction getusers(value, next) {\n\t\tvar temp = [];\n\t\tfor (var i = 0; i < value.length; i++) {\n\t\t\tvar user = names.findItem('id', value[i]);\n\t\t\tuser && temp.push(user);\n\t\t}\n\t\tnext(temp);\n\t}\n\n\tfunction findusers(search, next) {\n\t\tvar items = names.slice(0);\n\t\tnext(items);\n\t}\n\n</script>\n```"}],"knowledge":{"content_sha256":"c4e030d56b17f6696fa824cd0e7c72bc502ae014845a64212cfdc77ebf0d25db","source_sha256":"e241f736b3b013f1ff82dc8e6458cb625c83222cc29bac5a070bbd7ed1b8862a","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["AJAX","CDN","DEF","GET","HTML","MIT","TAPI","URL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"directory\"></ui-component>\n\n<div class=\"m\" style=\"width:300px\">\n\t<ui-component name=\"dynamicvaluelist\" path=\"common.users\" config=\"read:getusers;dirsource:findusers;placeholder:Add user\">Users</ui-component>\n</div>\n\n<script>\n\tvar names = [];\n\tnames.push({ id: '1', name: 'Denis' });\n\tnames.push({ id: '2', name: 'Peter' });\n\tnames.push({ id: '3', name: 'Tomáš' });\n\tnames.push({ id: '4', name: 'Lucka' });\n\tnames.push({ id: '5', name: 'Lenka' });\n\n\tfunction getusers(value, next) {\n\t\tvar temp = [];\n\t\tfor (var i = 0; i < value.length; i++) {\n\t\t\tvar user = names.findItem('id', value[i]);\n\t\t\tuser && temp.push(user);\n\t\t}\n\t\tnext(temp);\n\t}\n\n\tfunction findusers(search, next) {\n\t\tvar items = names.slice(0);\n\t\tnext(items);\n\t}\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/ean13","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/ean13/","markdown_url":"https://forge.totaljs.es/en/components/ean13.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"56fbac12e9df464e12fd8f41b157631ad606437aca3e30df423e516cc1f8d0ff","checked_at":"2026-10-20T12:20:52.627Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-EAN13","description":"The component will draw EAN-13 barcode according to the path value.","messages":[{"role":"user","content":"j-EAN13\n\nThe component will draw EAN-13 barcode according to the path value."},{"role":"assistant","content":"The component will draw EAN-13 barcode according to the path value.\n\n- Included <https://github.com/joushx/jQuery.EAN13> library (Johannes Mittendorfer, MIT license)\n\n---\n\n__Configuration__:\n\n- `width {Number}` barcode width, height is automatically calculated (default: `200`)\n- `padright {Boolean}`if `true`, it fills the value `0` from the right side and `false` from the left side\n\nUse it with `<ui-component name=\"ean13\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"EAN13\" path=\"eancode\"></ui-component>\n\n<script>\n\tvar eancode = '1234567890123';\n</script>\n```"}],"knowledge":{"content_sha256":"7bb7ca516a0a30faab2507d745c8eda850e5cc9d44bbdefd8ea952ceed3bf4e1","source_sha256":"56fbac12e9df464e12fd8f41b157631ad606437aca3e30df423e516cc1f8d0ff","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","EAN","EAN13","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"EAN13\" path=\"eancode\"></ui-component>\n\n<script>\n\tvar eancode = '1234567890123';\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/edit","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/edit/","markdown_url":"https://forge.totaljs.es/en/components/edit.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"60ee2b40038385e89a19c7c08271483db1d240e229f8e1653b039f0e9d7ea884","checked_at":"2026-10-20T12:20:53.370Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Edit","description":"The component enables contenteditable attribute for elements with edit or edit2 classes.","messages":[{"role":"user","content":"j-Edit\n\nThe component enables contenteditable attribute for elements with edit or edit2 classes."},{"role":"assistant","content":"The component enables `contenteditable` attribute for elements with `edit` or `edit2` classes.\n\n- singleton\n- put the component under `<body` element like `j-Directory` or `j-DatePicker`\n\n__Good to know__:\n\n- each element with the `edit` class is possible to edit with a single click\n- each element with the `edit2` class is possible to edit with a double click\n\n__Configuration__:\n\n- `dateformat {String}` a default format for parsing dates (default: `yyyy-MM-dd`)\n- `exec {String}` optional, a default path to `function(opt, accept(boolean/html_value_for_the_element))`\n- `floating {Boolean}` optional, can enables floating by default (default: `false`)\n- `minwidth {Number}` optional, a min width\n- `offsetX {Number}` optional, X offset\n- `offsetY {Number}` optional, Y offset\n\n---\n\n`data-edit` attribute supports:\n\n- `required {Boolean}` optional, enables \"required\" (default: `false`)\n- `type {String}` optional, can be `string` (default), `date`, `number`, `boolean`, `email`, `url`, or `html`\n- `check {String}` optional, a path to `function(opt, el)` must return `boolean`, it means `Can the user edit that field?`\n- `exec {String}` optional, a path to `function(opt, accept(boolean/html_value_for_the_element))`\n- `format {String}` optional, a format for `date`, default: `yyyy-MM-dd`\n- `maxlength {Number}` a maxlength\n- `minvalue {Number}` a min. value for number type (default: `undefined`)\n- `maxvalue {Number}` a max. value for number type (default: `undefined`)\n- `bind {Boolean}` if `true` rebinds `data-bind` on this element only (default: `true`)\n- `multiline {Boolean}` optional, enables multiline + appends class `editable-multiline` (`inline-block` + `width:100%`) when it's editing (default: `false`)\n- `floating {Boolean/String}` optional, enables floating window for the element (default: `false`, `string` can contain `position` or `offset` values)\n- `offsetX {Number}` optional, additional X offset for the floating window\n- `offsetY {Number}` optional, additional Y offset for the floating window\n- `offsetWidth {Number}` optional, additional width for the floating window\n- `width {Number}` optional, a fixed width for floating panel\n- `parent {String}` optional, a selector for obtaining of offset (can contain `parent` or jQuery selector for `.closest()` method)\n- `selectall {Boolean}` optional, selects all text\n- `cursor {String}` optional, a cursor position `beg` or `end` (default)\n- `notify {String}` optional, a path to `function(opt)` that will be notified after successful change\n- `prevent {Boolean}` optional, executes `.preventDefault()` and `.stopPropagation()` in the click event\n- `align {String}` optional, sets a valeu for the text-align property for the floating mode only\n- `focus {String}` optional, a link to the `function(opt, el)` when it's focused\n- `blur {String}` optional, a link to the `function(opt, el)` when it's blurred\n- __NEW__: `attr {String}` optional, an `data-{attr}` name to get the unformatted value\n\n__Good to know__:\n\n- `opt.set(value)` can be used to assign a value\n\nUse it with `<ui-component name=\"edit\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"edit\"></ui-component>\n\n<ui-plugin path=\"form\" style=\"width:500px;padding:10px;border:1px solid #E0E0E0\">\n\n\t<div class=\"row m\">\n\t\t<div class=\"col-xs-3\">\n\t\t\tName:\n\t\t</div>\n\t\t<div class=\"col-xs-9\">\n\t\t\t<ui-bind class=\"edit2 block\" data-edit=\"required:1;floating:1\" path=\"?.name\" config=\"html\" style=\"height:24px;background-color:#E0E0E0\"></ui-bind>\n\t\t</div>\n\t</div>\n\t<div class=\"row m\">\n\t\t<div class=\"col-xs-3\">\n\t\t\tEmail:\n\t\t</div>\n\t\t<div class=\"col-xs-9\">\n\t\t\t<div class=\"edit\" data-edit=\"required:1;type:email;exec:save_email\" style=\"height:24px;background-color:#E0E0E0\">@</div>\n\t\t</div>\n\t</div>\n\t<div class=\"row m\">\n\t\t<div class=\"col-xs-3\">\n\t\t\tHTML:\n\t\t</div>\n\t\t<div class=\"col-xs-9\">\n\t\t\t<ui-bind  class=\"edit block\" data-edit=\"multiline:1;floating:1;type:html\" path=\"form.html\" config=\"html\" style=\"min-height:24px;background-color:#E0E0E0\"></ui-bind>\n\t\t</div>\n\t</div>\n\t<pre><ui-bind path=\"?\" config=\"html:JSON.stringify(value, null, '  ')\"></ui-bind></pre>\n</ui-plugin>\n\n<script>\n\n\tfunction save_email(opt, next) {\n\t\tSET('form.email', opt.value);\n\t\t// next(true);\n\t\t// next(false) OR next(null) cancels the change\n\t\tnext(opt.value.toUpperCase());\n\t}\n\n\tvar form = {};\n\tform.html = '<b>Hello world!</b>';\n\tform.name = 'UI components';\n\n</script>\n```"}],"knowledge":{"content_sha256":"932bda43cc5628615fe094453501b336f6f999201fbf48cc755da49a475eb1cb","source_sha256":"60ee2b40038385e89a19c7c08271483db1d240e229f8e1653b039f0e9d7ea884","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","E0E0E0","HTML","JSON","MIT","SET"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"edit\"></ui-component>\n\n<ui-plugin path=\"form\" style=\"width:500px;padding:10px;border:1px solid #E0E0E0\">\n\n\t<div class=\"row m\">\n\t\t<div class=\"col-xs-3\">\n\t\t\tName:\n\t\t</div>\n\t\t<div class=\"col-xs-9\">\n\t\t\t<ui-bind class=\"edit2 block\" data-edit=\"required:1;floating:1\" path=\"?.name\" config=\"html\" style=\"height:24px;background-color:#E0E0E0\"></ui-bind>\n\t\t</div>\n\t</div>\n\t<div class=\"row m\">\n\t\t<div class=\"col-xs-3\">\n\t\t\tEmail:\n\t\t</div>\n\t\t<div class=\"col-xs-9\">\n\t\t\t<div class=\"edit\" data-edit=\"required:1;type:email;exec:save_email\" style=\"height:24px;background-color:#E0E0E0\">@</div>\n\t\t</div>\n\t</div>\n\t<div class=\"row m\">\n\t\t<div class=\"col-xs-3\">\n\t\t\tHTML:\n\t\t</div>\n\t\t<div class=\"col-xs-9\">\n\t\t\t<ui-bind  class=\"edit block\" data-edit=\"multiline:1;floating:1;type:html\" path=\"form.html\" config=\"html\" style=\"min-height:24px;background-color:#E0E0E0\"></ui-bind>\n\t\t</div>\n\t</div>\n\t<pre><ui-bind path=\"?\" config=\"html:JSON.stringify(value, null, '  ')\"></ui-bind></pre>\n</ui-plugin>\n\n<script>\n\n\tfunction save_email(opt, next) {\n\t\tSET('form.email', opt.value);\n\t\t// next(true);\n\t\t// next(false) OR next(null) cancels the change\n\t\tnext(opt.value.toUpperCase());\n\t}\n\n\tvar form = {};\n\tform.html = '<b>Hello world!</b>';\n\tform.name = 'UI components';\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/editable","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/editable/","markdown_url":"https://forge.totaljs.es/en/components/editable.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"3102ff7183cc056f768e1249d4a02c4882bed6d413efc4a8f238b7055606c3a9","checked_at":"2026-10-20T12:20:54.251Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Editable","description":"This component can edit all elements with editable class. It works with data-bind or dynamic values. Learn more from example.","messages":[{"role":"user","content":"j-Editable\n\nThis component can edit all elements with editable class. It works with data-bind or dynamic values. Learn more from example."},{"role":"assistant","content":"This component can edit all elements with `editable` class. It works with `data-bind` or dynamic values. Learn more from example.\n\n__Configuration__:\n\n- `autofocus` {Boolean} focuses first item (default: `false`)\n- `validate` {Boolean} can disable validation (default: `true`)\n- `disabled` {Boolean} can disable editing (it adds `ui-editable-disabled` and `ui-editable-enabled` class), default: `false`\n- `changed` {String} optional, a link to a method or path --> it will contain the object with changed values only\n- `enter` {String} optional, a link to a method `method(path, value, el)`, the method is executed if the user presses `ENTER`\n- `escape` {String} optional, a link to a method `method(path, value, el)`, the method is executed if the user presses `ESC`\n- `can` {String} optional, a link to method `method(opt, el)` must return `boolean`, it means `can edit?`\n- `error` {String} optional, a link to method `method(el, isInvalid, meta)` is evaluated when the item is invalid and vice-versa\n- __NEW__ `class {String}` optional, adds a class name in the form `ui-editable-CLASS`  (default: `default`)\n\n---\n\n`data-editable` attribute describes behaviour:\n\n- `path` {String} a __relative path__ to property (can be inherited from `data-bind` automatically)\n- `required` {Boolean} optional, enables \"required\" (default: `false`)\n- `type` {String} optional, can be `string` (default), `date`, `number`, `boolean`, `email` or `html` __NEW__ `tags`\n- `can` {String} optional, a path to `function(opt, el)` must return `boolean`, it means `can edit?`\n- `save` {String} optional, a path to `function(opt, accepted(boolean))`, `accepted` function must be evaluated\n- `format` {String} optional, a format for `date`, default: `yyyy-MM-dd`\n- `dirsource` {String} optional, path to a data-source\n- `dircustom` {String/Boolean} optional, can contain a path to `function(val, next(new_val))` or can be `Boolean`. This option can enable adding a custom value (value not defined in data-source)\n- `dirrender` {String} optional, a path to `function(item, text)` (must return HTML for `j-Directory`), this function can affect list of items in `j-Directory`\n- `dirminwidth` {Number} optional, a minimum width for `j-Directory`, default: `200`\n- `dirmaxwidth` {Number} optional, a maximum width for `j-Directory`\n- `dirplaceholder` {String} optional, a placeholder for `j-Directory`\n- `dirempty` {String} optional, adds an empty field for `j-Directory`\n- `dirkey` {String} optional, a key name for reading of `text` in `dirsource` (default: `name`)\n- `dirvalue` {String} optional, a key name for reading of `value` in `dirsource` (default: `id`)\n- `dirsearch` {Boolean} optional, can disable search for `j-Directory`\n- `autosource` {String} a path to `search` function in `autocomplete`, `function(search, render(arr))`\n- `autovalue` {String} a property path for the value in `autosource`\n- `maxlength` {Number} a maxlength\n- `minvalue` {Number} a min. value for number type (default: `undefined`)\n- `maxvalue` {Number} a max. value for number type (default: `undefined`)\n- `rebind` {Boolean} if `true` rebinds `data-bind` on this element only (default: `0`)\n- `validate` {String} optional, a condition for validating of value, can contain a link to `function(value)` or `!!value.match(/[a-z]+/)`\n- `multiline` {Boolean} optional, enables multiline + appends class `editable-multiline` (`inline-block` + `width:100%`) when it's editing (default: `false`)\n- `raw` {Boolean} optional, when `true` then the current HTML content will be as a placeholder if the value is `nullable` (default: `true`)\n- `accept` {String} optional, a condition for accepting of value, can contain a link to `function(value)` or `!!value.match(/[a-z]+/)` is similar to `validate`\n- `empty` {String} optional, enables empty value and can contain empty label or `1` or `true` for auto `rebind` of binder\n- `invalid` {String} optional, a link to method `method(el, isInvalid, meta)` is evaluated when the item is invalid and vice-versa\n\n__Methods__:\n\n- __NEW__: `component.changed()` returns an object with changed data\n\n__Good to know__:\n\n- if the user presses some `key` then the editable element will contain `keypressed` class as long as they haven't left the control\n- each changed element will contain class `changed`\n- if the component changes some values, the main element will contain class `ui-editable-changed`\n- if the element has `data-bind` with `empty`, it will be cleared after focus\n- __NEW__ now supports `tags` type. For now supports only with `rebind` and `data-bind`. (data-bind `template` with `{jQuery selector}` works from `+v18`)\n- __NEW__ if the element (__or parent, or parent of parent__) contains attribute `data-disabled=\"1\"`, the control will be disabled\n- __NEW__ hidden and disabled elements won't be validated\n\nUse it with `<ui-component name=\"editable\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\t.changed {}\n\t.changed::after { float: right; content: '+'; margin-left: 5px; font-weight: normal; color: orange; }\n</style>\n\n<ui-component name=\"LAZY datepicker\"></ui-component>\n<ui-component name=\"LAZY directory\"></ui-component>\n\n<ui-component name=\"editable\" path=\"form\">\n\t<div style=\"width:500px;padding:10px;border:1px solid #E0E0E0\">\n\n\t\t<div class=\"row\">\n\t\t\t<div class=\"col-xs-3\">\n\t\t\t\tName:\n\t\t\t</div>\n\t\t\t<div class=\"col-xs-9\">\n\t\t\t\t<ui-bind path=\"form.name\" config=\"html\" class=\"editable block\" data-editable=\"required:true\"></ui-bind>\n\t\t\t</div>\n\t\t</div>\n\t\t<div class=\"row\">\n\t\t\t<div class=\"col-xs-3\">\n\t\t\t\tEmail:\n\t\t\t</div>\n\t\t\t<div class=\"col-xs-9\">\n\t\t\t\t<ui-bind path=\"form.email\" config=\"html\" class=\"editable block\" data-editable=\"required:1;type:email\"></ui-bind>\n\t\t\t</div>\n\t\t</div>\n\t\t<div class=\"row\">\n\t\t\t<div class=\"col-xs-3\">\n\t\t\t\tNickname:\n\t\t\t</div>\n\t\t\t<div class=\"col-xs-9\">\n\t\t\t\t<ui-bind path=\"form.nickname\" config=\"empty:Nickname;html\" class=\"editable block\" data-editable=\"required:true\"></ui-bind>\n\t\t\t</div>\n\t\t</div>\n\t\t<div class=\"row\">\n\t\t\t<div class=\"col-xs-3\">\n\t\t\t\tFramework:\n\t\t\t</div>\n\t\t\t<div class=\"col-xs-9\">\n\t\t\t\t<ui-bind class=\"editable block\" config=\"html;empty:Choose a framework;change:renderframework\" path=\"form.frameworkid\" data-editable=\"dirsource:frameworks;\"></ui-bind>\n\t\t\t</div>\n\t\t</div>\n\t\t<div class=\"row\">\n\t\t\t<div class=\"col-xs-3\">\n\t\t\t\tCountry:\n\t\t\t</div>\n\t\t\t<div class=\"col-xs-9\">\n\t\t\t\t<ui-bind path=\"form.countries\" config=\"template:{#countrytags}\" class=\"editable block\" data-editable=\"rebind:1;dirsource:countries;type:tags\"></ui-bind>\n\t\t\t</div>\n\t\t</div>\n\t\t<div class=\"row\">\n\t\t\t<div class=\"col-xs-3\">\n\t\t\t\tDate:\n\t\t\t</div>\n\t\t\t<div class=\"col-xs-9\">\n\t\t\t\t<ui-bind path=\"form.date\" config=\"html:(new Date() || value).format('yyyy-MM-dd')\" class=\"editable block\" data-editable=\"type:date\"></ui-bind>\n\t\t\t</div>\n\t\t</div>\n\t</div>\n\t<pre><ui-bind path=\"form\" config=\"html:JSON.stringify(value, null, '  ')\"></ui-bind></pre>\n</ui-component>\n\n<script type=\"text/html\" id=\"countrytags\">\n\t{{ foreach m in value }}<span class=\"editable-tag\"><i class=\"ti ti-times\"></i>{{ m }}</span>{{ end }}\n</script>\n\n\n<script>\n\n\t// Alternative to tags (ui-bind path=\"path\" config=\"change:myfunc\")\n\tfunction myfunc(data, path, el) {\n\t\tif (!data)\n\t\t\treturn el.html('');\n\t\tvar template = '<span class=\"tag\"><i class=\"ti ti-times\"></i>{0}</span>';\n\t\tvar html = '';\n\t\tfor (var i = 0; i < data.length; i++)\n\t\t\thtml += template.format(data[i]);\n\t\tel.html(html);\n\t}\n\n\tvar frameworks = [{ id: 0, name: 'Nothing', template: '<b>{{ name }}</b>' }, { id: 1, name: 'jComponent' }, { id: 2, name: 'Angular.js' }, { id: 3, name: 'React' }];\n\tvar countries = ['Slovakia', 'Poland', 'Hungary', 'Germany', 'USA', 'Austria', 'Poland', 'Czechia'];\n\tvar form = {};\n\tform.date = new Date();\n\tform.name = 'Peter Širka';\n\tform.countries = ['Slovakia', 'Hungary'];\n\tform.frameworkid = 0;\n\n\tfunction renderframework(value, path, el) {\n\t\treturn value ? frameworks.findValue('id', value, 'name', DEF.empty) : DEF.empty;\n\t}\n\n</script>\n```"}],"knowledge":{"content_sha256":"5e8bdef0daa2b7b2bf0a4f18bd5d64b65d10a2fa39449bf17f5cd5b8d3af2157","source_sha256":"3102ff7183cc056f768e1249d4a02c4882bed6d413efc4a8f238b7055606c3a9","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","CLASS","DEF","E0E0E0","ENTER","ESC","HTML","JSON","LAZY","MIT","USA"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\t.changed {}\n\t.changed::after { float: right; content: '+'; margin-left: 5px; font-weight: normal; color: orange; }\n</style>\n\n<ui-component name=\"LAZY datepicker\"></ui-component>\n<ui-component name=\"LAZY directory\"></ui-component>\n\n<ui-component name=\"editable\" path=\"form\">\n\t<div style=\"width:500px;padding:10px;border:1px solid #E0E0E0\">\n\n\t\t<div class=\"row\">\n\t\t\t<div class=\"col-xs-3\">\n\t\t\t\tName:\n\t\t\t</div>\n\t\t\t<div class=\"col-xs-9\">\n\t\t\t\t<ui-bind path=\"form.name\" config=\"html\" class=\"editable block\" data-editable=\"required:true\"></ui-bind>\n\t\t\t</div>\n\t\t</div>\n\t\t<div class=\"row\">\n\t\t\t<div class=\"col-xs-3\">\n\t\t\t\tEmail:\n\t\t\t</div>\n\t\t\t<div class=\"col-xs-9\">\n\t\t\t\t<ui-bind path=\"form.email\" config=\"html\" class=\"editable block\" data-editable=\"required:1;type:email\"></ui-bind>\n\t\t\t</div>\n\t\t</div>\n\t\t<div class=\"row\">\n\t\t\t<div class=\"col-xs-3\">\n\t\t\t\tNickname:\n\t\t\t</div>\n\t\t\t<div class=\"col-xs-9\">\n\t\t\t\t<ui-bind path=\"form.nickname\" config=\"empty:Nickname;html\" class=\"editable block\" data-editable=\"required:true\"></ui-bind>\n\t\t\t</div>\n\t\t</div>\n\t\t<div class=\"row\">\n\t\t\t<div class=\"col-xs-3\">\n\t\t\t\tFramework:\n\t\t\t</div>\n\t\t\t<div class=\"col-xs-9\">\n\t\t\t\t<ui-bind class=\"editable block\" config=\"html;empty:Choose a framework;change:renderframework\" path=\"form.frameworkid\" data-editable=\"dirsource:frameworks;\"></ui-bind>\n\t\t\t</div>\n\t\t</div>\n\t\t<div class=\"row\">\n\t\t\t<div class=\"col-xs-3\">\n\t\t\t\tCountry:\n\t\t\t</div>\n\t\t\t<div class=\"col-xs-9\">\n\t\t\t\t<ui-bind path=\"form.countries\" config=\"template:{#countrytags}\" class=\"editable block\" data-editable=\"rebind:1;dirsource:countries;type:tags\"></ui-bind>\n\t\t\t</div>\n\t\t</div>\n\t\t<div class=\"row\">\n\t\t\t<div class=\"col-xs-3\">\n\t\t\t\tDate:\n\t\t\t</div>\n\t\t\t<div class=\"col-xs-9\">\n\t\t\t\t<ui-bind path=\"form.date\" config=\"html:(new Date() || value).format('yyyy-MM-dd')\" class=\"editable block\" data-editable=\"type:date\"></ui-bind>\n\t\t\t</div>\n\t\t</div>\n\t</div>\n\t<pre><ui-bind path=\"form\" config=\"html:JSON.stringify(value, null, '  ')\"></ui-bind></pre>\n</ui-component>\n\n<script type=\"text/html\" id=\"countrytags\">\n\t{{ foreach m in value }}<span class=\"editable-tag\"><i class=\"ti ti-times\"></i>{{ m }}</span>{{ end }}\n</script>\n\n\n<script>\n\n\t// Alternative to tags (ui-bind path=\"path\" config=\"change:myfunc\")\n\tfunction myfunc(data, path, el) {\n\t\tif (!data)\n\t\t\treturn el.html('');\n\t\tvar template = '<span class=\"tag\"><i class=\"ti ti-times\"></i>{0}</span>';\n\t\tvar html = '';\n\t\tfor (var i = 0; i < data.length; i++)\n\t\t\thtml += template.format(data[i]);\n\t\tel.html(html);\n\t}\n\n\tvar frameworks = [{ id: 0, name: 'Nothing', template: '<b>{{ name }}</b>' }, { id: 1, name: 'jComponent' }, { id: 2, name: 'Angular.js' }, { id: 3, name: 'React' }];\n\tvar countries = ['Slovakia', 'Poland', 'Hungary', 'Germany', 'USA', 'Austria', 'Poland', 'Czechia'];\n\tvar form = {};\n\tform.date = new Date();\n\tform.name = 'Peter Širka';\n\tform.countries = ['Slovakia', 'Hungary'];\n\tform.frameworkid = 0;\n\n\tfunction renderframework(value, path, el) {\n\t\treturn value ? frameworks.findValue('id', value, 'name', DEF.empty) : DEF.empty;\n\t}\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/elementid","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/elementid/","markdown_url":"https://forge.totaljs.es/en/components/elementid.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"58b5187c6416f42dfbc1f8096bdbb82784bfded6fec3f5c94465c1915f287dee","checked_at":"2026-10-20T12:20:54.985Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-ElementID","description":"This component fills the data-id attribute with the value read from the path.","messages":[{"role":"user","content":"j-ElementID\n\nThis component fills the data-id attribute with the value read from the path."},{"role":"assistant","content":"This component fills the `data-id` attribute with the value read from the path.\n\nUse it with `<ui-component name=\"elementid\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\nCheck the <b>data-id</b> attribute via Web Developer tools.\n\n<ui-component name=\"elementid\" path=\"common.id\"></ui-component>\n\n<script>\n\n\tvar common = { id: '123456' };\n\n</script>\n```"}],"knowledge":{"content_sha256":"c71e940a7509ba88a97dc0f76cfa1d7bbcbd7839e88e7fad2014916ecc11b4c1","source_sha256":"58b5187c6416f42dfbc1f8096bdbb82784bfded6fec3f5c94465c1915f287dee","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"Check the <b>data-id</b> attribute via Web Developer tools.\n\n<ui-component name=\"elementid\" path=\"common.id\"></ui-component>\n\n<script>\n\n\tvar common = { id: '123456' };\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/emoji","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/emoji/","markdown_url":"https://forge.totaljs.es/en/components/emoji.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"64633a759e4e584e09765febc4e756cd442054a9a1e025961cb8354b155eb5f1","checked_at":"2026-10-20T12:20:56.338Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Emoji","description":"j-Emoji: Using UTF emoji; Personal history with frequently used emoticons","messages":[{"role":"user","content":"j-Emoji\n\nj-Emoji: Using UTF emoji; Personal history with frequently used emoticons"},{"role":"assistant","content":"- Using UTF emoji\n- Personal history with frequently used emoticons\n- Great search tool\n- Changeable skin tones\n\n__Configuration__:\n\n- `categories {String}` change emoticons in categories. Use unicode without special characters (default: `128342,128578,128161,127944,128008,128690,128172,127828,127937`)\n- `height {Number}` set component height (default: `295`)\n- `history {Number}` history limit for frequently used emoticons (default: `49`)\n- `empty {String}` a message when no emoticons are found  (default: `No emoji match your search`)\n- `emptyemoji {Number}` change emoticon when no emoticons are found. Use unicode without special characters (default: `128557`)\n- `speed {Number}` scroll speed in ms (default: `500`)\n- `footer {String}` footer message (default: `Choose skin tone`)\n- `toneemoji {String}` skin tones emoticon. Use unicode without special characters (default: `9995`)\n- `search {String}` a placeholder for search field (default: `Search`)\n\n__Good to know__:\n\n- This component implements `FUNC.parseASCI(string)` function. The function will parse string and transform text like `:D` or `:joy:` into UTF emoticons.\n\n```javascript\nvar message = 'This one was good. :joy: See you soon :P';\nconsole.log(FUNC.parseASCI(message)); // This one was good. &#128514; See you soon &#128539;\n```\n\n## Usage\n\n```javascript\nvar opt = {};\n\n// opt.offsetX {Number}    : adds X offset (default: 0)\n// opt.offsetY {Number}    : adds Y offset (default: 0)\n// opt.align {String}      : align `left` (default), `center` or `right`\n// opt.position {String}   : position `top` (default) or `bottom`\n\nopt.element = YOUR_ELEMENT;\n// or if you want to use a fixed position:\n// opt.x {Number}          : `x` position\n// opt.y {Number}          : `y` position\n\nopt.callback = function(emoji) {\n\tconsole.log(emoji); // callback parameter has String.fromCodePoint() value\n};\n\nSETTER('emoji/show', opt);\n```\n\nUse it with `<ui-component name=\"emoji\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Denis Granec. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"emoji\"></ui-component>\n\n<div class=\"container\">\n\t<div class=\"row\">\n\t\t<div class=\"col-xs-4 col-xs-offset-4\" style=\"text-align:center\">\n\t\t\t<a href=\"javascript:void(0)\" style=\"color:black;font-size:30px\"><i class=\"ti ti-smile\"></i></a>\n\t\t</div>\n\t</div>\n</div>\n\n<script>\n$(document).on('click', 'a', function() {\n\tvar opt = {};\n\topt.align = 'center';\n\topt.position = 'top';\n\topt.element = this;\n\topt.callback = function(selected) {\n\t\tconsole.log(selected);\n\t};\n\tSETTER('emoji/show', opt);\n\tconsole.log(FUNC.parseASCII('This one was good. :joy: See you soon :P'));\n});\n</script>\n```"}],"knowledge":{"content_sha256":"63148ed1835fb8a2760513e89d5e83a91adc54e4d7c7499bea812eb132bada1a","source_sha256":"64633a759e4e584e09765febc4e756cd442054a9a1e025961cb8354b155eb5f1","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","FUNC","MIT","SETTER","String.fromCodePoint","UTF","YOUR_ELEMENT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"emoji\"></ui-component>\n\n<div class=\"container\">\n\t<div class=\"row\">\n\t\t<div class=\"col-xs-4 col-xs-offset-4\" style=\"text-align:center\">\n\t\t\t<a href=\"javascript:void(0)\" style=\"color:black;font-size:30px\"><i class=\"ti ti-smile\"></i></a>\n\t\t</div>\n\t</div>\n</div>\n\n<script>\n$(document).on('click', 'a', function() {\n\tvar opt = {};\n\topt.align = 'center';\n\topt.position = 'top';\n\topt.element = this;\n\topt.callback = function(selected) {\n\t\tconsole.log(selected);\n\t};\n\tSETTER('emoji/show', opt);\n\tconsole.log(FUNC.parseASCII('This one was good. :joy: See you soon :P'));\n});\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/emojibutton","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/emojibutton/","markdown_url":"https://forge.totaljs.es/en/components/emojibutton.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"07fc0103ed004f2c42fee84176412b7104146f0c7167f5787dc8d18af140ebdc","checked_at":"2026-10-20T12:20:57.234Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-EmojiButton","description":"j-EmojiButton: Component is singleton; Supports Dark theme","messages":[{"role":"user","content":"j-EmojiButton\n\nj-EmojiButton: Component is singleton; Supports Dark theme"},{"role":"assistant","content":"- Component is __singleton__\n- Supports __Dark theme__\n- It uses `j-Emoji`\n\n__Configuration__:\n\n- `default` a default color (default: `#FFFFFF`)\n- `align` align for j-ColorPicker (default: `left`)\n- `position` position for j-ColorPicker (default: `top`)\n- `offsetX` offset-x for j-ColorPicker (default: `0`)\n- `offsetY` offset-y for j-ColorPicker (default: `0`)\n- `disabled` can disabled this component (default: `false`)\n\nUse it with `<ui-component name=\"emojibutton\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"emoji\"></ui-component>\n\n<div class=\"row\">\n\t<div class=\"col-xs-4\">\n\t\t<ui-component name=\"emojibutton\" path=\"icon\"></ui-component>\n\t</div>\n</div>\n\n<script>\n\tvar icon = '☠';\n</script>\n```"}],"knowledge":{"content_sha256":"057a59a5001d4dc96f6fd1c118a347b9e5434ae256353a8628dd09dc99a2bf6e","source_sha256":"07fc0103ed004f2c42fee84176412b7104146f0c7167f5787dc8d18af140ebdc","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","FFFFFF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"emoji\"></ui-component>\n\n<div class=\"row\">\n\t<div class=\"col-xs-4\">\n\t\t<ui-component name=\"emojibutton\" path=\"icon\"></ui-component>\n\t</div>\n</div>\n\n<script>\n\tvar icon = '☠';\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/empty","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/empty/","markdown_url":"https://forge.totaljs.es/en/components/empty.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"6f099ad0cd070e98d90bee2f8a75050c6f789e1606d3f102725ccbd24a6dd887","checked_at":"2026-10-20T12:20:58.115Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Empty","description":"j-Empty: NEW wait {Boolean} waits for the data (default: false)","messages":[{"role":"user","content":"j-Empty\n\nj-Empty: NEW wait {Boolean} waits for the data (default: false)"},{"role":"assistant","content":"- `path` must contain a data-source which will be watched\n\n__Configuration__:\n\n- `icon {String}` a default icon (default: `ti ti-database`)\n- `parent {String}` a parent container which sets a minimal height (default: `parent`)\n- `height {Number}` height size (if you don't want to use `parent`, default: `undefined`)\n- `margin {Number}` a margin (default: `0`)\n- `topoffset {Boolean}` applies `element.offset().top` as a margin (default: `false`)\n- `topposition {Boolean}` applies `element.position().top` as a margin (default: `false`)\n- __NEW__ `wait {Boolean}` waits for the data (default: `false`)\n\n__Use-case 1__:\n\n```html\n<ui-component name=\"empty\" path=\"datasource\" class=\"hidden\">\n\tItems not found\n</ui-component>\n<ui-bind path=\"datasource\" config=\"show:value && value.length\" class=\"hidden\"></ui-bind>\n```\n\n__Use-case 2__:\n\n```html\n<ui-component name=\"empty\" path=\"datasource\" class=\"hidden\">\n\t<script type=\"text/html\">\n\t\tItems not found\n\t</script>\n\t<div>\n\t\tYOUR CONTENT. This DIV will be hidden automatically if the \"datasource\" is empty.\n\t</div>\n</ui-component\n```\n\nUse it with `<ui-component name=\"empty\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"empty\" path=\"datasource\" config=\"parent:window;wait:false\" class=\"hidden\">The datasource is empty.</ui-component>\n\n<script>\n\n\tvar datasource = [];\n\n\t// Empty component will be hidden after 5 seconds\n\tPUSH('datasource', { item: 1 }, 5000);\n\n</script>\n```"}],"knowledge":{"content_sha256":"61388ac2a0375b169fb21a7aa1a421bc67a23d77f6e6fc7e04bc1d2ced787240","source_sha256":"6f099ad0cd070e98d90bee2f8a75050c6f789e1606d3f102725ccbd24a6dd887","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","CONTENT","DEF","DIV","MIT","PUSH","YOUR"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"empty\" path=\"datasource\" config=\"parent:window;wait:false\" class=\"hidden\">The datasource is empty.</ui-component>\n\n<script>\n\n\tvar datasource = [];\n\n\t// Empty component will be hidden after 5 seconds\n\tPUSH('datasource', { item: 1 }, 5000);\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/emptyinline","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/emptyinline/","markdown_url":"https://forge.totaljs.es/en/components/emptyinline.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"eec9fc017d3444317d45c08363e45aebbbcecc2b1eeb1f6c4c77e8691584e7c6","checked_at":"2026-10-20T12:20:58.855Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-EmptyInline","description":"The component will display text inside the element if the value is empty or the array doesn't contain an item.","messages":[{"role":"user","content":"j-EmptyInline\n\nThe component will display text inside the element if the value is empty or the array doesn't contain an item."},{"role":"assistant","content":"The component will display text inside the element if the value is empty or the array doesn't contain an item.\n\n\n__Configuration__:\n\n- `icon {String}` a default icon (default: `ti ti-ban`)\n\nUse it with `<ui-component name=\"emptyinline\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"emptyinline\" path=\"common.pictures\">Without pictures</ui-component>\n\n<script>\n\n\tvar common = {};\n\n\tcommon.pictures = [];\n\n</script>\n```"}],"knowledge":{"content_sha256":"cadd4d596ef0d89a1525ee97c98a714d9c47f4b27ae29b40320290fa2e88228e","source_sha256":"eec9fc017d3444317d45c08363e45aebbbcecc2b1eeb1f6c4c77e8691584e7c6","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"emptyinline\" path=\"common.pictures\">Without pictures</ui-component>\n\n<script>\n\n\tvar common = {};\n\n\tcommon.pictures = [];\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/enter","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/enter/","markdown_url":"https://forge.totaljs.es/en/components/enter.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"c5da33258ae89e7c182a8bc1b8c08bfe275e67adf9c10defc282a8c96f0af2a1","checked_at":"2026-10-20T12:20:59.742Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Enter","description":"This component evaluates some method if the user press enter. Important: component finds all input elements and performs CAN() for the path.","messages":[{"role":"user","content":"j-Enter\n\nThis component evaluates some method if the user press enter. Important: component finds all input elements and performs CAN() for the path."},{"role":"assistant","content":"This component evaluates some method if the user press `enter`. Important: component finds all `input` elements and performs `CAN()` for the path.\n\n\n__Configuration__:\n- `exec {String}` a method name for execution\n- `validate {Boolean}` enables validate (default: `true`)\n- `trigger {String}` can contain a selector for triggering a real `click` event (default: `button[name=\"submit\"]`)\n- `timeout {Number}` a timeout as a prevention for multi-press of `enter` key (default: `1500`)\n- __NEW__ `delay {Number}` a delay for submitting data (default: `500` ms)\n\nUse it with `<ui-component name=\"enter\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"enter\" config=\"exec:doSubmit;validate:false\"  style=\"padding:50px;max-width:300px\">\n\t<div>Login</div>\n\t<div><input type=\"text\" /></div>\n\t<br />\n\t<div>Password</div>\n\t<div><input type=\"password\" /></div>\n</ui-component>\n\n<script>\n\tfunction doSubmit() {\n\t\talert('ENTER pressed');\n\t}\n</script>\n```"}],"knowledge":{"content_sha256":"43f68249e264bc13ba103db4b35ea697808b5ab84d1012e715dd3e9819e36618","source_sha256":"c5da33258ae89e7c182a8bc1b8c08bfe275e67adf9c10defc282a8c96f0af2a1","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CAN","CDN","DEF","ENTER","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"enter\" config=\"exec:doSubmit;validate:false\"  style=\"padding:50px;max-width:300px\">\n\t<div>Login</div>\n\t<div><input type=\"text\" /></div>\n\t<br />\n\t<div>Password</div>\n\t<div><input type=\"password\" /></div>\n</ui-component>\n\n<script>\n\tfunction doSubmit() {\n\t\talert('ENTER pressed');\n\t}\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/enterbox","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/enterbox/","markdown_url":"https://forge.totaljs.es/en/components/enterbox.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"4ed5d0d5fd4e3e84d011d91249b94cebfd9cb990e9875a9aee20b8c155a8d865","checked_at":"2026-10-20T12:21:00.478Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Enterbox","description":"A simple EnterBox. Each value needs to be confirmed via enter key or clicking on the button.","messages":[{"role":"user","content":"j-Enterbox\n\nA simple EnterBox. Each value needs to be confirmed via enter key or clicking on the button."},{"role":"assistant","content":"A simple EnterBox. Each value needs to be confirmed via `enter` key or clicking on the button.\n\n\n__Configuration__:\n\n- `maxlength {Number}` optional, a maximum length of value (default: `50`)\n- `icon {String}` icon\n- `placeholder {String}` a placeholder for the input control\n- `exec {String}` optional, a link to a function in the window scope (otherwise `data-jc-path` is required)\n- `button {String}` adds a label to the button (default: `empty`)\n\nUse it with `<ui-component name=\"enterbox\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"padding:50px;max-width:300px\">\n\t<ui-component name=\"enterbox\" config=\"exec:enterbox_enter;icon:ti ti-search;placeholder:Search and press enter ...\"></ui-component>\n</div>\n\n<script>\n\tfunction enterbox_enter(value) {\n\t\talert(value);\n\t}\n</script>\n```"}],"knowledge":{"content_sha256":"58464634463f50f166381db1834bce694716a5a6fc0abeab75e825dffcea1c88","source_sha256":"4ed5d0d5fd4e3e84d011d91249b94cebfd9cb990e9875a9aee20b8c155a8d865","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"padding:50px;max-width:300px\">\n\t<ui-component name=\"enterbox\" config=\"exec:enterbox_enter;icon:ti ti-search;placeholder:Search and press enter ...\"></ui-component>\n</div>\n\n<script>\n\tfunction enterbox_enter(value) {\n\t\talert(value);\n\t}\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/error","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/error/","markdown_url":"https://forge.totaljs.es/en/components/error.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"54d6ba11f2bb44ccf42ff6bb64b295a10902ed17fa4e5af65464bd96a5afe311","checked_at":"2026-10-20T12:21:01.351Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Error","description":"j-Error: easy usage; errors are displayed only if the path contains an array in the form below:","messages":[{"role":"user","content":"j-Error\n\nj-Error: easy usage; errors are displayed only if the path contains an array in the form below:"},{"role":"assistant","content":"- errors are displayed only if the path contains an array in the form below:\n\n```js\n[\n\t{ error: 'Error Message 1' },\n\t{ error: 'Error Message 2' }\n]\n```\n\n__Configuration__:\n- `icon {String}` an error icon (default: `warning`)\n\nUse it with `<ui-component name=\"error\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"error\" path=\"mycustomerror\"></ui-component>\n\n<script>\n\tvar mycustomerror = [{ error: 'Invalid Email Address' }, { error: 'Password is too short' }];\n</script>\n```"}],"knowledge":{"content_sha256":"88efcbf55060f16869e3614e6b98ea2b2aadf80e9afb3c15532ae977d8996e0c","source_sha256":"54d6ba11f2bb44ccf42ff6bb64b295a10902ed17fa4e5af65464bd96a5afe311","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"error\" path=\"mycustomerror\"></ui-component>\n\n<script>\n\tvar mycustomerror = [{ error: 'Invalid Email Address' }, { error: 'Password is too short' }];\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/errorhandler","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/errorhandler/","markdown_url":"https://forge.totaljs.es/en/components/errorhandler.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"f459831fdfad9cfd3acbc5369a5a7a6ca9d98da1603c95d9e3c5b3bfc207daa1","checked_at":"2026-10-20T12:21:02.082Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-ErrorHandler","description":"This component captures the ON('ERROR', function(err event trigger from the ERROR() method and tries to display form more comfortably.","messages":[{"role":"user","content":"j-ErrorHandler\n\nThis component captures the ON('ERROR', function(err event trigger from the ERROR() method and tries to display form more comfortably."},{"role":"assistant","content":"This component captures the `ON('ERROR', function(err` event trigger from the `ERROR()` method and tries to display form more comfortably.\n\n- singleton\n\n__Configuration__:\n\n- `keywords {String}` which will be searched in the error messages (default value: `401=login`)\n\t- must be in the form `search1=target_property,search2=target_property`\n- `exec {String}` a link to the `function(response)`, output:\n\t- `response.items {String Array}`\n\t- `response.message {String}`\n\t- `response.keyword_target_property {Boolean}` optional, according to the `keywords` settings\n- __NEW__: `offline {String}` an offline message, default: `No internet connection`\n\nThe component tries using `j-Message`, `j-Snackbar`, `j-NotifyBar` or `j-Notify` for displaying of errors if you don't have defined `exec` configuration.\n\nUse it with `<ui-component name=\"errorhandler\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"errorhandler\"></ui-component>\n<ui-component name=\"LAZY message\"></ui-component>\n\n<script>\n\n\tsetTimeout(function() {\n\t\tERROR([{ error: 'Error 1' }, { error: 'Error 2' }]);\n\t}, 3000);\n\n</script>\n```"}],"knowledge":{"content_sha256":"d0eff3067a30ff275b0b6037ad8ad0c00145d6e3a7792849ada3138938f37047","source_sha256":"f459831fdfad9cfd3acbc5369a5a7a6ca9d98da1603c95d9e3c5b3bfc207daa1","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","ERROR","LAZY","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"errorhandler\"></ui-component>\n<ui-component name=\"LAZY message\"></ui-component>\n\n<script>\n\n\tsetTimeout(function() {\n\t\tERROR([{ error: 'Error 1' }, { error: 'Error 2' }]);\n\t}, 3000);\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/exec","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/exec/","markdown_url":"https://forge.totaljs.es/en/components/exec.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"e35b635d8729b4216c5fd0fa2b250b4bf298556aaccf31f7502b33ae4efd8856","checked_at":"2026-10-20T12:21:02.832Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Exec","description":"The component executes a method when the user clicks on the same element with the exec, exec2, or exec3 class. More information is below. The component is a singleton, and it works for the whole HTML","messages":[{"role":"user","content":"j-Exec\n\nThe component executes a method when the user clicks on the same element with the exec, exec2, or exec3 class. More information is below. The component is a singleton, and it works for the whole HTML"},{"role":"assistant","content":"The component executes a method when the user clicks on the same element with the `exec`, `exec2`, or `exec3` class. More information is below. The component is a singleton, and it works for the whole HTML document.\n\n__Elements__:\n- Elements can contain `data-exec` attribut with a method name which will be execute when the user click on the element.\n- Elements can contain `data-href` attribute and the component performs `REDIRECT()`.\n\n__Attributes__:\n- `data-exec=\"METHOD_NAME\"` executes a method when user clicks (supports plugin calls via `?` character)\n- `data-prevent=\"true\"` prevents continuing in the `click` event\n- `data-href=\"URL\"` performs `REDIRECT()`\n\n__Attributes for the double click__:\n- `data-exec2=\"METHOD_NAME\"` executes a method when user clicks (supports plugin calls via `?` character)\n- `data-prevent2=\"true\"` prevents continuing in the `click` event\n- `data-href2=\"URL\"` performs `REDIRECT()`\n\n__Attributes for the context-menu__:\n- `data-exec3=\"METHOD_NAME\"` executes a method when user clicks (supports plugin calls via `?` character)\n- `data-href3=\"URL\"` performs `REDIRECT()`\n\n__Good to know__:\n\n- the component executes a target function in the form `function(element, event)`\n- `data-exec=`, `data-exec2=`, `data-exec3=` can contain `@METHOD_NAME` that will be executed in the current component plugin calls `component/METHOD_NAME(element, e)`\n\n\n__NEW: Extensions__:\n\n```js\nSETTER('exec/register', function(exec, el, e, type) {\n\n\t// @exec {String} a value from data-exec\n\t// @el {jQuery element}\n\t// @e {Event}\n\t// @type {String} empty '': click, '2': double-click '3': context menu\n\t// registers a new extension\n\n\tif (exec.charAt(0) === '/') {\n\n\t\tEXEC('customplugin/' + exec.substring(1), el, e);\n\n\t\t// \"return true\" stops the next processing of Exec\n\t\treturn true;\n\n\t}\n\n});\n```\n\nUse it with `<ui-component name=\"exec\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"exec\"></ui-component>\n<a href=\"javascript:void(0)\" class=\"exec\" data-exec=\"myClick\">1</a>\n<a href=\"javascript:void(0)\" class=\"exec\" data-exec=\"myClick\">2</a>\n<p class=\"exec\" data-exec=\"myClick\">3</p>\n<span class=\"exec\" data-exec=\"myClick\">4</span>\n<div class=\"exec\" data-exec=\"myClick\">5</div>\n\n<script>\n\tfunction myClick(el) {\n        alert('CLICK: ' + el.html());\n    }\n</script>\n```"}],"knowledge":{"content_sha256":"f991dd13f60f1964d740611e087fa98613535d4f28b5458f8eff001006486c11","source_sha256":"e35b635d8729b4216c5fd0fa2b250b4bf298556aaccf31f7502b33ae4efd8856","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","CLICK","DEF","EXEC","HTML","METHOD_NAME","MIT","REDIRECT","SETTER","URL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"exec\"></ui-component>\n<a href=\"javascript:void(0)\" class=\"exec\" data-exec=\"myClick\">1</a>\n<a href=\"javascript:void(0)\" class=\"exec\" data-exec=\"myClick\">2</a>\n<p class=\"exec\" data-exec=\"myClick\">3</p>\n<span class=\"exec\" data-exec=\"myClick\">4</span>\n<div class=\"exec\" data-exec=\"myClick\">5</div>\n\n<script>\n\tfunction myClick(el) {\n        alert('CLICK: ' + el.html());\n    }\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/expander","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/expander/","markdown_url":"https://forge.totaljs.es/en/components/expander.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"32d542ccaa963e2e974fe5084eeca8ffe18a19f8d02ad0960de705e52f847164","checked_at":"2026-10-20T12:21:03.578Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Expander","description":"j-Expander: NEW height {Number} a max. height (default: 150); NEW iconup {String} icon up (default: ti-angle-double-up)","messages":[{"role":"user","content":"j-Expander\n\nj-Expander: NEW height {Number} a max. height (default: 150); NEW iconup {String} icon up (default: ti-angle-double-up)"},{"role":"assistant","content":"__Configuration__:\n\n- `expand {Boolean}` expands content (default: `false`)\n- __NEW__ `height {Number}` a max. height (default: `150`)\n- __NEW__ `iconup {String}` icon up (default: `ti-angle-double-up`)\n- __NEW__ `icondown {String}` icon down (default: `ti-angle-double-down`)\n\nUse it with `<ui-component name=\"expander\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"expander\">\n\tBEG\n\t<div>0</div>\n\t<div>1</div>\n\t<div>2</div>\n\t<div>3</div>\n\t<div>4</div>\n\t<div>5</div>\n\t<div>6</div>\n\t<div>7</div>\n\t<div>8</div>\n\t<div>9</div>\n\t<div>0</div>\n\t<div>1</div>\n\t<div>2</div>\n\t<div>3</div>\n\t<div>4</div>\n\t<div>5</div>\n\t<div>6</div>\n\t<div>7</div>\n\t<div>8</div>\n\t<div>9</div>\n\tEND\n</ui-component>\n```"}],"knowledge":{"content_sha256":"6b6bd4062f07ae1775662ed6d936d0297ea69cea8ffa847a17057ebef40541ef","source_sha256":"32d542ccaa963e2e974fe5084eeca8ffe18a19f8d02ad0960de705e52f847164","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["BEG","CDN","DEF","END","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"expander\">\n\tBEG\n\t<div>0</div>\n\t<div>1</div>\n\t<div>2</div>\n\t<div>3</div>\n\t<div>4</div>\n\t<div>5</div>\n\t<div>6</div>\n\t<div>7</div>\n\t<div>8</div>\n\t<div>9</div>\n\t<div>0</div>\n\t<div>1</div>\n\t<div>2</div>\n\t<div>3</div>\n\t<div>4</div>\n\t<div>5</div>\n\t<div>6</div>\n\t<div>7</div>\n\t<div>8</div>\n\t<div>9</div>\n\tEND\n</ui-component>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/expansionpanel","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/expansionpanel/","markdown_url":"https://forge.totaljs.es/en/components/expansionpanel.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"d6bd025af24783b73c1f96ed91cc0c433639b52f778e1e0f89cfdbaf98eee558","checked_at":"2026-10-20T12:21:04.465Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-ExpansionPanel","description":"j-ExpansionPanel options: multiple, icon, data-id, title, icon","messages":[{"role":"user","content":"j-ExpansionPanel\n\nj-ExpansionPanel options: multiple, icon, data-id, title, icon"},{"role":"assistant","content":"__Configuration__:\n\n- `multiple {Boolean}` allows multiple opening of panels (default: `false`)\n- `icon {String}` a label icon for every panel (default: `ti ti-caret-right`)\n\n### How to define panels?\n\n```html\n\n<ui-component name=\"expansionpanel\" path=\"*tab\" config=\"multiple:0\" class=\"invisible\">\n\n\t<section data-id=\"logs\" title=\"Logs\" data-icon=\"ti ti-history\">\n\n\t\tHTML CONTENT\n\n\t</section>\n\n\t<section data-id=\"parts\" title=\"Parts\" data-icon=\"ti ti-code-branch\">\n\n\t\tHTML CONTENT\n\n\t</section>\n\n</ui-component>\n```\n\n__Attributes__:\n\n- `data-id` __required__, it must contain an identifier of the panel\n- `title` __required__, it must contain a panel name\n- `icon` optional, it can contain Total.js Icon (default: `config.icon`)\n\nUse it with `<ui-component name=\"expansionpanel\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"expansionpanel\" path=\"*tab\" config=\"multiple:0\" class=\"invisible\">\n\t<section data-id=\"logs\" title=\"Logs\" data-icon=\"ti ti-history\">\n\t\t<div class=\"padding\">Log</div>\n\t</section>\n\t<section data-id=\"parts\" title=\"Parts\" data-icon=\"ti ti-code-branch\">\n\t\t<div class=\"padding\">Parts</div>\n\t</section>\n\t<section data-id=\"files\" title=\"Files\" data-icon=\"ti ti-copy\">\n\t\t<div class=\"padding\">Files</div>\n\t</section>\n\t<section data-id=\"relations\" title=\"Relations\" data-icon=\"ti ti-link\">\n\t\t<div class=\"padding\">Relations</div>\n\t</section>\n\t<section data-id=\"maintenance\" title=\"Maintenance plan\" data-icon=\"ti ti-calendar\">\n\t\t<div class=\"padding\">Maintenance plan</div>\n\t</section>\n</ui-component>\n\n<script>\n\tvar common = { tab: '' };\n</script>\n```"}],"knowledge":{"content_sha256":"d43385300569704d6c795effbbfd366af1848cddcac9a6dedf8053029b9f1d8e","source_sha256":"d6bd025af24783b73c1f96ed91cc0c433639b52f778e1e0f89cfdbaf98eee558","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","CONTENT","DEF","HTML","MIT","Total.js"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"expansionpanel\" path=\"*tab\" config=\"multiple:0\" class=\"invisible\">\n\t<section data-id=\"logs\" title=\"Logs\" data-icon=\"ti ti-history\">\n\t\t<div class=\"padding\">Log</div>\n\t</section>\n\t<section data-id=\"parts\" title=\"Parts\" data-icon=\"ti ti-code-branch\">\n\t\t<div class=\"padding\">Parts</div>\n\t</section>\n\t<section data-id=\"files\" title=\"Files\" data-icon=\"ti ti-copy\">\n\t\t<div class=\"padding\">Files</div>\n\t</section>\n\t<section data-id=\"relations\" title=\"Relations\" data-icon=\"ti ti-link\">\n\t\t<div class=\"padding\">Relations</div>\n\t</section>\n\t<section data-id=\"maintenance\" title=\"Maintenance plan\" data-icon=\"ti ti-calendar\">\n\t\t<div class=\"padding\">Maintenance plan</div>\n\t</section>\n</ui-component>\n\n<script>\n\tvar common = { tab: '' };\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/extend","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/extend/","markdown_url":"https://forge.totaljs.es/en/components/extend.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"2c90b19a2b9c260956334c9449987c5c1b11351ccab799be9d86542749bd80b2","checked_at":"2026-10-20T12:21:05.500Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Extend","description":"This component is a specific component with powerful functionality. When the component is evaluated then it executes two events in the form below. The component can be helpful in web applications that","messages":[{"role":"user","content":"j-Extend\n\nThis component is a specific component with powerful functionality. When the component is evaluated then it executes two events in the form below. The component can be helpful in web applications that"},{"role":"assistant","content":"This component is a specific component with powerful functionality. When the component is evaluated then it executes two events in the form below. The component can be helpful in web applications that can be dynamically extended.\n\n__Configuration__:\n\n- `name {String}` A name for extend, the component will emit `ON('extend_' + name)` event\n\nThe component emits only two events that you can catch somewhere:\n\n```js\n// NAME: is defined in the component configuration\nON('extend_NAME', function(opt) {\n\t// opt.name {String} with a value according to the \"config.name\"\n\t// opt.component {Instance of j-Extend}\n\t// opt.element {jQuery element}\n\t// opt.dom {Node}\n});\n\nON('extend', function(opt) {\n\t// opt.name {String} with a value according to the \"config.name\"\n\t// opt.component {Instance of j-Extend}\n\t// opt.element {jQuery element}\n\t// opt.dom {Node}\n});\n```\n\nUse it with `<ui-component name=\"extend\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Pete Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"width:300px\">\n\t<ui-plugin path=\"form\">\n\t\t<ui-component name=\"extend\" config=\"name:test\"></ui-component>\n\t</ui-plugin>\n</div>\n\n<script>\n\tON('extend_test', function(opt) {\n\t\t// Here you can use IMPORT() method\n\t\topt.element.append(`\n\t\t\t<div class=\"m\">Extended:</div>\n\t\t\t<ui-component name=\"input\" path=\"?.name\" config=\"required:1\">Label</ui-component>\n\t\t`);\n\t});\n</script>\n```"}],"knowledge":{"content_sha256":"3246ce39351407d1048c86711b27c1346f4aa1a0dcbfedbaa3c0dee3c0d4f644","source_sha256":"2c90b19a2b9c260956334c9449987c5c1b11351ccab799be9d86542749bd80b2","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","IMPORT","MIT","NAME"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"width:300px\">\n\t<ui-plugin path=\"form\">\n\t\t<ui-component name=\"extend\" config=\"name:test\"></ui-component>\n\t</ui-plugin>\n</div>\n\n<script>\n\tON('extend_test', function(opt) {\n\t\t// Here you can use IMPORT() method\n\t\topt.element.append(`\n\t\t\t<div class=\"m\">Extended:</div>\n\t\t\t<ui-component name=\"input\" path=\"?.name\" config=\"required:1\">Label</ui-component>\n\t\t`);\n\t});\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/faicons","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/faicons/","markdown_url":"https://forge.totaljs.es/en/components/faicons.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"bd581627eff588edf88348128ed39f039ec9dbb9ca303d1dc18d65586f2b96fe","checked_at":"2026-10-20T12:21:06.384Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-FaIcons","description":"j-FaIcons: Font-Awesome Icons; singleton","messages":[{"role":"user","content":"j-FaIcons\n\nj-FaIcons: Font-Awesome Icons; singleton"},{"role":"assistant","content":"- Font-Awesome Icons\n- singleton\n- supports pro icons\n\n__Configuration__:\n\n- `search {String}` a placeholder for search field (default: `Search`)\n\n## Usage\n\n```js\nvar opt = {};\n\n// opt.offsetX {Number}    : adds X offset (default: 0)\n// opt.offsetY {Number}    : adds Y offset (default: 0)\n// opt.align {String}      : align `left` (default), `center` or `right`\n// opt.position {String}   : position `top` (default) or `bottom`\n// opt.empty {Boolean}     : adds empty icon (default: false)\n\nopt.element = YOUR_ELEMENT;\n// or if you want to use a fixed position:\n// opt.x {Number}          : `x` position\n// opt.y {Number}          : `y` position\n\nopt.callback = function(icon) {\n\tconsole.log(icon);\n};\n\nSETTER('faicons/show', opt);\n```\n\nUse it with `<ui-component name=\"faicons\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"faicons\"></ui-component>\n\n<div class=\"container\">\n\t<div class=\"row\">\n\t\t<div class=\"col-xs-4 col-xs-offset-4\" style=\"text-align:center\">\n\t\t\t<a href=\"javascript:void(0)\" style=\"color:black;font-size:30px\"><i class=\"far fa-grin-alt\"></i></a>\n\t\t</div>\n\t</div>\n</div>\n\n<script>\n$(document).on('click', 'a', function() {\n\tvar opt = {};\n\topt.align = 'center';\n\topt.position = 'top';\n\topt.element = this;\n\topt.callback = function(selected) {\n\t\tconsole.log(selected);\n\t};\n\tSETTER('faicons/show', opt);\n});\n</script>\n```"}],"knowledge":{"content_sha256":"9a6c2b7adbfa219b1f139ac74c487140658b4834fac3d75a506b1ec62589dc9e","source_sha256":"bd581627eff588edf88348128ed39f039ec9dbb9ca303d1dc18d65586f2b96fe","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","SETTER","YOUR_ELEMENT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"faicons\"></ui-component>\n\n<div class=\"container\">\n\t<div class=\"row\">\n\t\t<div class=\"col-xs-4 col-xs-offset-4\" style=\"text-align:center\">\n\t\t\t<a href=\"javascript:void(0)\" style=\"color:black;font-size:30px\"><i class=\"far fa-grin-alt\"></i></a>\n\t\t</div>\n\t</div>\n</div>\n\n<script>\n$(document).on('click', 'a', function() {\n\tvar opt = {};\n\topt.align = 'center';\n\topt.position = 'top';\n\topt.element = this;\n\topt.callback = function(selected) {\n\t\tconsole.log(selected);\n\t};\n\tSETTER('faicons/show', opt);\n});\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/faiconsbutton","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/faiconsbutton/","markdown_url":"https://forge.totaljs.es/en/components/faiconsbutton.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"34b137a025c278648ffddbb00edb8c3ff26aea9bbf85a24fcca2e8b6f1a60a1e","checked_at":"2026-10-20T12:21:07.126Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-FaIconsButton","description":"j-FaIconsButton: it uses j-FaIcons","messages":[{"role":"user","content":"j-FaIconsButton\n\nj-FaIconsButton: it uses j-FaIcons"},{"role":"assistant","content":"- it uses `j-FaIcons`\n\n__Configuration__:\n\n- `default {String}` a default color (default: `#FFFFFF`)\n- `align {String}` align for j-ColorPicker (default: `left`)\n- `position {String}` position for j-ColorPicker (default: `top`)\n- `offsetX {Number}` offset-x for j-ColorPicker (default: `0`)\n- `offsetY {Number}` offset-y for j-ColorPicker (default: `0`)\n- `disabled {Boolean}` can disabled this component (default: `false`)\n- `empty {Boolean}` adds an empty icon (default: `true`)\n\nUse it with `<ui-component name=\"faiconsbutton\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"faicons\"></ui-component>\n\n<div class=\"row\">\n\t<div class=\"col-xs-4\">\n\t\t<ui-component name=\"faiconsbutton\" path=\"icon\"></ui-component>\n\t</div>\n</div>\n\n<script>\n\tvar icon = 'fa fa-home';\n</script>\n```"}],"knowledge":{"content_sha256":"a9680c0a55081a8b1ed15b9e6e4913306a0c7806948004f4ba8972dfb708e834","source_sha256":"34b137a025c278648ffddbb00edb8c3ff26aea9bbf85a24fcca2e8b6f1a60a1e","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","FFFFFF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"faicons\"></ui-component>\n\n<div class=\"row\">\n\t<div class=\"col-xs-4\">\n\t\t<ui-component name=\"faiconsbutton\" path=\"icon\"></ui-component>\n\t</div>\n</div>\n\n<script>\n\tvar icon = 'fa fa-home';\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/faviconunread","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/faviconunread/","markdown_url":"https://forge.totaljs.es/en/components/faviconunread.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"b355ad56b520dc31dfe19b2b3cde78f1855ce38bf3ac7fc29145e6b83ba12643","checked_at":"2026-10-20T12:21:07.861Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-FaviconUnread","description":"j-FaviconUnread: NEW badge {Boolean} optional, enables usage of navigator.setAppBadge() (default: true)","messages":[{"role":"user","content":"j-FaviconUnread\n\nj-FaviconUnread: NEW badge {Boolean} optional, enables usage of navigator.setAppBadge() (default: true)"},{"role":"assistant","content":"__Configuration__\n\n- `fg {String}` foreground color (default: `white`)\n- `bg {String}` backround color (default: `red`)\n- `selector {String}` optional, selector for a specific image element (default: `favicon` in `head` of HTML)\n- __NEW__ `badge {Boolean}` optional, enables usage of `navigator.setAppBadge()` (default: `true`)\n\nUse it with `<ui-component name=\"faviconunread\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<!-- REMOVE \"selector:#favicon\" IN YOUR PROJECT!!! -->\n<ui-component name=\"faviconunread\" path=\"common.unread\" config=\"selector:#favicon\"></ui-component>\n\n<div>\n\t<img src=\"/favicon.ico\" id=\"favicon\" />\n</div>\n\n<script>\n\tSET('common.unread', 4, 2000);\n</script>\n```"}],"knowledge":{"content_sha256":"e85bbffc5ac16797818516a34d4f92c9d67ebfeeade3134935cbbdb3c88386d1","source_sha256":"b355ad56b520dc31dfe19b2b3cde78f1855ce38bf3ac7fc29145e6b83ba12643","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","HTML","MIT","PROJECT","REMOVE","SET","YOUR"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<!-- REMOVE \"selector:#favicon\" IN YOUR PROJECT!!! -->\n<ui-component name=\"faviconunread\" path=\"common.unread\" config=\"selector:#favicon\"></ui-component>\n\n<div>\n\t<img src=\"/favicon.ico\" id=\"favicon\" />\n</div>\n\n<script>\n\tSET('common.unread', 4, 2000);\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/features","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/features/","markdown_url":"https://forge.totaljs.es/en/components/features.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"9bfc99c7d0ebb3b276d7da413312fe9967c84eaaf1c671872c32f77d245599ae","checked_at":"2026-10-20T12:21:08.741Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Features","description":"j-Features: only for desktop computers; singleton","messages":[{"role":"user","content":"j-Features\n\nj-Features: only for desktop computers; singleton"},{"role":"assistant","content":"- only for desktop computers\n- singleton\n- works with Bootstrap Grid System\n\n### Methods\n\nMethod: `component.open(opt)`\n\n- `opt.placeholder {String}` a placeholder for search input (default: `config.placeholder`)\n- `opt.items {Object Array}` with list of features (required):\n    - `name {String}` a feature name\n    - `icon {String}` (optional) icon without `fa-`\n    - `keywords {String}` (optional) additional keywords for searching\n    - `value {Object/String/Number}` a value\n- `opt.scrolltop {Boolean}` scrolls to top until it's displaying the window (default: `false`)\n- `opt.callback(selected) {Function}` a callback which is called when the user performs click for some feature. `selected` argument contains selected item.\n\nMethod: `component.hide()`\n\n__Configuration__:\n\n- `placeholder {String}` a placeholder for the search input\n- `height {Number}` height of list item (optional, default `35`)\n\nUse it with `<ui-component name=\"features\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\tbody { background-color: #F0F0F0; }\n</style>\n\n<ui-component name=\"features\" config=\"placeholder:Search ...\"></ui-component>\n\n<div class=\"container\">\n\t<a href=\"javascript:features()\">Show features</a>\n</div>\n\n<script>\n\tfunction features() {\n\t\tvar opt = {};\n\t\topt.items = [{ name: 'Dashboard' }, { name: 'Invoices' }, { name: 'Orders' }, { name: 'Products' }, { name: 'Pages' }, { name: 'Notes' }, { name: 'Users', icon: 'user' }, { name: 'Settings' }];\n\t\topt.callback = function(selected) {\n\t\t\tconsole.log('CLICK', selected);\n\t\t};\n\t\tSETTER('features/show', opt);\n\t}\n\tsetTimeout(features, 500);\n</script>\n```"}],"knowledge":{"content_sha256":"80d4d34e5615578831bf6c9dd696eb7b313fc9e371955e7156ceebdba94922c5","source_sha256":"9bfc99c7d0ebb3b276d7da413312fe9967c84eaaf1c671872c32f77d245599ae","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","CLICK","DEF","F0F0F0","MIT","SETTER"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\tbody { background-color: #F0F0F0; }\n</style>\n\n<ui-component name=\"features\" config=\"placeholder:Search ...\"></ui-component>\n\n<div class=\"container\">\n\t<a href=\"javascript:features()\">Show features</a>\n</div>\n\n<script>\n\tfunction features() {\n\t\tvar opt = {};\n\t\topt.items = [{ name: 'Dashboard' }, { name: 'Invoices' }, { name: 'Orders' }, { name: 'Products' }, { name: 'Pages' }, { name: 'Notes' }, { name: 'Users', icon: 'user' }, { name: 'Settings' }];\n\t\topt.callback = function(selected) {\n\t\t\tconsole.log('CLICK', selected);\n\t\t};\n\t\tSETTER('features/show', opt);\n\t}\n\tsetTimeout(features, 500);\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/feedbackbutton","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/feedbackbutton/","markdown_url":"https://forge.totaljs.es/en/components/feedbackbutton.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"17fa5c1fa6104e111a069521be9c52d4829c05444dad2641a329913c2377a262","checked_at":"2026-10-20T12:21:09.477Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-FeedbackButton","description":"j-FeedbackButton: singleton; universal usage","messages":[{"role":"user","content":"j-FeedbackButton\n\nj-FeedbackButton: singleton; universal usage"},{"role":"assistant","content":"- singleton\n- universal usage\n- supports background-color change\n- supports left or right position\n- supports expiration time\n\n__Configuration__:\n\n- `color {String}` background color setting (default: `#333`)\n- `position {String}` a position, can be `left` or `right` (default: `right`)\n- `expire {String}` sets the expiration date (default: `3 days`)\n- `icon {String}` a font-awesome icon name (default: `fa fa-comment-alt`)\n- `animate {Boolean}` enables animation (default: `true`)\n- `label {String}` a label for the button (default: `Feedback`)\n- `url {String}` a link to URL address which will be open if the user click on the button\n- or `exec {String}` a link to method `function(hide)` which will be executed after click on the button\n\nUse it with `<ui-component name=\"feedbackbutton\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Jozef Marton. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"feedbackbutton\" config=\"color:#333;position:right\"></ui-component>\n```"}],"knowledge":{"content_sha256":"7d0cb03032c3ca7c10a308989a3932d7a1f17f7e8a2eb820397c6d70a4be71f2","source_sha256":"17fa5c1fa6104e111a069521be9c52d4829c05444dad2641a329913c2377a262","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","URL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"feedbackbutton\" config=\"color:#333;position:right\"></ui-component>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/filebrowser","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/filebrowser/","markdown_url":"https://forge.totaljs.es/en/components/filebrowser.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"811fd95501f132d2fd9b28b5a1ed397ffc8f71da7b61c9bf14c498c883fbb9e6","checked_at":"2026-10-20T12:21:10.212Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-FileBrowser","description":"The component will open a file browser and returns selected files.","messages":[{"role":"user","content":"j-FileBrowser\n\nThe component will open a file browser and returns selected files."},{"role":"assistant","content":"The component will open a file browser and returns selected files.\n\n\n__Methods__:\n\n- `component.open(opt)` - opens file browser\n\t- `opt.multiple {Boolean}` enables multiple files (default: `false`)\n\t- `opt.accept {String}` allows only specified content-type (default: `undefined`)\n\t- `opt.callback {Function(files, e)}` a callback\n\nUse it with `<ui-component name=\"filebrowser\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"filebrowser\"></ui-component>\n<a href=\"javascript:fopen()\">OPEN FILE</a>\n\n<script>\n\tfunction fopen() {\n\t\tvar opt = {};\n\t\topt.multiple = true;\n\t\t// opt.accept = 'image/*';\n\t\topt.callback = function(files, e) {\n\t\t\tconsole.log(files, e);\n\t\t};\n\t\tSETTER('filebrowser/show', opt);\n\t}\n</script>\n```"}],"knowledge":{"content_sha256":"8669e92a3c5d0bd8069ed75378492a6c3111e62f6e69162f9060a3776075daac","source_sha256":"811fd95501f132d2fd9b28b5a1ed397ffc8f71da7b61c9bf14c498c883fbb9e6","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","FILE","MIT","OPEN","SETTER"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"filebrowser\"></ui-component>\n<a href=\"javascript:fopen()\">OPEN FILE</a>\n\n<script>\n\tfunction fopen() {\n\t\tvar opt = {};\n\t\topt.multiple = true;\n\t\t// opt.accept = 'image/*';\n\t\topt.callback = function(files, e) {\n\t\t\tconsole.log(files, e);\n\t\t};\n\t\tSETTER('filebrowser/show', opt);\n\t}\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/filereader","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/filereader/","markdown_url":"https://forge.totaljs.es/en/components/filereader.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"1479bcad360506e09b5930f4b0f77cea5234310af3163526bad696bdb9dbef7e","checked_at":"2026-10-20T12:21:10.943Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-FileReader","description":"j-FileReader: singleton; easy usage","messages":[{"role":"user","content":"j-FileReader\n\nj-FileReader: singleton; easy usage"},{"role":"assistant","content":"- singleton\n- only for text files\n\n__Methods__:\n- `component.open(opt)` - opens file browser\n\t- `opt.multiple {Boolean}` enables multiple files (default: `false`)\n\t- `opt.accept {String}` allows only specified content-type (default: `undefined`)\n\t- `opt.base64 {Boolean}` reads a file in Base64 encoding (default: `false`)\n\t- __NEW__: `opt.files {HTTP Files}` reads files e.g. from Drag & Drop\n\t- `opt.callback {Function(file)}` a callback\n\t- __NEW__: `opt.progress {Function(percentage, file)}` optional\n\n```js\nvar opt = {};\nopt.accept = 'text/*';\n// opt.files = e.files;\nopt.callback = function(file) {\n\tconsole.log(file);\n};\nSETTER('filereader/open', opt);\n```\n\nUse it with `<ui-component name=\"filereader\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"filereader\"></ui-component>\n<a href=\"javascript:fopen()\">OPEN FILE</a>\n\n<script>\n\tfunction fopen() {\n\t\tvar opt = {};\n\t\topt.accept = 'text/*';\n\t\topt.callback = function(file) {\n\t\t\t$(document.body).append('<div><pre><code>{0}</code></pre></div>'.format(JSON.stringify(file, null, '    ').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;')));\n\t\t};\n\t\tSETTER('filereader/open', opt);\n\t}\n</script>\n```"}],"knowledge":{"content_sha256":"5c394280e9c16c796e245f9c9667eb9d2b862d74ce3500d6ec431a99a3b68d3f","source_sha256":"1479bcad360506e09b5930f4b0f77cea5234310af3163526bad696bdb9dbef7e","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","FILE","HTTP","JSON","MIT","OPEN","SETTER"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"filereader\"></ui-component>\n<a href=\"javascript:fopen()\">OPEN FILE</a>\n\n<script>\n\tfunction fopen() {\n\t\tvar opt = {};\n\t\topt.accept = 'text/*';\n\t\topt.callback = function(file) {\n\t\t\t$(document.body).append('<div><pre><code>{0}</code></pre></div>'.format(JSON.stringify(file, null, '    ').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/\"/g, '&quot;')));\n\t\t};\n\t\tSETTER('filereader/open', opt);\n\t}\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/filesaver","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/filesaver/","markdown_url":"https://forge.totaljs.es/en/components/filesaver.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"862eaa7aa61fef0dbc0c7f595d5d476cb355aa14c1874b23fad4a48a1d154683","checked_at":"2026-10-20T12:21:18.152Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-FileSaver","description":"This component is a wrapper for the [FileSaver.js library](https://github.com/eligrey/FileSaver.js/). With this component you can easily save locally generated files directly in the web browser.","messages":[{"role":"user","content":"j-FileSaver\n\nThis component is a wrapper for the [FileSaver.js library](https://github.com/eligrey/FileSaver.js/). With this component you can easily save locally generated files directly in the web browser."},{"role":"assistant","content":"This component is a wrapper for the [FileSaver.js library](https://github.com/eligrey/FileSaver.js/). With this component you can easily save locally generated files directly in the web browser.\n\n- singleton\n\n### Methods\n\n- `component.save(filename, string/blob, [contentType])`\n\nUse it with `<ui-component name=\"filesaver\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"LAZY filesaver\"></ui-component>\n<button>Save something</button>\n\n<script>\n\n\t$(document).on('click', 'button', function() {\n\t\tSETTER('filesaver/save', 'totaljs.txt', 'Hello world!\\n\\nwww.totaljs.com');\n\t});\n\n</script>\n```"}],"knowledge":{"content_sha256":"b7bd27ae5922a8ec9f48cb64cd5c34e50035deb3b15170a503d212a3a1c6634a","source_sha256":"862eaa7aa61fef0dbc0c7f595d5d476cb355aa14c1874b23fad4a48a1d154683","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","LAZY","MIT","SETTER"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"LAZY filesaver\"></ui-component>\n<button>Save something</button>\n\n<script>\n\n\t$(document).on('click', 'button', function() {\n\t\tSETTER('filesaver/save', 'totaljs.txt', 'Hello world!\\n\\nwww.totaljs.com');\n\t});\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/fileupload","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/fileupload/","markdown_url":"https://forge.totaljs.es/en/components/fileupload.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"d123fe2efa54d7b79f910a194cb51afbae2fe7f7335c97600f278e70f0515e2d","checked_at":"2026-10-20T12:21:19.031Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-FileUpload","description":"Is very helpful component for uploading e.g. profile photos. I consider this component as inline file upload. Look into the html source. The component uploads files via multipart/form-data.","messages":[{"role":"user","content":"j-FileUpload\n\nIs very helpful component for uploading e.g. profile photos. I consider this component as inline file upload. Look into the html source. The component uploads files via multipart/form-data."},{"role":"assistant","content":"Is very helpful component for uploading e.g. profile photos. I consider this component as inline file upload. Look into the html source. The component uploads files via `multipart/form-data`.\n\n__Configuration__:\n\n- `url` {String} a target URL for the uploading\n- `accept` {String} (optional) can contain a content-type frow browsing files e.g. `image/*`\n- `multiple` enables `multiple` files upload (default: `false`)\n- `array` {Boolean} enables pushing an uploaded value to array (`path` must be the array)\n- `label` {String} (optional) changes a current `innerHTML` (default: `innerHTML`)\n- `disabled` {Boolean} disables\n\n__Friendly components__:\nThis component works with `loading` (when the file is uploading) and `message` (when server throws an error) component.\n\nUse it with `<ui-component name=\"fileupload\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<a href=\"javascript:void(0)\"><ui-component name=\"fileupload\" path=\"mypicture\" config=\"url:/my-upload-url/\">Upload</ui-component></a>\n\n<script>\n\tvar mypicture = '';\n</script>\n```"}],"knowledge":{"content_sha256":"7121cf1c353c6ba4843c67c5a0d9e02911090d8f3f6584cfb37726f014d965ec","source_sha256":"d123fe2efa54d7b79f910a194cb51afbae2fe7f7335c97600f278e70f0515e2d","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","URL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<a href=\"javascript:void(0)\"><ui-component name=\"fileupload\" path=\"mypicture\" config=\"url:/my-upload-url/\">Upload</ui-component></a>\n\n<script>\n\tvar mypicture = '';\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/fileuploader","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/fileuploader/","markdown_url":"https://forge.totaljs.es/en/components/fileuploader.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"05e44ae45b1dc89811a7a6c271ffb068006a3d49b23b11f1d7ac00dfed951750","checked_at":"2026-10-20T12:21:19.925Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-FileUploader","description":"A simple component for uploading files. It supports auto-resizing images.","messages":[{"role":"user","content":"j-FileUploader\n\nA simple component for uploading files. It supports auto-resizing images."},{"role":"assistant","content":"A simple component for uploading files. It supports auto-resizing images.\n\n- __IMPORTANT__: in jComponent v20, the value of `opt.url` must be in the form `POST /url/`\n\n__Configuration__:\n\n- `url {String}` a default URL address for uploading\n\n__Methods__:\n\n- `fileuploader/upload(opt)` shows a file dialog\n\t- `opt.url {String}` __important__ URL where will be image uploaded\n\t- `opt.callback(response, errors) {Function}` a callback __important__\n\t- `opt.multiple {Boolean}` supports multiple files (default: `false`)\n\t- `opt.progress(percentage)` {Function}\n\t- `opt.accept {String}` accept type (default: `*/*`)\n\t- `opt.prefix {String}` a name prefix `<input name=\"file{0}\"` (default: `file{0}`)\n\t- `opt.files {FileList}` optional (if you use e.g Drag & Drop)\n\t- `opt.data {Object}` optional, additional custom data `key_string:value_string`\n\t- `opt.width {Number}` optional, image width\n\t- `opt.height {Number}` optional, image height\n\t- `opt.background {String}` optional, background color defined in HEX or can be `transparent`\n\t- `opt.keeporiginal {Boolean}` optional, prevents for double resizing (default: `false`)\n\t- `opt.onlylarger {Boolean}` optional, resizes images only larger than `opt.width` or `opt.height`\n\t- `opt.base64 {String}` optional, it can contain a valid `dataURI` format (required to fill `opt.filename`)\n\t- `opt.filename {String}` optional, required with `opt.base64` field\n\t- `opt.quality {Number}` optional, a default JPEG quality (default: `90`)\n\t- __NEW__: `opt.disproportionate {Boolean}` use `opt.width` or `opt.height` as maximum values\n\n__Friendly components__:\nThis component works with `loading` when the file is uploading.\n\nUse it with `<ui-component name=\"fileuploader\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"fileuploader\"></ui-component>\n<div class=\"exec link\" data-exec=\"upload\">Upload</div>\n\n<script>\n\tfunction upload() {\n\n\t\tvar opt = {};\n\n\t\topt.url = '/api/upload/';\n\n\t\t// jComponent v20:\n\t\t// opt.url = 'POST /api/upload/';\n\n\t\t// opt.multiple = true;\n\t\t// opt.accept = 'text/*';\n\n\t\topt.callback = function(response, err) {\n\t\t\tconsole.log(response, err);\n\t\t};\n\t\topt.progress = function(percentage) {\n\t\t\tconsole.log(percentage);\n\t\t};\n\n\t\tSETTER('fileuploader/upload', opt);\n\t}\n</script>\n```"}],"knowledge":{"content_sha256":"0bf0e5010e88a88d8ab03a2096071e4d462904f248953f0ebfda53e2140a173c","source_sha256":"05e44ae45b1dc89811a7a6c271ffb068006a3d49b23b11f1d7ac00dfed951750","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","HEX","JPEG","MIT","POST","SETTER","URL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"fileuploader\"></ui-component>\n<div class=\"exec link\" data-exec=\"upload\">Upload</div>\n\n<script>\n\tfunction upload() {\n\n\t\tvar opt = {};\n\n\t\topt.url = '/api/upload/';\n\n\t\t// jComponent v20:\n\t\t// opt.url = 'POST /api/upload/';\n\n\t\t// opt.multiple = true;\n\t\t// opt.accept = 'text/*';\n\n\t\topt.callback = function(response, err) {\n\t\t\tconsole.log(response, err);\n\t\t};\n\t\topt.progress = function(percentage) {\n\t\t\tconsole.log(percentage);\n\t\t};\n\n\t\tSETTER('fileuploader/upload', opt);\n\t}\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/fileuploadlist","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/fileuploadlist/","markdown_url":"https://forge.totaljs.es/en/components/fileuploadlist.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"5acf502d32f74d5efdcf55d8f4dafb3fd08f780b3ec4f2b763a18d48b8781ee8","checked_at":"2026-10-20T12:21:20.667Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-FileUploadList","description":"This component can upload files and the result adds into the model. Model must be the array.","messages":[{"role":"user","content":"j-FileUploadList\n\nThis component can upload files and the result adds into the model. Model must be the array."},{"role":"assistant","content":"This component can upload files and the result adds into the model. Model must be the `array`.\n\n__Configuration__:\n\n- `url` {String} URL to upload (default: `/api/upload/`)\n- `accept` {String} Optional, a content type for filtering files e.g. `image/*`\n- `multiple` {Boolean} Optional, enables multiple files uploading (default: `true`)\n- `progress` {String} a path to `function(percentage)`\n- `text` {String} a key name for rendering of `text` value (default: `name`)\n\n__Model__:\n\nServer must return `Array` in the form below:\n\n```javascript\n[\n\t{\n\t\tname: String,\n\t\tlength: Number,\n\t\turl: String\n\t}\n]\n```\n\nUse it with `<ui-component name=\"fileuploadlist\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"fileuploadlist\" path=\"files\"></ui-component>\n<br />\n<div><a href=\"javascript:browsefiles()\">Upload files</a></div>\n\n<script>\n\tvar files = [];\n\tfiles.push({ name: 'repeater.html', length: 2676, url: '/download/16753-1467713967-repeater.html' });\n\tfiles.push({ name: 'snapshot.js', length: 507, url: '/download/16754-168632909-snapshot.js'});\n\tfiles.push({ name: 'tracking-cms.gif', length: 974730, url: '/download/16755-1806731401-tracking-cms.gif' });\n\n\tfunction browsefiles() {\n\t\tSETTER('fileuploadlist', 'browse');\n\t}\n</script>\n```"}],"knowledge":{"content_sha256":"5ce004b5f58d35be3b8f602313b8706c516c6198990dedce7b766351dbb9dda1","source_sha256":"5acf502d32f74d5efdcf55d8f4dafb3fd08f780b3ec4f2b763a18d48b8781ee8","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","SETTER","URL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"fileuploadlist\" path=\"files\"></ui-component>\n<br />\n<div><a href=\"javascript:browsefiles()\">Upload files</a></div>\n\n<script>\n\tvar files = [];\n\tfiles.push({ name: 'repeater.html', length: 2676, url: '/download/16753-1467713967-repeater.html' });\n\tfiles.push({ name: 'snapshot.js', length: 507, url: '/download/16754-168632909-snapshot.js'});\n\tfiles.push({ name: 'tracking-cms.gif', length: 974730, url: '/download/16755-1806731401-tracking-cms.gif' });\n\n\tfunction browsefiles() {\n\t\tSETTER('fileuploadlist', 'browse');\n\t}\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/fixedmodal","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/fixedmodal/","markdown_url":"https://forge.totaljs.es/en/components/fixedmodal.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"48c7f19729b7eddc2840e833f7da35e3f8b2fa14aa784869350e5510f1665a8f","checked_at":"2026-10-20T12:21:21.409Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-FixedModal","description":"The content of the j-FixedModal is divided to 3 parts: header, body and footer. Each part must be defined. The component adds CSS classes to each part, for example first div will contain ui-fixedmodal","messages":[{"role":"user","content":"j-FixedModal\n\nThe content of the j-FixedModal is divided to 3 parts: header, body and footer. Each part must be defined. The component adds CSS classes to each part, for example first div will contain ui-fixedmodal"},{"role":"assistant","content":"- Works only with `+v17`\n- The component moves the content of this component under `<body>` tag (because of positioning)\n\n\n__Configuration__:\n\n- `width {Number}` modal max-width\n- `if {String}` condition for showing of the modal, it's compared with the value within of `path`\n- `reload {String}` link to a global function and it's executed if the form is showing\n- `submit {String}` link to a global function and it's executed if the submit button is pressed\n- `cancel {String}` link to a global function and it's executed if the cancel button is pressed\n- `enter {Boolean}` captures `enter` key automatically and performs submit (default: `false`)\n- `autofocus {Boolean/String}` can focus an input. `String` === `jQuery selector` for the input\n- `default {String}` a short alias for `DEFAULT(default, true)`\n- `zindex {Number}` can affect z-index (default: `12`)\n\n__Good to know__:\n\nThe content of the `j-FixedModal` is divided to 3 parts: `header`, `body` and `footer`. Each part must be defined. The component adds CSS classes to each part, for example first `div` will contain `ui-fixedmodal-header`, second `ui-fixedmodal-body` and third `ui-fixedmodal-footer`.\n\n---\n\nThis component supports dynamic evaluation of the content of `<script type=\"text/html\">`. The example below contains a script with HTML and the component evaluates the content if the j-Modal will be displayed (only once).\n\n```html\n<ui-component name=\"fixedmodal\" path=\"path\" config=\"config\">\n\t<script type=\"text/html\">\n\t\tA CONTENT\n\t</script>\n</ui-component>\n```\n\nUse it with `<ui-component name=\"fixedmodal\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\nLorem ipsum dolor sit amet, consectetur adipisicing elit. Dolores illum quaerat quisquam voluptatem soluta necessitatibus accusamus possimus consequuntur nihil nam. Fugiat unde nemo, ratione magnam. Placeat minima, fugiat quo a. Esse commodi temporibus quos numquam suscipit! Mollitia accusantium temporibus nisi quibusdam alias provident rerum impedit, fuga officia non illum unde magni recusandae eos ducimus, maiores, dolore voluptates reiciendis accusamus odit quo ipsum veniam. Consectetur vitae minima dolor, possimus earum quae necessitatibus quia! Ex reiciendis nam eius adipisci harum ad. Atque fuga sint deleniti repellat quisquam. Deserunt laudantium maiores dicta, aliquid quidem id veritatis sint rem iusto. Ab similique culpa quasi!\n\n<ui-component name=\"fixedmodal\" path=\"common.form\" config=\"if:folder;submit:folder_submit\" class=\"hidden\">\n\n\t<!-- HEADER -->\n\t<div>\n\t\t<i class=\"ti ti-folder orange\"></i>\n\t\t<strong>New folder</strong>\n\t\tLorem ipsum dolor sit amet, consectetur adipisicing elit. Repellendus qui facilis magni porro reiciendis.\n\t</div>\n\n\t<!-- BODY -->\n\t<div>\n\t\tCONTENT Lorem ipsum dolor sit amet, consectetur adipisicing elit. Odit illo rem dicta temporibus beatae quibusdam dolore fugit, quidem nostrum. Hic asperiores enim ipsum maxime. Hic nisi nulla iusto id commodi?\n\t</div>\n\n\t<!-- FOOTER -->\n\t<div>\n\t\t<button name=\"cancel\">Cancel</button>\n\t\t<button name=\"submit\"><i class=\"ti ti-plus\"></i>Create folder</button>\n\t</div>\n\n</ui-component>\n\n<script>\n\n\tfunction folder_submit(hide) {\n\t\talert('SUBMIT');\n\t\thide();\n\t}\n\n\tvar common = {};\n\n\t// Show the form\n\tcommon.form = 'folder';\n\n</script>\n```"}],"knowledge":{"content_sha256":"761d418e38615c750d7232c782fea7deac80d87579d1aa74204d98fe7128bf34","source_sha256":"48c7f19729b7eddc2840e833f7da35e3f8b2fa14aa784869350e5510f1665a8f","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["BODY","CDN","CONTENT","CSS","DEF","DEFAULT","FOOTER","HEADER","HTML","MIT","SUBMIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"Lorem ipsum dolor sit amet, consectetur adipisicing elit. Dolores illum quaerat quisquam voluptatem soluta necessitatibus accusamus possimus consequuntur nihil nam. Fugiat unde nemo, ratione magnam. Placeat minima, fugiat quo a. Esse commodi temporibus quos numquam suscipit! Mollitia accusantium temporibus nisi quibusdam alias provident rerum impedit, fuga officia non illum unde magni recusandae eos ducimus, maiores, dolore voluptates reiciendis accusamus odit quo ipsum veniam. Consectetur vitae minima dolor, possimus earum quae necessitatibus quia! Ex reiciendis nam eius adipisci harum ad. Atque fuga sint deleniti repellat quisquam. Deserunt laudantium maiores dicta, aliquid quidem id veritatis sint rem iusto. Ab similique culpa quasi!\n\n<ui-component name=\"fixedmodal\" path=\"common.form\" config=\"if:folder;submit:folder_submit\" class=\"hidden\">\n\n\t<!-- HEADER -->\n\t<div>\n\t\t<i class=\"ti ti-folder orange\"></i>\n\t\t<strong>New folder</strong>\n\t\tLorem ipsum dolor sit amet, consectetur adipisicing elit. Repellendus qui facilis magni porro reiciendis.\n\t</div>\n\n\t<!-- BODY -->\n\t<div>\n\t\tCONTENT Lorem ipsum dolor sit amet, consectetur adipisicing elit. Odit illo rem dicta temporibus beatae quibusdam dolore fugit, quidem nostrum. Hic asperiores enim ipsum maxime. Hic nisi nulla iusto id commodi?\n\t</div>\n\n\t<!-- FOOTER -->\n\t<div>\n\t\t<button name=\"cancel\">Cancel</button>\n\t\t<button name=\"submit\"><i class=\"ti ti-plus\"></i>Create folder</button>\n\t</div>\n\n</ui-component>\n\n<script>\n\n\tfunction folder_submit(hide) {\n\t\talert('SUBMIT');\n\t\thide();\n\t}\n\n\tvar common = {};\n\n\t// Show the form\n\tcommon.form = 'folder';\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/floatingbox","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/floatingbox/","markdown_url":"https://forge.totaljs.es/en/components/floatingbox.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"71c2c13dcc3db760948df8985d263199c4c3d062054560f684a3dc130f99bf94","checked_at":"2026-10-20T12:21:22.150Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-FloatingBox","description":"j-FloatingBox: supports dark mode","messages":[{"role":"user","content":"j-FloatingBox\n\nj-FloatingBox: supports dark mode"},{"role":"assistant","content":"- supports __dark mode__\n\n__Configuration__:\n\n- `zindex {Number}` a default z-index for forms (default: `10`)\n\n__Methods__:\n\n- `component.show(opt)` shows a box\n\t- `opt.id {String}` __required__ - a box identifier\n\t- `opt.element {HTML Element}` __required__ - target element\n\t\t- or you can use absolute position:\n\t\t- `opt.x {Number}`\n\t\t- `opt.y {Number}`\n\t- `opt.offsetX {Number}` affects offset X\n\t- `opt.offsetY {Number}` affects offset Y\n\t- `opt.align {String}` align `left` (default), `center` or `right`\n\t- `opt.position {String}` align `top` (default), `bottom`\n\t- `opt.minwidth {Number}`\n\t- `opt.maxwidth {Number}`\n\t- `opt.mouseleave {String}` a link to the method, it's executed when the mouse cursor leaves the box `function(e, close)}`\n\t- `opt.autofocus {Boolean}` tries to focus first input/textarea\n\t- `opt.hide {String}` a link to the method `function(box_element)`\n\t- `opt.show {String}` a link to the method `function(box_element)`\n\t- `opt.autohide {Boolean}` enables hiding a box after clicking on it\n\t- `opt.delay {Number}` a simple delay for rendering (default: `0`)\n\t- `opt.scrolltop {Boolean}` it deducts scroll top position from the current offset Y (default: `false`)\n\t- `opt.url {String}` optional, URL address for the content\n\t\t- `opt.path {String}` will replace all `~PATH~` phrases in the downloaded template\n\t\t- `opt.ID {String}` will replace all `~ID~` phrases in the downloaded template\n- `component.hide()` hides the last open box\n- `component.hide([box_id])` hides the specific box and its all nested boxes\n- `component.hide(true)` hides all boxes\n\n### Boxes\n\n```html\n<div class=\"floatingbox\" data-id=\"yourid\" data-config=\"\">\n\tYour content\n</div>\n```\n\n__Configuration__:\n\n- `init {String}` a link to the method, it's executed when the box is initialized the first time `function(element)`\n- `reload {String}` a link to the method `function(element)`\n- `hide {String}` a link to the method, it's executed when the box is going to hide `function(element)`\n- `scope {String}` a custom scope/plugin name\n- `zindex {Number}` a custom z-index for this box\n\nUse it with `<ui-component name=\"floatingbox\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"exec\"></ui-component>\n<ui-component name=\"floatingbox\" config=\"zindex:10\"></ui-component>\n\n<!-- Form templates -->\n\n<div class=\"floatingbox\" data-id=\"1\" style=\"width:200px\">\n\t<div class=\"padding\">\n\t\t<div>Box 1</div>\n\t\t<br />\n\t\t<br />\n\t\t<br />\n\t</div>\n</div>\n\n<div class=\"floatingbox\" data-id=\"2\" data-config=\"init:box2init\">\n\t<div class=\"padding\">\n\t\t<div>Box 2</div>\n\t\t<br />\n\t\t<br />\n\t\t<br />\n\t\t<span class=\"exec\" data-exec=\"showbox\" data-id=\"3\" style=\"color:#2A6CEC;cursor:pointer;text-decoration:underline\">Show box 3</span>\n\t</div>\n</div>\n\n<div class=\"floatingbox\" data-id=\"3\">\n\t<div class=\"padding\">\n\t\tBox 3\n\t</div>\n</div>\n\n<!-- Executors -->\n\n<div class=\"row\">\n\t<div class=\"col-xs-4\">\n\t\t<span class=\"exec\" data-exec=\"showbox\" data-id=\"1\" style=\"color:#2A6CEC;cursor:pointer;text-decoration:underline\" class=\"block\">Show box 1</span>\n\t</div>\n\t<div class=\"col-xs-4\">\n\t\t<span class=\"exec\" data-exec=\"showbox\" data-id=\"2\" style=\"color:#2A6CEC;cursor:pointer;text-decoration:underline\" class=\"block\">Show box 2</span>\n\t</div>\n\t<div class=\"col-xs-4\">\n\t\t<span class=\"exec\" data-exec=\"showbox\" data-id=\"3\" style=\"color:#2A6CEC;cursor:pointer;text-decoration:underline\" class=\"block\">Show box 3</span>\n\t</div>\n</div>\n\n<script>\n\n\tfunction showbox(el) {\n\t\tvar opt = {};\n\t\topt.element = el;\n\t\topt.id = el.attrd('id');\n\t\tSETTER('floatingbox/show', opt);\n\t}\n\n\tfunction box2init(el) {\n\t\tconsole.log('Box 2 is initialized', el);\n\t}\n\n</script>\n```"}],"knowledge":{"content_sha256":"95f26345022d440ded35794e13b782909a3a37f0abcfbd6a19f2b6b970106daf","source_sha256":"71c2c13dcc3db760948df8985d263199c4c3d062054560f684a3dc130f99bf94","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","HTML","MIT","PATH","SETTER","URL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"exec\"></ui-component>\n<ui-component name=\"floatingbox\" config=\"zindex:10\"></ui-component>\n\n<!-- Form templates -->\n\n<div class=\"floatingbox\" data-id=\"1\" style=\"width:200px\">\n\t<div class=\"padding\">\n\t\t<div>Box 1</div>\n\t\t<br />\n\t\t<br />\n\t\t<br />\n\t</div>\n</div>\n\n<div class=\"floatingbox\" data-id=\"2\" data-config=\"init:box2init\">\n\t<div class=\"padding\">\n\t\t<div>Box 2</div>\n\t\t<br />\n\t\t<br />\n\t\t<br />\n\t\t<span class=\"exec\" data-exec=\"showbox\" data-id=\"3\" style=\"color:#2A6CEC;cursor:pointer;text-decoration:underline\">Show box 3</span>\n\t</div>\n</div>\n\n<div class=\"floatingbox\" data-id=\"3\">\n\t<div class=\"padding\">\n\t\tBox 3\n\t</div>\n</div>\n\n<!-- Executors -->\n\n<div class=\"row\">\n\t<div class=\"col-xs-4\">\n\t\t<span class=\"exec\" data-exec=\"showbox\" data-id=\"1\" style=\"color:#2A6CEC;cursor:pointer;text-decoration:underline\" class=\"block\">Show box 1</span>\n\t</div>\n\t<div class=\"col-xs-4\">\n\t\t<span class=\"exec\" data-exec=\"showbox\" data-id=\"2\" style=\"color:#2A6CEC;cursor:pointer;text-decoration:underline\" class=\"block\">Show box 2</span>\n\t</div>\n\t<div class=\"col-xs-4\">\n\t\t<span class=\"exec\" data-exec=\"showbox\" data-id=\"3\" style=\"color:#2A6CEC;cursor:pointer;text-decoration:underline\" class=\"block\">Show box 3</span>\n\t</div>\n</div>\n\n<script>\n\n\tfunction showbox(el) {\n\t\tvar opt = {};\n\t\topt.element = el;\n\t\topt.id = el.attrd('id');\n\t\tSETTER('floatingbox/show', opt);\n\t}\n\n\tfunction box2init(el) {\n\t\tconsole.log('Box 2 is initialized', el);\n\t}\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/floatinginput","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/floatinginput/","markdown_url":"https://forge.totaljs.es/en/components/floatinginput.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"f74b10761a1dd48a4d98f3441474041abe256df9fd737071b563ed716e3a8ac4","checked_at":"2026-10-20T12:21:23.046Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-FloatingInput","description":"j-FloatingInput: singleton; supports dark mode","messages":[{"role":"user","content":"j-FloatingInput\n\nj-FloatingInput: singleton; supports dark mode"},{"role":"assistant","content":"- singleton\n- supports __dark mode__\n\n__Methods__:\n\nMethod: `component.show(options)`\n\n- `options.element {Element/jQuery Element}`\n- `options.offsetX {Number}` optional, `x` offset (default: `0`)\n- `options.offsetY {Number}` optional, `y` offset (default: `0`)\n- `options.offsetWidth {Number}` optional, `width` offset (default: `0`)\n- `options.placeholder {String}` optional, a placeholder for search field\n- `options.minwidth {Number}` optional, a minimal width, default `200`\n- `options.maxwidth {Number}` optional, a maximal width, default `infinite`\n- `options.maxlength {Number}` optional, a maximal length of value, default `50`\n- `options.callback(value, element) {Function}` is triggered when the user clicks on the item\n- `options.icon {String}` optional, Font-Awesome icon\n- `options.close {Function}` optional, can determine closing of panel\n- `options.align {String}` optional, `left` (default), `right` or `center`\n- `options.value {String}` optional, a default value for the input\n- `options.position {String}` optional, `top` (default), `bottom`\n- `options.summary {String}` additional help content\n- `options.monospace {Boolean}` enables monospace font\n- `options.select {Boolean}` selects text\n- __NEW__ `options.multiline {Boolean}` enables multiline\n- __NEW__ `options.height {Number}` a height for multiline (default: `100`)\n\nMethod: `component.hide()`\n\n__Configuration__:\n\n- `placeholder` - a placeholder for the input\n\nUse it with `<ui-component name=\"floatinginput\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\tbody { background-color: #F0F0F0; }\n</style>\n\n<ui-component name=\"floatinginput\" config=\"placeholder:Type something ...\"></ui-component>\n\n<div class=\"container\">\n\t<a href=\"javascript:void(0)\">Type your name</a>\n</div>\n\n<script>\n\t$(document).on('click', 'a', function() {\n\n\t\tvar opt = {};\n\t\topt.element = this;\n\t\topt.callback = function(value) {\n\t\t\tconsole.log('CLICK', value);\n\t\t};\n\n\t\tSETTER('floatinginput/show', opt);\n\t});\n</script>\n```"}],"knowledge":{"content_sha256":"1d8849edd82125c5ed13f81986cda4fa2656e6edee9974ca9a3851ffb5d4dfe2","source_sha256":"f74b10761a1dd48a4d98f3441474041abe256df9fd737071b563ed716e3a8ac4","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","CLICK","DEF","F0F0F0","MIT","SETTER"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\tbody { background-color: #F0F0F0; }\n</style>\n\n<ui-component name=\"floatinginput\" config=\"placeholder:Type something ...\"></ui-component>\n\n<div class=\"container\">\n\t<a href=\"javascript:void(0)\">Type your name</a>\n</div>\n\n<script>\n\t$(document).on('click', 'a', function() {\n\n\t\tvar opt = {};\n\t\topt.element = this;\n\t\topt.callback = function(value) {\n\t\t\tconsole.log('CLICK', value);\n\t\t};\n\n\t\tSETTER('floatinginput/show', opt);\n\t});\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/floatingpanel","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/floatingpanel/","markdown_url":"https://forge.totaljs.es/en/components/floatingpanel.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"8226a5023b24bab2e62c6cdde648df2a477eb2c441555c8e4d62dd95abf19c81","checked_at":"2026-10-20T12:21:23.786Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-FloatingPanel","description":"You can combine methods with j-Shortcuts component.","messages":[{"role":"user","content":"j-FloatingPanel\n\nYou can combine methods with j-Shortcuts component."},{"role":"assistant","content":"- singleton\n- supports custom scrollbars\n- supports __dark mode__\n\n__Methods__:\n\nMethod: `component.show(options)`\n\n- `options.element {Element/jQuery Element}`\n- `options.offsetX {Number}` optional, `x` offset (default: `0`)\n- `options.offsetY {Number}` optional, `y` offset (default: `0`)\n- `options.offsetWidth {Number}` optional, `width` offset (default: `0`)\n- `options.minwidth {Number}` optional, a minimal width (default `200`)\n- `options.maxwidth {Number}` optional, a maximal width (default `infinite`)\n- `options.height {Number}` optional, a height (default: `200`)\n- `options.callback(element) {Function}` is triggered when the user clicks on the element with `.selectable` class\n- `options.init {Function}` is triggered before the component is displayed\n- `options.close {Function}` optional, can determine closing of panel\n- `options.align {String}` optional, `left` (default), `right` or `center`\n- `options.position {String}` optional, `top` (default), `bottom`\n- `options.classname {String}` optional, a custom container class for all items\n\nMethod: `component.hide()`\n\n__HTML rendering__:\n\n```js\nvar opt = {};\nopt.html = 'Your HTML code';\n```\n\n__RAW element rendering__:\n\n```js\nvar opt = {};\nopt.html = YOUR_DOM_ELEMENT;\n// IMPORTANT: if the FloatingBox will be closed, then the element will be moved to the previous parent.\n```\n\n__Importing content__:\n\n```js\nvar opt = {};\nopt.import = '/url/part.html';\n// IMPORTANT: the content will be cached for the future usage\n```\n\n### Good to know\n\n- elements with `selectable` class will be selectable\n\n__Methods__:\n\n- `component.up()` selects item above\n- `component.down()` selects item below\n- `component.select([el])` selects item\n\nYou can combine methods with `j-Shortcuts` component.\n\nUse it with `<ui-component name=\"floatingpanel\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"exec\"></ui-component>\n<ui-component name=\"floatingpanel\"></ui-component>\n\n<span class=\"exec\" data-exec=\"showpanel\" style=\"color:#2A6CEC;cursor:pointer;text-decoration:underline\" class=\"block\">Show panel</span>\n\n<script>\n\n\tfunction showpanel(el) {\n\t\tvar opt = {};\n\t\topt.element = el;\n\t\topt.html = '<div class=\"padding\">Custom content</div>';\n\t\tSETTER('floatingpanel/show', opt);\n\t}\n\n</script>\n```"}],"knowledge":{"content_sha256":"45ba6e92b0aa5454f04971ad8204afac4a82864bd9ebc1e20173c2fc295f873a","source_sha256":"8226a5023b24bab2e62c6cdde648df2a477eb2c441555c8e4d62dd95abf19c81","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","HTML","IMPORTANT","MIT","SETTER","YOUR_DOM_ELEMENT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"exec\"></ui-component>\n<ui-component name=\"floatingpanel\"></ui-component>\n\n<span class=\"exec\" data-exec=\"showpanel\" style=\"color:#2A6CEC;cursor:pointer;text-decoration:underline\" class=\"block\">Show panel</span>\n\n<script>\n\n\tfunction showpanel(el) {\n\t\tvar opt = {};\n\t\topt.element = el;\n\t\topt.html = '<div class=\"padding\">Custom content</div>';\n\t\tSETTER('floatingpanel/show', opt);\n\t}\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/floatingsearch","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/floatingsearch/","markdown_url":"https://forge.totaljs.es/en/components/floatingsearch.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"3ef190b431025146d878094398fbcb23e6d19408ef86ce91783a98548ada6560","checked_at":"2026-10-20T12:21:24.522Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-FloatingSearch","description":"A simple floating search input. It uses absolute position and it's targeted above of toolbars because it can overlay the toolbar.","messages":[{"role":"user","content":"j-FloatingSearch\n\nA simple floating search input. It uses absolute position and it's targeted above of toolbars because it can overlay the toolbar."},{"role":"assistant","content":"A simple floating search input. It uses absolute position and it's targeted above of toolbars because it can overlay the toolbar.\n\n\n__Configuration__\n\n- `placeholder {String}` A placeholder for input (default: empty)\n- `height {Number}` A height in pixels (default: `45`)\n\n__Methods__:\n\n- `floatingsearch/show` shows the component in the current position\n- `floatingsearch/hide` hides the component\n\nUse it with `<ui-component name=\"floatingsearch\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"position:relative\">\n\t<ui-component name=\"floatingsearch\" path=\"pathtosearch\"></ui-component>\n\t<div style=\"height:45px;background-color:#F0F0F0;line-height:45px;padding:0 12px;\">\n\t\t<div class=\"exec pull-right\" data-exec=\"showsearch\"><i class=\"ti ti-search\"></i></div>\n\t\tTOOLBAR\n\t</div>\n</div>\n<br />\n<ui-bind path=\"pathtosearch\" config=\"text b\" class=\"block\">You're searching: <b></b></ui-bind>\n\n<script>\n\tfunction showsearch() {\n\t\tSETTER('floatingsearch/show');\n\t}\n</script>\n```"}],"knowledge":{"content_sha256":"b49f0da6f8d84554d8903bb0d8a5e8af4f65ecd100d1e35956f0dc762f3a0502","source_sha256":"3ef190b431025146d878094398fbcb23e6d19408ef86ce91783a98548ada6560","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","F0F0F0","MIT","SETTER","TOOLBAR"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"position:relative\">\n\t<ui-component name=\"floatingsearch\" path=\"pathtosearch\"></ui-component>\n\t<div style=\"height:45px;background-color:#F0F0F0;line-height:45px;padding:0 12px;\">\n\t\t<div class=\"exec pull-right\" data-exec=\"showsearch\"><i class=\"ti ti-search\"></i></div>\n\t\tTOOLBAR\n\t</div>\n</div>\n<br />\n<ui-bind path=\"pathtosearch\" config=\"text b\" class=\"block\">You're searching: <b></b></ui-bind>\n\n<script>\n\tfunction showsearch() {\n\t\tSETTER('floatingsearch/show');\n\t}\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/flow","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/flow/","markdown_url":"https://forge.totaljs.es/en/components/flow.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"05572add7525717233a48f49276095c0f2e8aa4934fe6267cd6a66b7e333116d","checked_at":"2026-10-20T12:21:25.269Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Flow","description":"j-Flow: identifiers can't contain char; NEW onpause {String} a path to method fn(path, is), it's executed when the output/input is paused","messages":[{"role":"user","content":"j-Flow\n\nj-Flow: identifiers can't contain char; NEW onpause {String} a path to method fn(path, is), it's executed when the output/input is paused"},{"role":"assistant","content":"- identifiers can't contain `__` char\n\n__Configuration__:\n\n- `width {Number}` a width of Flow designer, default: `6000`\n- `height {Number}` a height of Flow designer, default: `6000`\n- `grid {Number}` a size of background grid, default: `25`\n- `paddingX {Number}` internal padding, default: `6`\n- `curvedlines {Boolean}` enables curved lines, default: `false`\n- `infopath {String}` a path to variable/method, response: `{ zoom: Number, selected: Object }`\n- `undopath {String}` a path to variable/method, response is Object array\n- `redopath {String}` a path to variable/method, response is Object array\n- `ondrop {String}` a path to method `fn(dropmeta, grid)`\n- `onmake {String}` a path to method `fn(el, com)`, it's executed when the component is creating\n- `ondone {String}` a path to method `fn(el, com)`, it's executed when the component is created\n- `onmove {String}` a path to method `fn(el, com/group, type)`, it's executed when the component/group is moving\n- `onremove {String}` a path to method `fn(el, com)`, it's executed when the component is removed\n- `onconnect {String}` a path to method `fn(meta)`, it's executed when the two components are connected\n- `ondisconnect {String}` a path to method `fn(meta)`, it's executed when the two components are disconnected\n- __NEW__ `onpause {String}` a path to method `fn(path, is)`, it's executed when the output/input is paused\n- `steplines {Boolean}` enables step lines (default: `false`)\n- `animationradius {Number}` animation radius in pixels (default: `5`)\n- `contextmenu {String}` a path to `function(e, type, component/meta)` (type can be `map`, `component`, `connection`, `group`)\n- `dblclick {String}` a path to `function(component)`\n- `outputoffsetX {Number}` a default X offset for output point (default: `10`)\n- `outputoffsetY {Number}` a default Y offset for output point (default: `12`)\n- `inputoffsetX {Number}` a default X offset for input point (default: `10`)\n- `inputoffsetY {Number}` a default Y offset for input point (default: `12`)\n- `snapping {Number}` snapping in pixels (default: `0` = disabled)\n- `multiple {Boolean}` enables multiple selecting of the component (default: `true`)\n- `history {Number}` a count of steps in the undo/redo history (default: `100`)\n- `animationlimit {Number}` a maximum count of dots for all animations (default: `100`)\n- `animationlimitconnection {Number}` a maximum count of dots per connection (default: `5`)\n- `allowpause {Boolean}` allows pausing on outputs/inputs (default: `true`)\n- `markers {Boolean}` adds arrow markers to the connection (default: `true`)\n\n__Commands__:\n\n- `flow.refresh` recalculates positions\n- `flow.selected.disconnect` disconnects selected connection\n- `flow.selected.remove` removes selected component\n- `flow.selected.clear` removes selected component or connection\n- `flow.zoom` performs zoom, argument: `type` (can be: `in`, `out`, `reset` or `percentage {Number}`), second optional argument: `percentage`\n- `flow.undo` performs undo step\n- `flow.redo` performs redo step\n- `flow.reset` resets editor\n- `flow.components.add` adds a new component, argument: `component_declaration`\n- `flow.components.find` finds a component, argument: `component_id`\n- `flow.clean` cleans data and non-exist connections\n- `flow.traffic(componentid__outputid, { count: 3, speed: 3, limit: 20, delay: 50, reverse: false })` loads animation\n\t- `reverse` animation can be enabled by adding `_` as a starting char of identifier `_componentid__outputid`\n- __NEW__ `flow.groups.find` finds a group, argument: `group_id`\n- __NEW__ `flow.check` a new faster alternative to the `flow.refresh` command\n- __NEW__ `flow.find` finds a component or group, argument: `component_id` or `group_id`\n\n__Adding a new component__:\n\n- the component automatically creates ID of component\n- `connections` is `{Object}`\n\n```js\n// Component declaration\nvar component = {\n\tx: 100,\n\ty: 50,\n\thtml: 'Component test',\n\n\toutputs: ['1 output name', '2 output name'],\n\t// or\n\t// outputs: [{ id: 'A', name: 'A output name' }, { id: 'B', name: 'B output name', color: '#FF8E37' }],\n\n\tconnections: { '0': [{ id: '0002', index: '2', disabled: false }] }, // Look to the connection object below\n\t// or\n\t// connections: { 'A': [{ id: '0002', index: 'B', disabled: false }] },\n\n\tactions: { select: true, move: true, disabled: false, remove: true, connect: true }\n};\n\n// Appends component\nCMD('flow.components.add', component);\n```\n\n```js\n// Connection object\n// connections: { '0': [CONNECTION_OBJECT1, CONNECTION_OBJECT2, CONNECTION_OBJECT3] }\n{\n\tid: 'TARGET_ID_COMPONENT',\n\tindex: '0', // An input index or index id, must be string!!!\n\tdisabled: false // Optional, it's the connection disabled? Disabled connection can't be removed\n}\n```\n\n__Flow output__:\n\n- returns `Object` which keys are `ID` of components\n\n```js\n{\n\t'F1562743923216': { x: 1, y: 1, connections: [], ... },\n\t'F1562743965303': { x: 1, y: 1, connections: [], ... },\n\n\t// Contains paused endpoints\n\tpaused: { 'input__COMPONENTID__INDEXID': 1, 'output__COMPONENTID__INDEXID': 1 },\n\n\t// Contains groups\n\tgroups: [{ id: 'GroupID', name: 'Name', x: 100, y: 100, width: 100, height: 100, background: 'rgba(0,0,0,0.1)', color: '#000', border: 'rgba(0,0,0,0.1)' }, ...];\n}\n```\n\n__Good to know__:\n\n- each component (in the Flow) contains `f-COMPONENTNAME` class\n- `selectable` class in the component element enables copying text and disables moving\n- `connections` supports `type:\"transform\"` with dashed line\n\nUse it with `<ui-component name=\"flow\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\t.toolbar { margin-bottom: 10px; }\n\t.toolbar button { min-width: 26px; height: 24px; background-color: transparent; border: 0; font-size: 12px; color: gray; }\n\t.toolbar button span { margin-left: 3px; }\n\t.toolbar button[name=\"remove\"] { color: red !important; }\n\t.toolbar button:disabled { color: #C0C0C0 !important; cursor: not-allowed; }\n\t.toolbar button:hover { color: black; }\n\t.toolbar button:first-child { border-left: 0; }\n</style>\n\n<div class=\"toolbar\">\n\t<button is=\"is-button\" path=\"floweditor.undo\" config=\"enabled:value && value.length > 0;click:floweditor_undo\" disabled=\"disabled\"><i class=\"ti ti-undo\"></i></button>\n\t<button is=\"is-button\" path=\"floweditor.redo\" config=\"enabled:value && value.length > 0;click:floweditor_redo\" disabled=\"disabled\"><i class=\"ti ti-redo\"></i></button>\n\t<button is=\"is-button\" path=\"floweditor.info.selected\" config=\"enabled;click:floweditor_remove\" name=\"remove\" disabled=\"disabled\"><i class=\"ti ti-trash\"></i></button>\n</div>\n\n<ui-component name=\"flow\" path=\"flowmodel\" config=\"width:6000;height:6000;grid:26;infopath:floweditor.info;undopath:floweditor.undo;redopath:floweditor.redo\"></ui-component>\n\n<script>\n\n\tvar flowmodel = {\n\t\tgroups: [{ id: 'G12345', name: 'My group', x: 50, y: 50, width: 300, height: 300 }],\n\t\t'F1562743923216': { x: 100, y: 150, html: 'Component 1', outputs: ['Done', 'Error'], connections: { '0': [{ id: 'F1562743965303', index: '2', disabled: false }] }},\n\t\t'F1562743965303': { x: 500, y: 250, html: 'Component 2', inputs: ['1', '2', '3'] }\n\t};\n\n\tvar floweditor = {};\n\n\tfunction floweditor_undo() {\n\t\tCMD('flow.undo');\n\t}\n\n\tfunction floweditor_redo() {\n\t\tCMD('flow.redo');\n\t}\n\n\tfunction floweditor_remove() {\n\t\tCMD('flow.selected.clear');\n\t}\n\n\t// Extend buttons for using Web components\n\tcustomElements.define('is-button', class extends HTMLButtonElement {\n\t\tconstructor() {\n\t\t\tsuper();\n\t\t\tsetTimeout(NEWUIBIND, 2, this);\n\t\t}\n\t}, { extends: 'button' });\n\n</script>\n```"}],"knowledge":{"content_sha256":"3b8273cd136e6c4c996d703a1fb9fa22830599cedd6871114d8061a17a1820cc","source_sha256":"05572add7525717233a48f49276095c0f2e8aa4934fe6267cd6a66b7e333116d","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["C0C0C0","CDN","CMD","COMPONENTNAME","CONNECTION_OBJECT1","CONNECTION_OBJECT2","CONNECTION_OBJECT3","DEF","F1562743923216","F1562743965303","FF8E37","G12345","HTMLB","MIT","NEWUIBIND","TARGET_ID_COMPONENT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\t.toolbar { margin-bottom: 10px; }\n\t.toolbar button { min-width: 26px; height: 24px; background-color: transparent; border: 0; font-size: 12px; color: gray; }\n\t.toolbar button span { margin-left: 3px; }\n\t.toolbar button[name=\"remove\"] { color: red !important; }\n\t.toolbar button:disabled { color: #C0C0C0 !important; cursor: not-allowed; }\n\t.toolbar button:hover { color: black; }\n\t.toolbar button:first-child { border-left: 0; }\n</style>\n\n<div class=\"toolbar\">\n\t<button is=\"is-button\" path=\"floweditor.undo\" config=\"enabled:value && value.length > 0;click:floweditor_undo\" disabled=\"disabled\"><i class=\"ti ti-undo\"></i></button>\n\t<button is=\"is-button\" path=\"floweditor.redo\" config=\"enabled:value && value.length > 0;click:floweditor_redo\" disabled=\"disabled\"><i class=\"ti ti-redo\"></i></button>\n\t<button is=\"is-button\" path=\"floweditor.info.selected\" config=\"enabled;click:floweditor_remove\" name=\"remove\" disabled=\"disabled\"><i class=\"ti ti-trash\"></i></button>\n</div>\n\n<ui-component name=\"flow\" path=\"flowmodel\" config=\"width:6000;height:6000;grid:26;infopath:floweditor.info;undopath:floweditor.undo;redopath:floweditor.redo\"></ui-component>\n\n<script>\n\n\tvar flowmodel = {\n\t\tgroups: [{ id: 'G12345', name: 'My group', x: 50, y: 50, width: 300, height: 300 }],\n\t\t'F1562743923216': { x: 100, y: 150, html: 'Component 1', outputs: ['Done', 'Error'], connections: { '0': [{ id: 'F1562743965303', index: '2', disabled: false }] }},\n\t\t'F1562743965303': { x: 500, y: 250, html: 'Component 2', inputs: ['1', '2', '3'] }\n\t};\n\n\tvar floweditor = {};\n\n\tfunction floweditor_undo() {\n\t\tCMD('flow.undo');\n\t}\n\n\tfunction floweditor_redo() {\n\t\tCMD('flow.redo');\n\t}\n\n\tfunction floweditor_remove() {\n\t\tCMD('flow.selected.clear');\n\t}\n\n\t// Extend buttons for using Web components\n\tcustomElements.define('is-button', class extends HTMLButtonElement {\n\t\tconstructor() {\n\t\t\tsuper();\n\t\t\tsetTimeout(NEWUIBIND, 2, this);\n\t\t}\n\t}, { extends: 'button' });\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/folder","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/folder/","markdown_url":"https://forge.totaljs.es/en/components/folder.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"2058056c88a8ab95e2d1854d743f75a71a8813a7b8fb25b33e3512632af1220b","checked_at":"2026-10-20T12:21:26.164Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Folder","description":"The component can render a content like file browser. path must be in the form e.g. Directory/Directory/File. Learn from example.","messages":[{"role":"user","content":"j-Folder\n\nThe component can render a content like file browser. path must be in the form e.g. Directory/Directory/File. Learn from example."},{"role":"assistant","content":"The component can render a content like file browser. `path` must be in the form e.g. `Directory/Directory/File`. Learn from example.\n\n\n__Configuration__:\n\n- `up {String}` a label for Up button (default: `..`)\n- `delimiter {String}` a delimiter for the path (default: `/`)\n- `root {String}` a label for first item in breadcrumb (default: `Root`)\n- `scrollbar {Boolean}` enables custom scrollbar (default: `true`)\n- `scrollbarY {Boolean}` shows Y scrollbar always (default: `false`)\n- `delimiter {String}` a delimiter for path (default: `/`)\n- `options {String}` a path to method `function(item, el)` (user must click on the right button)\n- `click {String}` a path to variable or method `function(item)` (user must click on the item)\n- `checked {String}` a path to variable or method `function(checked_items, component)` (user must check the checkbox)\n- `browse {String}` __IMPORTANT__: a path to method `function(path, next(ARRAY), [item])` which must return a content for the component\n- `parent {String}` optional, a container with fixed height, can be `window`. Default value: `component.element()`\n- `margin {Number}` optional, a top/bottom margin together (default: `0`)\n- `drop {String}` optional, a path to method which will be executed if a file will be drag&dropped\n- `key {String}` optional, a key name for creating of path (default: `name`)\n- `raw {Boolean}` optional, disables HTML encoding `name` (default: `false`)\n- __NEW__: `contextmenu {String}` optional, a path to method `function(item, el, e)` (when a user displays the context menu)\n\n```js\nfunction fn_browser(path, next, item) {\n\n\t// @path {String} paths divided according to the \"config.delimiter\"\n\t// @next {Function(Array)} importat: this function expects array with items\n\t// @item {Object} can be null\n\n\tvar arr = [];\n\n\t// Object for array:\n\t// obj.name {String} a label/name\n\t// obj.checkbox {Boolean} enables checkbox (default: false)\n\t// obj.checked {Boolean} item will be checked\n\t// obj.icon {String} a icon for item\n\t// obj.type {Number} determines a type of item (1: directory, other: file)\n\t// obj.classname {String} adds a CSS class onto \"label\" element\n\n\t// Good to know:\n\t// Only directories can be drilled\n\n\t// Example:\n\tarr.push({ name: 'Node.js', checkbox: true, type: 1, icon: 'folder' });\n\tarr.push({ name: 'Client-Side', checkbox: true, type: 1, icon: 'folder' });\n\n\tnext(arr);\n\t// next(array, noscroll);\n}\n```\n\nUse it with `<ui-component name=\"folder\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div class=\"container\">\n\t<br />\n\t<ui-component name=\"folder\" path=\"currentpath\" config=\"browse:fn_browse;checked:fn_checked;click:fn_click\" style=\"height:500px\"></ui-component>\n\t<br />\n</div>\n\n<script>\n\n\tvar currentpath = 'Frameworks/Node.js';\n\n\tfunction fn_browse(path, next, item) {\n\n\t\tvar arr = [];\n\n\t\tswitch (path) {\n\t\t\tcase 'Frameworks':\n\t\t\t\tarr.push({ name: 'Node.js', checkbox: true, type: 1, icon: 'folder' });\n\t\t\t\tarr.push({ name: 'Client-Side', checkbox: true, type: 1, icon: 'folder' });\n\t\t\t\tbreak;\n\t\t\tcase 'Frameworks/Node.js':\n\t\t\t\tarr.push({ name: 'Total.js', checkbox: true, checked: true, icon: 'file' });\n\t\t\t\tarr.push({ name: 'Express.js', checkbox: true, icon: 'file' });\n\t\t\t\tarr.push({ name: 'Koa.js', checkbox: true, icon: 'file' });\n\t\t\t\tarr.push({ name: 'Meteor.js', checkbox: true, icon: 'file' });\n\t\t\t\tbreak;\n\t\t\tcase 'Frameworks/Client-Side':\n\t\t\t\tarr.push({ name: 'React', checkbox: true, icon: 'file' });\n\t\t\t\tarr.push({ name: 'Vue', checkbox: true, icon: 'file' });\n\t\t\t\tarr.push({ name: 'jComponent', checkbox: true, checked: true, icon: 'file' });\n\t\t\t\tarr.push({ name: 'Angular.js', checkbox: true, icon: 'file' });\n\t\t\t\tarr.push({ name: 'jQuery', checkbox: true, icon: 'file' });\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tarr.push({ name: 'Frameworks', checkbox: true, type: 1, icon: 'folder' });\n\t\t\t\tarr.push({ name: 'Item 1', checkbox: true, icon: 'file' });\n\t\t\t\tarr.push({ name: 'Item 2', checkbox: true, icon: 'file' });\n\t\t\t\tarr.push({ name: 'Item 3', checkbox: true, icon: 'file' });\n\t\t\t\tbreak;\n\t\t}\n\n\t\tnext(arr);\n\t}\n\n\tfunction fn_click(item) {\n\t\tconsole.log('click', item);\n\t}\n\n\tfunction fn_checked(arr) {\n\t\tconsole.log('checked', arr);\n\t}\n\n</script>\n```"}],"knowledge":{"content_sha256":"02b73fa6ac68bd8aae79cfb69761be8b8a87264b2371371601ca590cacf6c995","source_sha256":"2058056c88a8ab95e2d1854d743f75a71a8813a7b8fb25b33e3512632af1220b","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["ARRAY","CDN","CSS","DEF","HTML","MIT","Total.js"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div class=\"container\">\n\t<br />\n\t<ui-component name=\"folder\" path=\"currentpath\" config=\"browse:fn_browse;checked:fn_checked;click:fn_click\" style=\"height:500px\"></ui-component>\n\t<br />\n</div>\n\n<script>\n\n\tvar currentpath = 'Frameworks/Node.js';\n\n\tfunction fn_browse(path, next, item) {\n\n\t\tvar arr = [];\n\n\t\tswitch (path) {\n\t\t\tcase 'Frameworks':\n\t\t\t\tarr.push({ name: 'Node.js', checkbox: true, type: 1, icon: 'folder' });\n\t\t\t\tarr.push({ name: 'Client-Side', checkbox: true, type: 1, icon: 'folder' });\n\t\t\t\tbreak;\n\t\t\tcase 'Frameworks/Node.js':\n\t\t\t\tarr.push({ name: 'Total.js', checkbox: true, checked: true, icon: 'file' });\n\t\t\t\tarr.push({ name: 'Express.js', checkbox: true, icon: 'file' });\n\t\t\t\tarr.push({ name: 'Koa.js', checkbox: true, icon: 'file' });\n\t\t\t\tarr.push({ name: 'Meteor.js', checkbox: true, icon: 'file' });\n\t\t\t\tbreak;\n\t\t\tcase 'Frameworks/Client-Side':\n\t\t\t\tarr.push({ name: 'React', checkbox: true, icon: 'file' });\n\t\t\t\tarr.push({ name: 'Vue', checkbox: true, icon: 'file' });\n\t\t\t\tarr.push({ name: 'jComponent', checkbox: true, checked: true, icon: 'file' });\n\t\t\t\tarr.push({ name: 'Angular.js', checkbox: true, icon: 'file' });\n\t\t\t\tarr.push({ name: 'jQuery', checkbox: true, icon: 'file' });\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tarr.push({ name: 'Frameworks', checkbox: true, type: 1, icon: 'folder' });\n\t\t\t\tarr.push({ name: 'Item 1', checkbox: true, icon: 'file' });\n\t\t\t\tarr.push({ name: 'Item 2', checkbox: true, icon: 'file' });\n\t\t\t\tarr.push({ name: 'Item 3', checkbox: true, icon: 'file' });\n\t\t\t\tbreak;\n\t\t}\n\n\t\tnext(arr);\n\t}\n\n\tfunction fn_click(item) {\n\t\tconsole.log('click', item);\n\t}\n\n\tfunction fn_checked(arr) {\n\t\tconsole.log('checked', arr);\n\t}\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/fontawesome","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/fontawesome/","markdown_url":"https://forge.totaljs.es/en/components/fontawesome.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"974c0d173fd57a0a3e621558c74338465b70a948fce26362e5571fc33343cf4f","checked_at":"2026-10-20T12:21:27.040Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-FontAwesome","description":"A simple FontAwesome viewer with fulltext searching. Supports a recent feature and it's responsive.","messages":[{"role":"user","content":"j-FontAwesome\n\nA simple FontAwesome viewer with fulltext searching. Supports a recent feature and it's responsive."},{"role":"assistant","content":"A simple __FontAwesome__ viewer with fulltext searching. Supports a __recent__ feature and it's responsive.\n\n__Configuration__:\n\n- `search` {String} a placeholder for the search input\n\n__Methods__:\n- `component.show(x, y, [target], [callback(icon, target)]` and it works as `toggle` when `x` and `y` are same as previous call\n- `component.hide()`\n\nUse it with `<ui-component name=\"fontawesome\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"fontawesome\" config=\"search:Search icons\"></ui-component>\n\n<script>\n\tsetTimeout(function() {\n\t\tSETTER('fontawesome', 'show', 100, 100, function(icon) {\n\t\t\tconsole.log(icon);\n\t\t});\n\t}, 500);\n</script>\n```"}],"knowledge":{"content_sha256":"f11bb7050dab9238a0d5fd19d49cab1aceb54438a4319b11e9ba97155f2d6c13","source_sha256":"974c0d173fd57a0a3e621558c74338465b70a948fce26362e5571fc33343cf4f","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","SETTER"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"fontawesome\" config=\"search:Search icons\"></ui-component>\n\n<script>\n\tsetTimeout(function() {\n\t\tSETTER('fontawesome', 'show', 100, 100, function(icon) {\n\t\t\tconsole.log(icon);\n\t\t});\n\t}, 500);\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/fontawesomebox","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/fontawesomebox/","markdown_url":"https://forge.totaljs.es/en/components/fontawesomebox.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"3dc879a49c92542b680175b1d105c98cc3bed63935e952a535a60d4c45ca6202","checked_at":"2026-10-20T12:21:27.768Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-FontAwesomeBox","description":"A simple FontAwesome viewer with fulltext searching. Optimized for jComponent +v17.","messages":[{"role":"user","content":"j-FontAwesomeBox\n\nA simple FontAwesome viewer with fulltext searching. Optimized for jComponent +v17."},{"role":"assistant","content":"A simple __FontAwesome__ viewer with fulltext searching. Optimized for jComponent `+v17`.\n\n__Configuration__:\n\n- `search` {String} a placeholder for the search input\n- `height` {Number} Optional, default: `250`\n- `exec` {String} Optional, can contain a link to the function\n\nUse it with `<ui-component name=\"fontawesomebox\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div class=\"row\">\n\t<div class=\"col-md-4 col-xs-6\">\n\t\t<ui-component name=\"fontawesomebox\" path=\"fa\" config=\"search:Search icons\"></ui-component>\n\t</div>\n</div>\n\n<script>\n\tvar fa = 'home';\n</script>\n```"}],"knowledge":{"content_sha256":"570de3e8d748ec8881920925b6b970a7fb83cf3fae55b43fdf978663e5dce434","source_sha256":"3dc879a49c92542b680175b1d105c98cc3bed63935e952a535a60d4c45ca6202","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div class=\"row\">\n\t<div class=\"col-md-4 col-xs-6\">\n\t\t<ui-component name=\"fontawesomebox\" path=\"fa\" config=\"search:Search icons\"></ui-component>\n\t</div>\n</div>\n\n<script>\n\tvar fa = 'home';\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/form","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/form/","markdown_url":"https://forge.totaljs.es/en/components/form.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"1670ac7b119d151f65847dfaaa374054779d09188dfb2626601c852da316a6ad","checked_at":"2026-10-20T12:21:28.642Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Form","description":"New version of this component supports dynamic evaluation of the content of <script type='text/html'>. The example below contains a script with HTML and the component evaluates the content if the j-Fo","messages":[{"role":"user","content":"j-Form\n\nNew version of this component supports dynamic evaluation of the content of <script type='text/html'>. The example below contains a script with HTML and the component evaluates the content if the j-Fo"},{"role":"assistant","content":"__Configuration__:\n\n- `title {String}` form title\n- `width {Number}` form max-width\n- `if {String}` condition for showing of the form, it's compared with the value within of `path`\n- `icon {String}` Font-Awesome icon without `ti-` or __new__: `ti ti-home`\n- `reload {String}` link to a global `function(form_component)` and it's executed if the form is displaying\n- `submit {String}` link to a global `function(hide)` and it's executed if the submit button is pressed\n- `cancel {String}` link to a global `function(hide)` and it's executed if the cancel button is pressed\n- `enter {Boolean}` captures `enter` key automatically and performs submit (default: `false`)\n- `center {Boolean}` centers the form to middle of screen\n- `autofocus {Boolean/String}` can focus an input. `String` === `jQuery selector` for the input\n- `default {String}` a short alias for `DEFAULT(default, true)`\n- `closebutton {Boolean}` can hide `x` button (default: `false`)\n- `zindex {Number}` can affect z-index (default: `12`)\n- `scrollbar {Boolean}` enables custom scrollbar (default: `true`)\n- `closeoutside {Boolean}` closes the form when the user clicks outside of the form (default: `false`)\n- `closeesc {Boolean}` closes the form when the user presses `ESC` key\n- `independent {Boolean}` closing doesn't depent on the condition according to the path\n- `close {String}` link to a global `function()` and it's executed if the form is closing\n- __NEW__ `hide {String}` link to a global `function()` and it's executed if the form hides\n\n__Good to know__:\n\nNew version of this component supports dynamic evaluation of the content of `<script type=\"text/html\">`. The example below contains a script with HTML and the component evaluates the content if the j-Form will be displayed (only once).\n\n```html\n<ui-component name=\"form\" path=\"path\" config=\"config\">\n\t<script type=\"text/html\">\n\t\tA CONTENT\n\t</script>\n</ui-component>\n```\n\nUse it with `<ui-component name=\"form\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"form\" path=\"common.form\" config=\"if:user;width:500;title:Title;submit:userform_submit\" class=\"hidden\">\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<nav>\n\t\t<button name=\"submit\">SUBMIT</button>\n\t\t<button name=\"cancel\">CANCEL</button>\n\t</nav>\n</ui-component>\n\n<script>\n\n\tfunction userform_submit(hide) {\n\t\talert('SUBMIT');\n\t\thide();\n\t}\n\n\tvar common = {};\n\n\t// Show the form\n\tcommon.form = 'user';\n</script>\n```"}],"knowledge":{"content_sha256":"12df3b4f6d5d5c4b1a23f77ed2deaacbc8ef563380fe11d61442107079c66da1","source_sha256":"1670ac7b119d151f65847dfaaa374054779d09188dfb2626601c852da316a6ad","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CANCEL","CDN","CONTENT","DEF","DEFAULT","ESC","HTML","MIT","SOME","SUBMIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"form\" path=\"common.form\" config=\"if:user;width:500;title:Title;submit:userform_submit\" class=\"hidden\">\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<nav>\n\t\t<button name=\"submit\">SUBMIT</button>\n\t\t<button name=\"cancel\">CANCEL</button>\n\t</nav>\n</ui-component>\n\n<script>\n\n\tfunction userform_submit(hide) {\n\t\talert('SUBMIT');\n\t\thide();\n\t}\n\n\tvar common = {};\n\n\t// Show the form\n\tcommon.form = 'user';\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/formdata","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/formdata/","markdown_url":"https://forge.totaljs.es/en/components/formdata.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"48a8862c9d42c8c38412986bc37bbd0bbd585d2424573e5413fa2f6ccc81cbc1","checked_at":"2026-10-20T12:21:35.818Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-FormData","description":"With this component you can send data via the POST/PUT/DELETE method to the current window or another.","messages":[{"role":"user","content":"j-FormData\n\nWith this component you can send data via the POST/PUT/DELETE method to the current window or another."},{"role":"assistant","content":"With this component you can send data via the POST/PUT/DELETE method to the current window or another.\n\n- singleton\n\n### Configuration\n\n- `method {String}` a default HTTP method (default: `POST`)\n- `target {String}` a default target (default: `_blank`)\n- `type {String}` a default form type (default: `application/x-www-form-urlencoded`)\n\n### Methods\n\n- `component.send(opt)`\n\t- `opt.method {String}` form method, default `config.method`\n\t- `opt.target {String}` form target, default `config.target`\n\t- `opt.type {String}` form type, default `config.target`\n\t- `opt.url {String}` __required__ URL address\n\t- `opt.data {Object}` __required__ form data in the form `{ key1:value, key2:value }`\n\nUse it with `<ui-component name=\"formdata\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"LAZY formdata\"></ui-component>\n<button>Send data</button>\n\n<script>\n\n\t$(document).on('click', 'button', function() {\n\t\tSETTER('formdata/send', { url: 'http://127.0.0.1:8000/api/', data: { name: 'Total.js Platform' }});\n\t});\n\n</script>\n```"}],"knowledge":{"content_sha256":"aa184025d0249bd48512426da243185b52d0ab034cdbd3850581203a46cc45fa","source_sha256":"48a8862c9d42c8c38412986bc37bbd0bbd585d2424573e5413fa2f6ccc81cbc1","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","DELETE","HTTP","LAZY","MIT","POST","PUT","SETTER","Total.js","URL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"LAZY formdata\"></ui-component>\n<button>Send data</button>\n\n<script>\n\n\t$(document).on('click', 'button', function() {\n\t\tSETTER('formdata/send', { url: 'http://127.0.0.1:8000/api/', data: { name: 'Total.js Platform' }});\n\t});\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/formtab","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/formtab/","markdown_url":"https://forge.totaljs.es/en/components/formtab.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"e29b6eca5803c94653eecf0bb6fbb315ed3e3371c75fbc5f198345ce4de8cfe1","checked_at":"2026-10-20T12:21:36.703Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-FormTab","description":"j-FormTab: The component moves the content of this component under <body> tag (because of positioning); The component expects array","messages":[{"role":"user","content":"j-FormTab\n\nj-FormTab: The component moves the content of this component under <body> tag (because of positioning); The component expects array"},{"role":"assistant","content":"- The component moves the content of this component under `<body>` tag (because of positioning)\n- The component expects array\n\n\n__Configuration__:\n\n- `width {Number}` width of form\n- `widthlg {Number}` width of form for `lg` devices (default: `config.width`)\n- `widthmd {Number}` width of form for `md` devices (default: `config.width`)\n- `widthsm {Number}` width of form for `sm` devices (default: `config.width`)\n- `widthxs {Number}` width of form for `xs` devices (default: `config.width`)\n- `height {Number}` height of form\n- `heightlg {Number}` height of form for `lg` devices (default: `config.height`)\n- `heightmd {Number}` height of form for `md` devices (default: `config.height`)\n- `heightsm {Number}` height of form for `sm` devices (default: `config.height`)\n- `heightxs {Number}` height of form for `xs` devices (default: `config.height`)\n- `margin {Number}` a default margin between forms (default: `10`)\n- `marginlg {Number}` a default margin for `lg` devices (default: `config.margin`)\n- `marginmd {Number}` a default margin for `md` devices (default: `config.margin`)\n- `marginsm {Number}` a default margin for `sm` devices (default: `config.margin`)\n- `marginxs {Number}` a default margin for `xs` devices (default: `config.margin`)\n- `marginfullscreen {Number}` a default margin for fullscreen mode (default: `20`)\n- `marginfullscreenlg {Number}` a default fullscreen margin for `lg` devices (default: `config.marginfullscreen`)\n- `marginfullscreenmd {Number}` a default fullscreen margin for `md` devices (default: `config.marginfullscreen`)\n- `marginfullscreensm {Number}` a default fullscreen margin for `sm` devices (default: `config.marginfullscreen`)\n- `marginfullscreenxs {Number}` a default fullscreen margin for `xs` devices (default: `config.marginfullscreen`)\n- `submit {String}` link to a global function `function(meta, data, hide)` and it's executed if the submit button is pressed\n- `cancel {String}` link to a global function and it's executed if the `cancel` button is pressed\n- `enter {Boolean}` captures `enter` key automatically and virtually clicks on the submit button (default: `false`)\n- `onclose {String}` a link to function `function(item, close)` (optional)\n- `onopen {String}` a link to function `function(item, el)` (optional)\n- `autofocus {Boolean}` enables auto-focus for the first input (default: `false`)\n- __NEW__ `right {Number}` right margin (defualt: `0`)\n- __NEW__ `bottom {Number}` bottom margin (defualt: `0`)\n- __NEW__ `useminheight {Boolean}` use `min-height` instead of `height` CSS attribute (default: `false`)\n\n__Good to know__:\n\n- each element with `cancel` class will be used as a cancel button\n\n__Data-Source__:\n\n```javascript\n[\n\t{\n\t\t// Required\n\t\tid: 'YOUR_ID_OF_THE_FORM',\n\t\tname: 'YOUR_NAME_OF_THE_FORM',\n\n\t\t// Optional\n\t\tminimized: true,\n\t\tdata: { blabla: '\"data\" will be cloned into the form scope' }\n\t\t// NEW: scope: false (disables scope)\n\t\t// NEW: scope: mycustomscopepath\n\t}\n]\n```\n\nUse it with `<ui-component name=\"formtab\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"formtab\" path=\"common.formtab\">\n\t<script type=\"text/html\">\n\t\t<div class=\"padding\">\n\t\t\tYOUR CONTENT HERE.\n\t\t</div>\n\t</script>\n</ui-component>\n\n<script>\n\tvar common = {};\n\tcommon.formtab = [];\n\tcommon.formtab.push({ id: '1', name: 'New message' });\n\tcommon.formtab.push({ id: '2', minimized: true, name: 'Message: John Deal' });\n\tcommon.formtab.push({ id: '3', minimized: true, name: 'Message: Denis Granec' });\n</script>\n```"}],"knowledge":{"content_sha256":"5cb2ded0aec6e93c38d1fc1ee76d67fbc3984ff9e6a128cbf7f5ff513dcd8d17","source_sha256":"e29b6eca5803c94653eecf0bb6fbb315ed3e3371c75fbc5f198345ce4de8cfe1","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","CONTENT","CSS","DEF","HERE","MIT","NEW","YOUR","YOUR_ID_OF_THE_FORM","YOUR_NAME_OF_THE_FORM"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"formtab\" path=\"common.formtab\">\n\t<script type=\"text/html\">\n\t\t<div class=\"padding\">\n\t\t\tYOUR CONTENT HERE.\n\t\t</div>\n\t</script>\n</ui-component>\n\n<script>\n\tvar common = {};\n\tcommon.formtab = [];\n\tcommon.formtab.push({ id: '1', name: 'New message' });\n\tcommon.formtab.push({ id: '2', minimized: true, name: 'Message: John Deal' });\n\tcommon.formtab.push({ id: '3', minimized: true, name: 'Message: Denis Granec' });\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/fullform","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/fullform/","markdown_url":"https://forge.totaljs.es/en/components/fullform.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"084727ffdf29d511575101f2c1585d750786432d5959cf779e704f37c0e98a3a","checked_at":"2026-10-20T12:21:37.585Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-FullForm","description":"The component moves the content of this component under <body> tag (because of positioning) and it supports evaluation of the content of <script type='text/html'>. The example below contains a script","messages":[{"role":"user","content":"j-FullForm\n\nThe component moves the content of this component under <body> tag (because of positioning) and it supports evaluation of the content of <script type='text/html'>. The example below contains a script"},{"role":"assistant","content":"__Configuration__:\n\n- `title {String}` form title\n- `if {String}` condition for showing of the form, it's compared with the value within of `path`\n- `icon {String}` Font-Awesome icon without `ti-` or __new__: `ti ti-home`\n- `reload {String}` link to a global `function(fullform_component)` and it's executed if the form is displaying\n- `submit {String}` link to a global `function(hide)` and it's executed if the submit button is pressed\n- `cancel {String}` link to a global `function(hide)` and it's executed if the cancel button is pressed\n- `enter {Boolean}` captures `enter` key automatically and performs submit (default: `false`)\n- `center {Boolean}` centers the form to middle of screen\n- `autofocus` {Boolean/String} can focus an input. `String` === `jQuery selector` for the input\n- `default {String}` a short alias for `DEFAULT(default, true)`\n- `closebutton {Boolean}` can hide `x` button (default: `false`)\n- `zindex {Number}` can affect z-index (default: `12`)\n- `scrollbar {Boolean}` enables custom scrollbar (default: `true`)\n- `scrolltop {Boolean}` enables scrolling to top after form is displayed (default: `true`)\n- `scrollbarshadow {Boolean}` optional, enables a shadow in scrollbar (default: `false`)\n- `style {Number}` supports two styles (1: classic (default), 2: full size without padding)\n- `closeoutside {Boolean}` closes the form when the user clicks outside of the form (default: `false`)\n- `closeesc {Boolean}` closes the form when the user presses `ESC` key\n- `independent {Boolean}` closing doesn't depent on the condition according to the path\n- `close {String}` link to a global `function()` and it's executed if the form is closing\n- `hide {String}` link to a global `function()` and it's executed if the form hides\n\n__Good to know__:\n\nThe component moves the content of this component under `<body>` tag (because of positioning) and it supports evaluation of the content of `<script type=\"text/html\">`. The example below contains a script with HTML and the component evaluates the content if the j-Form will be displayed (only once).\n\n```html\n<ui-component name=\"fullform\" path=\"path\" config=\"config\">\n\t<script type=\"text/html\">\n\t\tA CONTENT\n\t</script>\n</ui-component>\n```\n\nUse it with `<ui-component name=\"fullform\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"fullform\" path=\"common.form\" config=\"if:user;title:Title;submit:userform_submit\" class=\"hidden\">\n\t<div class=\"padding\">\n\t\tSOME CONTENT\n\t</div>\n\t<nav>\n\t\t<button name=\"submit\">SUBMIT</button>\n\t\t<button name=\"cancel\">CANCEL</button>\n\t</nav>\n</ui-component>\n\n<script>\n\n\tfunction userform_submit(hide) {\n\t\talert('SUBMIT');\n\t\thide();\n\t}\n\n\tvar common = {};\n\n\t// Show the form\n\tcommon.form = 'user';\n\n</script>\n```"}],"knowledge":{"content_sha256":"aec5b275d9054c88355fd61dbce523e28321e359d03e7eb5e962037cbe804a72","source_sha256":"084727ffdf29d511575101f2c1585d750786432d5959cf779e704f37c0e98a3a","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CANCEL","CDN","CONTENT","DEF","DEFAULT","ESC","HTML","MIT","SOME","SUBMIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"fullform\" path=\"common.form\" config=\"if:user;title:Title;submit:userform_submit\" class=\"hidden\">\n\t<div class=\"padding\">\n\t\tSOME CONTENT\n\t</div>\n\t<nav>\n\t\t<button name=\"submit\">SUBMIT</button>\n\t\t<button name=\"cancel\">CANCEL</button>\n\t</nav>\n</ui-component>\n\n<script>\n\n\tfunction userform_submit(hide) {\n\t\talert('SUBMIT');\n\t\thide();\n\t}\n\n\tvar common = {};\n\n\t// Show the form\n\tcommon.form = 'user';\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/gallery","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/gallery/","markdown_url":"https://forge.totaljs.es/en/components/gallery.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"33909b2b12f879e649bed898647c65bac95d7c60bb720759dc781b27dd694b40","checked_at":"2026-10-20T12:21:39.532Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Gallery","description":"j-Gallery: works with Bootstrap Grid System; easy usage","messages":[{"role":"user","content":"j-Gallery\n\nj-Gallery: works with Bootstrap Grid System; easy usage"},{"role":"assistant","content":"- works with Bootstrap Grid System\nUse it with `<ui-component name=\"gallery\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\tbody { background-color: #F0F0F0; }\n</style>\n\n<div>Images are from <a href=\"https://pixabay.com\" target=\"_blank\">https://pixabay.com</a></div>\n<br />\n\n<ui-component name=\"gallery\" class=\"row\"> \n\t<div class=\"col-xs-4\">\n\t\t<img src=\"https://cdn.pixabay.com/photo/2018/07/24/20/18/noodles-3559956__480.jpg\" data-src=\"https://cdn.pixabay.com/photo/2018/07/24/20/18/noodles-3559956_1280.jpg\" border=\"0\" class=\"img-responsive\" />\n\t</div>\n\t<div class=\"col-xs-4\">\n\t\t<img src=\"https://cdn.pixabay.com/photo/2018/07/22/13/12/sunflower-3554567__480.jpg\" data-src=\"https://cdn.pixabay.com/photo/2018/07/22/13/12/sunflower-3554567_1280.jpg\" border=\"0\" class=\"img-responsive\" />\n\t</div>\n\t<div class=\"col-xs-4\">\n\t\t<img src=\"https://cdn.pixabay.com/photo/2018/07/06/23/31/bike-3521373__480.jpg\" data-src=\"https://cdn.pixabay.com/photo/2018/07/06/23/31/bike-3521373_1280.jpg\" border=\"0\" class=\"img-responsive\" />\n\t</div>\n</ui-component>\n<br />\n<ui-component name=\"gallery\" class=\"row\">\n\t<div class=\"col-xs-4\">\n\t\t<img src=\"https://cdn.pixabay.com/photo/2018/07/26/05/52/sheep-3562868__480.jpg\" data-src=\"https://cdn.pixabay.com/photo/2018/07/26/05/52/sheep-3562868_1280.jpg\" border=\"0\" class=\"img-responsive\" />\n\t</div>\n\t<div class=\"col-xs-4\">\n\t\t<img src=\"https://cdn.pixabay.com/photo/2018/06/28/15/23/soft-fruits-3504149__480.jpg\" data-src=\"https://cdn.pixabay.com/photo/2018/06/28/15/23/soft-fruits-3504149_1280.jpg\" border=\"0\" class=\"img-responsive\" />\n\t</div>\n\t<div class=\"col-xs-4\">\n\t\t<img src=\"https://cdn.pixabay.com/photo/2018/07/06/08/49/tomato-3520004__480.jpg\" data-src=\"https://cdn.pixabay.com/photo/2018/07/06/08/49/tomato-3520004_1280.jpg\" border=\"0\" class=\"img-responsive\" />\n\t</div>\n</ui-component>\n```"}],"knowledge":{"content_sha256":"d4aea4dbf1b5d6b7f80e179424d91f0132af46f3460cb9443efdab8c7cfdbb87","source_sha256":"33909b2b12f879e649bed898647c65bac95d7c60bb720759dc781b27dd694b40","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","F0F0F0","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\tbody { background-color: #F0F0F0; }\n</style>\n\n<div>Images are from <a href=\"https://pixabay.com\" target=\"_blank\">https://pixabay.com</a></div>\n<br />\n\n<ui-component name=\"gallery\" class=\"row\"> \n\t<div class=\"col-xs-4\">\n\t\t<img src=\"https://cdn.pixabay.com/photo/2018/07/24/20/18/noodles-3559956__480.jpg\" data-src=\"https://cdn.pixabay.com/photo/2018/07/24/20/18/noodles-3559956_1280.jpg\" border=\"0\" class=\"img-responsive\" />\n\t</div>\n\t<div class=\"col-xs-4\">\n\t\t<img src=\"https://cdn.pixabay.com/photo/2018/07/22/13/12/sunflower-3554567__480.jpg\" data-src=\"https://cdn.pixabay.com/photo/2018/07/22/13/12/sunflower-3554567_1280.jpg\" border=\"0\" class=\"img-responsive\" />\n\t</div>\n\t<div class=\"col-xs-4\">\n\t\t<img src=\"https://cdn.pixabay.com/photo/2018/07/06/23/31/bike-3521373__480.jpg\" data-src=\"https://cdn.pixabay.com/photo/2018/07/06/23/31/bike-3521373_1280.jpg\" border=\"0\" class=\"img-responsive\" />\n\t</div>\n</ui-component>\n<br />\n<ui-component name=\"gallery\" class=\"row\">\n\t<div class=\"col-xs-4\">\n\t\t<img src=\"https://cdn.pixabay.com/photo/2018/07/26/05/52/sheep-3562868__480.jpg\" data-src=\"https://cdn.pixabay.com/photo/2018/07/26/05/52/sheep-3562868_1280.jpg\" border=\"0\" class=\"img-responsive\" />\n\t</div>\n\t<div class=\"col-xs-4\">\n\t\t<img src=\"https://cdn.pixabay.com/photo/2018/06/28/15/23/soft-fruits-3504149__480.jpg\" data-src=\"https://cdn.pixabay.com/photo/2018/06/28/15/23/soft-fruits-3504149_1280.jpg\" border=\"0\" class=\"img-responsive\" />\n\t</div>\n\t<div class=\"col-xs-4\">\n\t\t<img src=\"https://cdn.pixabay.com/photo/2018/07/06/08/49/tomato-3520004__480.jpg\" data-src=\"https://cdn.pixabay.com/photo/2018/07/06/08/49/tomato-3520004_1280.jpg\" border=\"0\" class=\"img-responsive\" />\n\t</div>\n</ui-component>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/gauge","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/gauge/","markdown_url":"https://forge.totaljs.es/en/components/gauge.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"bdd76fbaf11ace4bf584aa32a1043ca818967d5a4142a72d5b3255a4eea1d96d","checked_at":"2026-10-20T12:21:40.411Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Gauge","description":"j-Gauge options: decimals, format, colors, text, stroke","messages":[{"role":"user","content":"j-Gauge\n\nj-Gauge options: decimals, format, colors, text, stroke"},{"role":"assistant","content":"__Configuration__:\n\n- `decimals {Number}` a count of decimals (default: `1`)\n- `format {String}` a format for text (default: `{0}%`)\n- `colors {String}` colors in the form `PERCENTAGE #COLOR STROKEWIDTH` (default: `30 #8CC152 25,40 #EDBC5A 25,30 #DB3737 25`)\n- `text {Boolean}` enables text (default: `true`)\n- `stroke {Number}` a default stroke width (default: `25`)\n\nUse it with `<ui-component name=\"gauge\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"width:200px\">\n\t<ui-component name=\"gauge\" path=\"cpu\"></ui-component>\n</div>\n\n<script>\n\tsetInterval(function() {\n\t\tSET('cpu', (Math.random() * 100));\n\t}, 1500);\n</script>\n```"}],"knowledge":{"content_sha256":"665fdef3bf4a4e9f4c6f5987a29a9ff78aa09f0d216765f160673db872c5d689","source_sha256":"bdd76fbaf11ace4bf584aa32a1043ca818967d5a4142a72d5b3255a4eea1d96d","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","COLOR","DB3737","DEF","EDBC5A","MIT","PERCENTAGE","SET","STROKEWIDTH"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"width:200px\">\n\t<ui-component name=\"gauge\" path=\"cpu\"></ui-component>\n</div>\n\n<script>\n\tsetInterval(function() {\n\t\tSET('cpu', (Math.random() * 100));\n\t}, 1500);\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/gps","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/gps/","markdown_url":"https://forge.totaljs.es/en/components/gps.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"d70e6767a8d0cf4158c7f85a43ba54c2bedf8202b31ec09a881964581eb27478","checked_at":"2026-10-20T12:21:41.145Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-GPS","description":"The component tries to read GPS coordinates.","messages":[{"role":"user","content":"j-GPS\n\nThe component tries to read GPS coordinates."},{"role":"assistant","content":"- singleton\n- readonly\n\nThe component tries to read GPS coordinates.\n\n__Configuration__:\n\n- `watcher {Boolean}` enables a real-time watcher for GPS (default: `true`)\n\n__Good to know__:\n\nThe component emits `ON('gps', function(coords) {` event with a new GPS coordinates.\n\nUse it with `<ui-component name=\"gps\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"gps\" path=\"mygps\"></ui-component>\n<pre><ui-bind path=\"mygps\" config=\"html:JSON.stringify(value, null, '  ')\"></ui-bind></pre>\n```"}],"knowledge":{"content_sha256":"a381e0d03b2f78fc3ec5a217394dbe7d72d03c683a7d8b956b4a00631874129e","source_sha256":"d70e6767a8d0cf4158c7f85a43ba54c2bedf8202b31ec09a881964581eb27478","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","GPS","JSON","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"gps\" path=\"mygps\"></ui-component>\n<pre><ui-bind path=\"mygps\" config=\"html:JSON.stringify(value, null, '  ')\"></ui-bind></pre>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/grid","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/grid/","markdown_url":"https://forge.totaljs.es/en/components/grid.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"6517bf7827ae168c4c4ca2e7c48d93de469409e302d52928c15ccf13828ca382","checked_at":"2026-10-20T12:21:41.883Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Grid","description":"Display tabular records with filtering, paging and a configurable datasource.","messages":[{"role":"user","content":"j-Grid\n\nDisplay tabular records with filtering, paging and a configurable datasource."},{"role":"assistant","content":"- needed `bootstrap` grid system\n\n__Data-source__ needs to contain:\n\n```javascript\n{\n    items: [{ name: 'Row 1' }, { name: 'Row 2' }, ...] // items\n    page: 1,    // current pages\n    pages: 30,  // count of pages\n    limit: 5,   // items limit per page\n    count: 150  // count of items in DB\n}\n```\n\n__Configuration__:\n\n- `filter` {Boolean} shows a filter (optional, default: `true`)\n- `filterlabel` {String} a default placeholder for all filters (optional)\n- `external` {Boolean} is an external data-source (dynamic data loading) (optinal, default: `false`)\n- `pluralizepages` {String} pluralization for pages (optional, default: `# pages,# page,# pages,# pages`)\n- `pluralizeitems` {String} pluralization for items (optional, default: `# items,# item,# items,# items`)\n- `exec` {String} a link to `function(type, filter, sort, page)`, it's evaluated if the grid performs some operations\n- `padding` {Number} height padding (optional, default: `0`)\n- `autosize` {Boolean} enables full-height auto-size (optional, default `true`)\n- `boolean` {String} a values for filtering of boolean values (optional, default: `true|on|yes`)\n- `pagination` {Boolean} shows pagination panel (optional, default: `false`)\n- `init` {Function} an init function for e.g. custom columns\n- `checked` {Function} if rows will contain `<input type=\"checkbox` then this function will be evaluated when the checkbox is checked\n\n__Column properties__:\n\n- `name` {String} a name of field in the object\n- `text` {String} a column label\n- `title` {String} a column tooltip (optional)\n- `size` {Number} a column width: `1` small, `2` medium, `3` large, `4` very large (optional, default `1`)\n- `size` {String} a column width in pixels, value needs to contains `px` e.g. `20px`\n- `filter` {String/Boolean} a placeholder for the filter or `boolean` can disable filter for this column (optional)\n- `format` {String/Number} for formatting values (`string` for date, `number` for numbers)\n- `background` {String} a background color (optional)\n- `align` {String} can be `center` or `right` (optional, default: `left`)\n- `template` {String} can be a Tangular template and the model is the whole object of row\n- `render` {String/Function(value, column, row)} a custom rendering, `string` is evaluated as an arrow function\n- `header` {String/Function(column)} a custom rendering of header, `string` is evaluated as an arrow function\n- `sort` {Boolean} enables sorting (optional, default: `true`)\n- `class` {String} additional classes for the column, e.g. `hidden-xs`\n- `search` {Boolean/String} `true` will filter a value according to the `template` result or `String` can be a Tangular template which will be used as a value for search\n\n__Filtering__:\n\n- `numbers`: (1) `number` searches an exact number, (2) `number / number` is an interval between numbers\n- `strings`: (1) `string` searches an exact string, (2) `string1, string2, string3` is a multiple search criterium\n- `dates`: (1) year `2017` searches all dates in this year, (2) `2017-02-12 / 2017-03-13` is an interval between dates, (3) `20.12 / 31.12` an interval between dates in the current year\n\nUse it with `<ui-component name=\"grid\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\tbody{padding:50px;margin:0;background-color:#F0F0F0;font-family:Arial;height:100%;}\n\thtml{height:100%;}\n</style>\n\n<ui-component name=\"grid\" path=\"griddata\" config=\"exec:filtering\">\n\t<script type=\"text/plain\">\n\t\t[\n\t\t\t{ name: 'name', text: 'Name', size: 2, render: 'n => n.toUpperCase()', filter: 'Search in name' },\n\t\t\t{ name: 'price', text: 'Price', size: 1, format: 2, filter: 'Search in prices', align: 'right' },\n\t\t\t{ name: 'description', text: 'Description', size: 3, filter: 'Search in description' },\n\t\t\t{ name: 'created', text: 'Created', size: 1, filter: 'Search in dates', format: 'dd.MM.yyyy', align: 'center' }\n\t\t]\n\t</script>\n</ui-component>\n\n<script>\n\tvar griddata = { page: 1, count: 18, pages: 3, limit: 6, items: [] };\n\tvar items = griddata.items;\n\n\titems.push({ name: '1 Lorem ipsum dolor.', description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consectetur, mollitia!', price: 233, created: new Date().add('-5 days') });\n\titems.push({ name: '2 Lorem ipsum dolor.', description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consectetur, mollitia!', price: 122, created: new Date().add('-1 year') });\n\titems.push({ name: '3 Lorem ipsum dolor.', description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consectetur, mollitia!', price: 119, created: new Date().add('-1 month') });\n\titems.push({ name: '4 Lorem ipsum dolor.', description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consectetur, mollitia!', price: 35.34, created: new Date().add('-14 days') });\n\titems.push({ name: '5 Lorem ipsum dolor.', description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consectetur, mollitia!', price: 439.33, created: new Date().add('-5 hours') });\n\titems.push({ name: '6 Lorem ipsum dolor.', description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consectetur, mollitia!', price: 111.12, created: new Date().add('-349 minutes') });\n\n\t// Auto-resize\n\t$(window).on('resize', function() {\n\t\tsetTimeout2('resize', function() {\n\t\t\tEMIT('resize');\n\t\t}, 100);\n\t});\n\n\tfunction filtering(type, filter, sort, page) {\n\t\tconsole.log(type, filter, sort, page);\n\t}\n\n</script>\n```"}],"knowledge":{"content_sha256":"f681777167bcda4d24aafb2c8be7e364025e40c4aaa663bba57380e1ad59e7ce","source_sha256":"6517bf7827ae168c4c4ca2e7c48d93de469409e302d52928c15ccf13828ca382","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","EMIT","F0F0F0","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\tbody{padding:50px;margin:0;background-color:#F0F0F0;font-family:Arial;height:100%;}\n\thtml{height:100%;}\n</style>\n\n<ui-component name=\"grid\" path=\"griddata\" config=\"exec:filtering\">\n\t<script type=\"text/plain\">\n\t\t[\n\t\t\t{ name: 'name', text: 'Name', size: 2, render: 'n => n.toUpperCase()', filter: 'Search in name' },\n\t\t\t{ name: 'price', text: 'Price', size: 1, format: 2, filter: 'Search in prices', align: 'right' },\n\t\t\t{ name: 'description', text: 'Description', size: 3, filter: 'Search in description' },\n\t\t\t{ name: 'created', text: 'Created', size: 1, filter: 'Search in dates', format: 'dd.MM.yyyy', align: 'center' }\n\t\t]\n\t</script>\n</ui-component>\n\n<script>\n\tvar griddata = { page: 1, count: 18, pages: 3, limit: 6, items: [] };\n\tvar items = griddata.items;\n\n\titems.push({ name: '1 Lorem ipsum dolor.', description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consectetur, mollitia!', price: 233, created: new Date().add('-5 days') });\n\titems.push({ name: '2 Lorem ipsum dolor.', description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consectetur, mollitia!', price: 122, created: new Date().add('-1 year') });\n\titems.push({ name: '3 Lorem ipsum dolor.', description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consectetur, mollitia!', price: 119, created: new Date().add('-1 month') });\n\titems.push({ name: '4 Lorem ipsum dolor.', description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consectetur, mollitia!', price: 35.34, created: new Date().add('-14 days') });\n\titems.push({ name: '5 Lorem ipsum dolor.', description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consectetur, mollitia!', price: 439.33, created: new Date().add('-5 hours') });\n\titems.push({ name: '6 Lorem ipsum dolor.', description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consectetur, mollitia!', price: 111.12, created: new Date().add('-349 minutes') });\n\n\t// Auto-resize\n\t$(window).on('resize', function() {\n\t\tsetTimeout2('resize', function() {\n\t\t\tEMIT('resize');\n\t\t}, 100);\n\t});\n\n\tfunction filtering(type, filter, sort, page) {\n\t\tconsole.log(type, filter, sort, page);\n\t}\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/hashchange","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/hashchange/","markdown_url":"https://forge.totaljs.es/en/components/hashchange.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"0d5888b7e65c94732015300e2e2c5c0abee8b795898309c1c2fc0e9dc740ed1e","checked_at":"2026-10-20T12:21:42.614Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-HashChange","description":"The component captures all changes for #hashtag in the URL address. When change, it emits event hash. The component reacts to the real changes.","messages":[{"role":"user","content":"j-HashChange\n\nThe component captures all changes for #hashtag in the URL address. When change, it emits event hash. The component reacts to the real changes."},{"role":"assistant","content":"The component captures all changes for `#hashtag` in the URL address. When change, it emits event `hash`. The component reacts to the real changes.\n\n- singleton\n\n__Configuration__:\n\n- `delay {Number}` a delay for emitting `hash` event (default: `0`)\n- __NEW__: `middleware {String}` a middleware (separated by the comma) for initial emit of the `hash` event\n\n__How to capture hash change?__\n\n```js\n// Global:\nON('hash', function(hash) {\n\t// @hash {String} - it can be \"empty\"\n});\n\n// Or in the Plugin declaration:\nexports.on('hash', function(hash) {\n\n});\n```\n\nUse it with `<ui-component name=\"hashchange\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"hashchange\"></ui-component>\n\n<ui-bind path=\"currenthash\" config=\"text\"></ui-bind>\n\n<script>\n\n\tsetInterval(() => location.hash = GUID(10), 1000);\n\n\tON('hash', function(hash) {\n\t\tSET('currenthash', hash);\n\t});\n\n</script>\n```"}],"knowledge":{"content_sha256":"c241dbb8abb70e36090cfdca69291f742110a3e26a1e1cdb5dacea4e31e35330","source_sha256":"0d5888b7e65c94732015300e2e2c5c0abee8b795898309c1c2fc0e9dc740ed1e","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","GUID","MIT","SET","URL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"hashchange\"></ui-component>\n\n<ui-bind path=\"currenthash\" config=\"text\"></ui-bind>\n\n<script>\n\n\tsetInterval(() => location.hash = GUID(10), 1000);\n\n\tON('hash', function(hash) {\n\t\tSET('currenthash', hash);\n\t});\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/highlightsyntax","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/highlightsyntax/","markdown_url":"https://forge.totaljs.es/en/components/highlightsyntax.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"64c529d1ecee377b52a25c91f8c5736781be9689a8b323a91f46a9a300d9a87b","checked_at":"2026-10-20T12:21:43.957Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-HighlightSyntax","description":"Simple syntax highlighter with help of Highlight.js.","messages":[{"role":"user","content":"j-HighlightSyntax\n\nSimple syntax highlighter with help of Highlight.js."},{"role":"assistant","content":"Simple syntax highlighter with help of __Highlight.js__.\n\n\n__Configuration__:\n\n- `type` needs to contain a content-type for highlighting\n\nUse it with `<ui-component name=\"highlightsyntax\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"highlightsyntax\" path=\"exampleHTML\" config=\"type:html\"></ui-component>\n<ui-component name=\"highlightsyntax\" path=\"exampleJS\" config=\"type:js\"></ui-component>\n<ui-component name=\"highlightsyntax\" path=\"exampleCSS\" config=\"type:css\"></ui-component>\n\n<script>\n\tvar exampleHTML = \"This is <b>HTML</b>\";\n\tvar exampleJS = \"var a = 0;\";\n\tvar exampleCSS = \"body { color: red; }\";\n</script>\n```"}],"knowledge":{"content_sha256":"6a7ebd4abf86a54e45af02a55d43b61d2c410cf42ce822d9fa6c650ddf3bab29","source_sha256":"64c529d1ecee377b52a25c91f8c5736781be9689a8b323a91f46a9a300d9a87b","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","HTML","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"highlightsyntax\" path=\"exampleHTML\" config=\"type:html\"></ui-component>\n<ui-component name=\"highlightsyntax\" path=\"exampleJS\" config=\"type:js\"></ui-component>\n<ui-component name=\"highlightsyntax\" path=\"exampleCSS\" config=\"type:css\"></ui-component>\n\n<script>\n\tvar exampleHTML = \"This is <b>HTML</b>\";\n\tvar exampleJS = \"var a = 0;\";\n\tvar exampleCSS = \"body { color: red; }\";\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/icons","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/icons/","markdown_url":"https://forge.totaljs.es/en/components/icons.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"1dc277e3e76dd6c07752fca8d2360ca49f1eaa2428a224aa1f8c06c577f87573","checked_at":"2026-10-20T12:21:44.700Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Icons","description":"j-Icons: Total.js Icons list; singleton","messages":[{"role":"user","content":"j-Icons\n\nj-Icons: Total.js Icons list; singleton"},{"role":"assistant","content":"- Total.js Icons list\n- singleton\n\n__Configuration__:\n\n- `search {String}` a placeholder for search field (default: `Search`)\n- `list {String}` URL address for obtaining list of icons (default: Total.js Icons CDN)\n- __NEW__: `custom {String}` a link to the custom icon list (it uses `tic ti-NAME` prefix)\n\n## Usage\n\n```javascript\nvar opt = {};\n\n// opt.offsetX {Number}    : adds X offset (default: 0)\n// opt.offsetY {Number}    : adds Y offset (default: 0)\n// opt.align {String}      : align `left` (default), `center` or `right`\n// opt.position {String}   : position `top` (default) or `bottom`\n// opt.empty {Boolean}     : adds empty icon (default: false)\n\nopt.element = YOUR_ELEMENT;\n// or if you want to use a fixed position:\n// opt.x {Number}          : `x` position\n// opt.y {Number}          : `y` position\n\nopt.callback = function(icon) {\n\tconsole.log(icon);\n};\n\nSETTER('icons/show', opt);\n```\n\nUse it with `<ui-component name=\"icons\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"icons\"></ui-component>\n\n<div class=\"container\">\n\t<div class=\"row\">\n\t\t<div class=\"col-xs-4 col-xs-offset-4\" style=\"text-align:center\">\n\t\t\t<a href=\"javascript:void(0)\" style=\"color:black;font-size:30px\"><i class=\"ti ti-smile\"></i></a>\n\t\t</div>\n\t</div>\n</div>\n\n<script>\n$(document).on('click', 'a', function() {\n\tvar opt = {};\n\topt.align = 'center';\n\topt.position = 'top';\n\topt.element = this;\n\topt.callback = function(selected) {\n\t\tconsole.log(selected);\n\t};\n\tSETTER('icons/show', opt);\n});\n</script>\n```"}],"knowledge":{"content_sha256":"799c6257752e70dba514dc44f3f83d92cf4430bd223473eea2569b91162bfeb0","source_sha256":"1dc277e3e76dd6c07752fca8d2360ca49f1eaa2428a224aa1f8c06c577f87573","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","NAME","SETTER","Total.js","URL","YOUR_ELEMENT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"icons\"></ui-component>\n\n<div class=\"container\">\n\t<div class=\"row\">\n\t\t<div class=\"col-xs-4 col-xs-offset-4\" style=\"text-align:center\">\n\t\t\t<a href=\"javascript:void(0)\" style=\"color:black;font-size:30px\"><i class=\"ti ti-smile\"></i></a>\n\t\t</div>\n\t</div>\n</div>\n\n<script>\n$(document).on('click', 'a', function() {\n\tvar opt = {};\n\topt.align = 'center';\n\topt.position = 'top';\n\topt.element = this;\n\topt.callback = function(selected) {\n\t\tconsole.log(selected);\n\t};\n\tSETTER('icons/show', opt);\n});\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/idletime","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/idletime/","markdown_url":"https://forge.totaljs.es/en/components/idletime.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"4e3a17321ce08254d38278aea0bd2af3b3f894a04841e7007554d87fc8cd9719","checked_at":"2026-10-20T12:21:45.601Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-IdleTime","description":"This component emits event ON('idletime', function(is) {}) when the website is in idle. It reacts on mouse, keyboard, touch events or focusing of window. And the component sets Boolean according to th","messages":[{"role":"user","content":"j-IdleTime\n\nThis component emits event ON('idletime', function(is) {}) when the website is in idle. It reacts on mouse, keyboard, touch events or focusing of window. And the component sets Boolean according to th"},{"role":"assistant","content":"This component emits event `ON('idletime', function(is) {})` when the website is in idle. It reacts on mouse, keyboard, touch events or focusing of window. And the component sets `Boolean` according to the path with the current idle state.\n\n\n__Configuration__:\n\n- `count {Number}` in seconds, default: `300` 5 minutes)\n\n__Events__:\n\n- `ON('idletime', function(is))` is emitted if the document is idle (`is=true`) / active (`is=false`)\n- `ON('reload', function() {})` is emitted if the document is transformed from invisible to visible state within `config.count` interval\n\nUse it with `<ui-component name=\"idletime\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"idletime\"></ui-component>\n\n<script>\n\tON('idletime', function(is) {\n\t\tconsole.log('Is idle?', is);\n\t});\n</script>\n```"}],"knowledge":{"content_sha256":"198c71ca25834ba41cfa6b2767e946f052b7f61e4bc0438292f8fe32388eb8a4","source_sha256":"4e3a17321ce08254d38278aea0bd2af3b3f894a04841e7007554d87fc8cd9719","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"idletime\"></ui-component>\n\n<script>\n\tON('idletime', function(is) {\n\t\tconsole.log('Is idle?', is);\n\t});\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/iframe","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/iframe/","markdown_url":"https://forge.totaljs.es/en/components/iframe.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"ded597945d93ecadd76a37468ffcfaf7cff3c65a39a84b299cb448279c3092a9","checked_at":"2026-10-20T12:21:46.491Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-iFrame","description":"The component renders another URL address (stored in the path attribute) directly in the iframe. The biggest benefit is auto-resizing of the iframe element according to the parent selector.","messages":[{"role":"user","content":"j-iFrame\n\nThe component renders another URL address (stored in the path attribute) directly in the iframe. The biggest benefit is auto-resizing of the iframe element according to the parent selector."},{"role":"assistant","content":"The component renders another URL address (stored in the `path` attribute) directly in the iframe. The biggest benefit is auto-resizing of the iframe element according to the `parent` selector.\n\n\n__Configuration__:\n\n- `parent {String}` optional, a container selector (default: `window`)\n- `scrollbar {Boolean}` optional, enables native iframe scrolling (default: `false`)\n- `margin {Number}` optional, a vertical margin (default: `0`)\n- __NEW__: `marginxs {Number}` optional, a vertical margin for extra small display\n- __NEW__: `marginsm {Number}` optional, a vertical margin for small display\n- __NEW__: `marginmd {Number}` optional, a vertical margin for medium display\n- __NEW__: `marginlg {Number}` optional, a vertical margin for large display\n- __NEW__: `message {String}` optional, a link to the `function(msg)`\n- __NEW__: `ready {String}` optional, a link to the `function(com)` when the iframe is ready\n- __NEW__: `autohide {Boolean}` optional, adds `hidden` class if the iframe is blank\n\n__Methods__:\n\n- __NEW__: `SETTER('iframe/send', { your_message_data })` for sending message to the iframe\n\nUse it with `<ui-component name=\"iframe\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"iframe\" path=\"common.url\"></ui-component>\n\n<script>\n\tvar common = { url: 'https://www.totaljs.com' };\n</script>\n```"}],"knowledge":{"content_sha256":"155a5ec5da588e902f2643a63898638460e8563abf95c81e3ccf38e65f17ff4e","source_sha256":"ded597945d93ecadd76a37468ffcfaf7cff3c65a39a84b299cb448279c3092a9","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","SETTER","URL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"iframe\" path=\"common.url\"></ui-component>\n\n<script>\n\tvar common = { url: 'https://www.totaljs.com' };\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/iframeeditable","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/iframeeditable/","markdown_url":"https://forge.totaljs.es/en/components/iframeeditable.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"0c07c213410c8603b8325f9bc11f89f75be6e98f901578641acaa7df565fe034","checked_at":"2026-10-20T12:21:47.378Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-iFrameEditable","description":"j-iFrameEditable: can edit HTML safely; height of iframe is automatically counted from the body","messages":[{"role":"user","content":"j-iFrameEditable\n\nj-iFrameEditable: can edit HTML safely; height of iframe is automatically counted from the body"},{"role":"assistant","content":"- can edit HTML safely\n- height of iframe is automatically counted from the `body`\n- __important__: content must contain `contenteditable` attribute\n\n__Configuration__:\n\n- `bind {String}` can contain `body` (default), `html` or `contenteditable`\n\n__Methods__:\n\n- `component.resize()`\n\nUse it with `<ui-component name=\"iframeeditable\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div>This component can edit HTML code safely:</div>\n<ui-component name=\"iframeeditable\" path=\"customhtml\"></ui-component>\n\n<script>\n\tSET('customhtml', '<div contenteditable=\"true\"><div><b>Total.js</b></div><br /><br /><a href=\"https://www.totaljs.com\">www.totaljs.com</a></div>');\n</script>\n```"}],"knowledge":{"content_sha256":"b620daa85581caaedf7a2bd24359836df396706c6be723812fcdfa90150137b8","source_sha256":"0c07c213410c8603b8325f9bc11f89f75be6e98f901578641acaa7df565fe034","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","HTML","MIT","SET","Total.js"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div>This component can edit HTML code safely:</div>\n<ui-component name=\"iframeeditable\" path=\"customhtml\"></ui-component>\n\n<script>\n\tSET('customhtml', '<div contenteditable=\"true\"><div><b>Total.js</b></div><br /><br /><a href=\"https://www.totaljs.com\">www.totaljs.com</a></div>');\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/iframepreview","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/iframepreview/","markdown_url":"https://forge.totaljs.es/en/components/iframepreview.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"ff09f699d24f1d5c38a3114efaf1c9ed874a5982344c3e53eb3781ac61556b32","checked_at":"2026-10-20T12:21:48.270Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-iFramePreview","description":"j-iFramePreview: easy usage for e.g. invoices, mails or some other previews; can render HTML safely","messages":[{"role":"user","content":"j-iFramePreview\n\nj-iFramePreview: easy usage for e.g. invoices, mails or some other previews; can render HTML safely"},{"role":"assistant","content":"- can render HTML safely\n- height of iframe is automatically counted from the `body`\n\n__Methods__:\n- `component.resize()`\n\nUse it with `<ui-component name=\"iframepreview\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div>This component can render HTML code safely:</div>\n<ui-component name=\"iframepreview\" path=\"customhtml\"></ui-component>\n\n<script>\n\tSET('customhtml', '<div><b>Total.js</b></div><br /><br /><a href=\"https://www.totaljs.com\">www.totaljs.com</a>');\n</script>\n```"}],"knowledge":{"content_sha256":"660c3ee1b7cef0cb562f1b5c9687a59cadfbcaf102ac35fb10b7a39eba90f632","source_sha256":"ff09f699d24f1d5c38a3114efaf1c9ed874a5982344c3e53eb3781ac61556b32","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","HTML","MIT","SET","Total.js"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div>This component can render HTML code safely:</div>\n<ui-component name=\"iframepreview\" path=\"customhtml\"></ui-component>\n\n<script>\n\tSET('customhtml', '<div><b>Total.js</b></div><br /><br /><a href=\"https://www.totaljs.com\">www.totaljs.com</a>');\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/iframeviewer","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/iframeviewer/","markdown_url":"https://forge.totaljs.es/en/components/iframeviewer.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"7b82be7bd7ab9784fe0a49ec33cb2b87e7093348a086ffe5cbedbb71bfe40291","checked_at":"2026-10-20T12:21:49.004Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-iFrameViewer","description":"This component show a layer (over the content) with a iframe.","messages":[{"role":"user","content":"j-iFrameViewer\n\nThis component show a layer (over the content) with a iframe."},{"role":"assistant","content":"This component show a layer (over the content) with a iframe.\n\n- singleton\n\n__Options__:\n\n- `name {String}` a label name to header\n- `url {String}` URL address for iframe\n- `ready {Function(iframe)}` optional, `onready` handler\n- `close {Function}` optional, `onclose` handler\n- `esc {Boolean}` can close the component with pressing of `ESC` key (defualt: `false`)\n- `width {String}` a width of iframe (default: `100%`)\n- `margin {Number}` a vertical (top/bottom) margin (default: `0`)\n- __NEW__: `html {String}` a custom HTML content for the iframe\n\nUse it with `<ui-component name=\"iframeviewer\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"iframeviewer\"></ui-component>\n\n<script>\n\tSETTER(true, 'iframeviewer/open', { url: 'https://www.totaljs.com', name: 'Total.js', esc: true });\n</script>\n```"}],"knowledge":{"content_sha256":"15a4655235b27c69c4d83e1d1afa3c91d776ea9b68087951638028c4f0ead584","source_sha256":"7b82be7bd7ab9784fe0a49ec33cb2b87e7093348a086ffe5cbedbb71bfe40291","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","ESC","HTML","MIT","SETTER","Total.js","URL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"iframeviewer\"></ui-component>\n\n<script>\n\tSETTER(true, 'iframeviewer/open', { url: 'https://www.totaljs.com', name: 'Total.js', esc: true });\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/imageuploader","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/imageuploader/","markdown_url":"https://forge.totaljs.es/en/components/imageuploader.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"82f73dea733b688f3adb6cdd99be1a03473a89f48936561ebae1a045bdd99872","checked_at":"2026-10-20T12:21:49.886Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-ImageUploader","description":"A simple singleton component for uploading of resized images. The component uploads base64 according to the schema defined in options of caller.","messages":[{"role":"user","content":"j-ImageUploader\n\nA simple singleton component for uploading of resized images. The component uploads base64 according to the schema defined in options of caller."},{"role":"assistant","content":"A simple singleton component for uploading of resized images. The component uploads base64 according to the `schema` defined in `options` of caller.\n\n\n__Methods__:\n\n- `imageuploader/upload(opt)` shows a file dialog\n\t- `opt.width {Number}`\n\t- `opt.height {Number}`\n\t- `opt.background {String}` a default background color (__NEW__: supports the `transparent` value and output will be `.png`)\n\t- `opt.url {String}` __important__ URL where will be image uploaded\n\t- `opt.multiple {Boolean}` supports multiple files (default: `false`)\n\t- `opt.schema {String}` default `{ file: base64, name: filename }`\n\t- `opt.quality {Number}` default `90`\n\t- `opt.callback(response, errors)` a callback __important__\n\t- `opt.keeporiginal {Boolean}` keeps the original image if the dimension is the same as the width/height defined in the configuration (default: `false`)\n\t- __NEW__ `opt.files` {FileList} optional (if you use e.g Drag & Drop)\n\n__Friendly components__:\nThis component works with `loading` when the file is uploading.\n\nUse it with `<ui-component name=\"imageuploader\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"imageuploader\"></ui-component>\n<div onclick=\"uploadphoto()\">Click to upload</div>\n\n<script>\n\n\tfunction uploadphoto() {\n\t\tvar opt = {};\n\t\topt.width = 200;\n\t\topt.height = 200;\n\t\topt.url = '/api/upload/base64/';\n\t\topt.background = '#FFFFFF';\n\t\topt.multiple = true;\n\t\topt.callback = function(response, err) {\n\t\t\tconsole.log(response, err);\n\t\t};\n\t\tSETTER('imageuploader/upload', opt);\n\t}\n\n</script>\n```"}],"knowledge":{"content_sha256":"b13d50455eb04111ecc37861a865794402c64a99d66c54787308acb66cb010b1","source_sha256":"82f73dea733b688f3adb6cdd99be1a03473a89f48936561ebae1a045bdd99872","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","FFFFFF","MIT","SETTER","URL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"imageuploader\"></ui-component>\n<div onclick=\"uploadphoto()\">Click to upload</div>\n\n<script>\n\n\tfunction uploadphoto() {\n\t\tvar opt = {};\n\t\topt.width = 200;\n\t\topt.height = 200;\n\t\topt.url = '/api/upload/base64/';\n\t\topt.background = '#FFFFFF';\n\t\topt.multiple = true;\n\t\topt.callback = function(response, err) {\n\t\t\tconsole.log(response, err);\n\t\t};\n\t\tSETTER('imageuploader/upload', opt);\n\t}\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/imageviewer","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/imageviewer/","markdown_url":"https://forge.totaljs.es/en/components/imageviewer.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"82f3d796efae0eac43967e5cd3cec0f57410492a1598bca0160e09a4e623a389","checked_at":"2026-10-20T12:21:50.803Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-ImageViewer","description":"j-ImageViewer: singleton; place the component under <body element","messages":[{"role":"user","content":"j-ImageViewer\n\nj-ImageViewer: singleton; place the component under <body element"},{"role":"assistant","content":"- singleton\n- place the component under `<body` element\n\n\n__Configuration__:\n\n- `container {String}` a container for group of images (default: `.img-container`)\n- `selector {String}` a image selector, (default: `.img-viewer`)\n- `loading {Boolean}` enables loading between slides (default: `true`)\n- `unknown {String}` a title for unnamed images (default: `Unknown image`)\n\n__Good to know__:\n\n- image is loaded from the `data-src=\"\"` or from the `src=\"\"` attributes\n- names are obtained from the `title=\"\"` or from the `alt=\"\"` attributes\n\nUse it with `<ui-component name=\"imageviewer\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"imageviewer\"></ui-component>\n\n<div class=\"row img-container\">\n\t<div class=\"col-xs-4\">\n\t\t<img src=\"https://cdn.pixabay.com/photo/2021/10/25/07/03/eagle-6740066__340.jpg\" data-src=\"https://cdn.pixabay.com/photo/2021/10/25/07/03/eagle-6740066_960_720.jpg\" border=\"0\" class=\"img-responsive img-viewer\" />\n\t</div>\n\t<div class=\"col-xs-4\">\n\t\t<img src=\"https://cdn.pixabay.com/photo/2021/12/15/18/18/flowers-6873165__340.jpg\" data-src=\"https://cdn.pixabay.com/photo/2021/12/15/18/18/flowers-6873165_960_720.jpg\" border=\"0\" class=\"img-responsive img-viewer\" />\n\t</div>\n\t<div class=\"col-xs-4\">\n\t\t<img src=\"https://cdn.pixabay.com/photo/2021/12/14/09/50/fatherhood-6870198__340.jpg\" data-src=\"https://cdn.pixabay.com/photo/2021/12/14/09/50/fatherhood-6870198_960_720.jpg\" border=\"0\" class=\"img-responsive img-viewer\" />\n\t</div>\n</div>\n<br />\n<div class=\"row img-container\">\n\t<div class=\"col-xs-4\">\n\t\t<img src=\"https://cdn.pixabay.com/photo/2021/11/09/18/51/llama-6782140__340.jpg\" data-src=\"https://cdn.pixabay.com/photo/2021/11/09/18/51/llama-6782140_960_720.jpg\" border=\"0\" class=\"img-responsive img-viewer\" />\n\t</div>\n\t<div class=\"col-xs-4\">\n\t\t<img src=\"https://cdn.pixabay.com/photo/2020/06/11/18/17/guinea-pig-5287747__340.jpg\" data-src=\"https://cdn.pixabay.com/photo/2020/06/11/18/17/guinea-pig-5287747_960_720.jpg\" border=\"0\" class=\"img-responsive img-viewer\" />\n\t</div>\n\t<div class=\"col-xs-4\">\n\t\t<img src=\"https://cdn.pixabay.com/photo/2021/12/05/08/54/austria-6847009__340.jpg\" data-src=\"https://cdn.pixabay.com/photo/2021/12/05/08/54/austria-6847009_960_720.jpg\" border=\"0\" class=\"img-responsive img-viewer\" />\n\t</div>\n</div>\n```"}],"knowledge":{"content_sha256":"05f29f7cb10ea3951cbe7312172770e61ea7dd73cd5cfab1fa44a7e45fc93370","source_sha256":"82f3d796efae0eac43967e5cd3cec0f57410492a1598bca0160e09a4e623a389","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"imageviewer\"></ui-component>\n\n<div class=\"row img-container\">\n\t<div class=\"col-xs-4\">\n\t\t<img src=\"https://cdn.pixabay.com/photo/2021/10/25/07/03/eagle-6740066__340.jpg\" data-src=\"https://cdn.pixabay.com/photo/2021/10/25/07/03/eagle-6740066_960_720.jpg\" border=\"0\" class=\"img-responsive img-viewer\" />\n\t</div>\n\t<div class=\"col-xs-4\">\n\t\t<img src=\"https://cdn.pixabay.com/photo/2021/12/15/18/18/flowers-6873165__340.jpg\" data-src=\"https://cdn.pixabay.com/photo/2021/12/15/18/18/flowers-6873165_960_720.jpg\" border=\"0\" class=\"img-responsive img-viewer\" />\n\t</div>\n\t<div class=\"col-xs-4\">\n\t\t<img src=\"https://cdn.pixabay.com/photo/2021/12/14/09/50/fatherhood-6870198__340.jpg\" data-src=\"https://cdn.pixabay.com/photo/2021/12/14/09/50/fatherhood-6870198_960_720.jpg\" border=\"0\" class=\"img-responsive img-viewer\" />\n\t</div>\n</div>\n<br />\n<div class=\"row img-container\">\n\t<div class=\"col-xs-4\">\n\t\t<img src=\"https://cdn.pixabay.com/photo/2021/11/09/18/51/llama-6782140__340.jpg\" data-src=\"https://cdn.pixabay.com/photo/2021/11/09/18/51/llama-6782140_960_720.jpg\" border=\"0\" class=\"img-responsive img-viewer\" />\n\t</div>\n\t<div class=\"col-xs-4\">\n\t\t<img src=\"https://cdn.pixabay.com/photo/2020/06/11/18/17/guinea-pig-5287747__340.jpg\" data-src=\"https://cdn.pixabay.com/photo/2020/06/11/18/17/guinea-pig-5287747_960_720.jpg\" border=\"0\" class=\"img-responsive img-viewer\" />\n\t</div>\n\t<div class=\"col-xs-4\">\n\t\t<img src=\"https://cdn.pixabay.com/photo/2021/12/05/08/54/austria-6847009__340.jpg\" data-src=\"https://cdn.pixabay.com/photo/2021/12/05/08/54/austria-6847009_960_720.jpg\" border=\"0\" class=\"img-responsive img-viewer\" />\n\t</div>\n</div>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/importcsv","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/importcsv/","markdown_url":"https://forge.totaljs.es/en/components/importcsv.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"e83c7816d22b39e61359de8f603603cb1bb629fd895ca2c3c11fbba15f9e42b8","checked_at":"2026-10-20T12:21:51.694Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-ImportCSV","description":"Output of this component is mapped array. The component uses j-Directory component.","messages":[{"role":"user","content":"j-ImportCSV\n\nOutput of this component is mapped array. The component uses j-Directory component."},{"role":"assistant","content":"Output of this component is mapped array. The component uses `j-Directory` component.\n\n\n__Configuration__:\n\n- __IMPORTANT__ `datasource {String}` a path to CSV data (`String` type)\n- `skipfirst {Boolean}` skips the first parsed line (default: `true`)\n- `notdefined {String}` a text for undefined columns (default: `---`)\n- `parent {String}` jQuery selector for obtaining of width/height (default: `parent`)\n- `dirsearch {String}` a placeholder for search field (default: `Search`)\n- `margin {Number}` a margin for `parent` (default: `0`)\n- `marginxs {Number}` optional, a top/bottom margin together for `xs` screen width\n- `marginsm {Number}` optional, a top/bottom margin together for `sm` screen width\n- `marginmd {Number}` optional, a top/bottom margin together for `md` screen width\n- `marginlg {Number}` optional, a top/bottom margin together for `lg` screen width\n\nUse it with `<ui-component name=\"importcsv\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"directory\"></ui-component>\n\n<div style=\"width:500px;height:300px\">\n\t<ui-component name=\"importcsv\" path=\"output\" config=\"datasource:data;keys:firstname=First name,lastname=Last name,email=Email;skipfirst:0\"></ui-component>\n\t<ui-bind path=\"output\" config=\"html b:value ? value.length : 0\" class=\"block help\">Total items for import: <b>0</b></ui-bind>\n</div>\n\n<script>\n\n\tvar data = `Peter;Širka;peter@totalavengers.com\\nTomáš,Novák,tomas@totalavengers.com\\nDenis,Granec,denis@totalavengers.com`;\n\tvar output = [];\n\n</script>\n```"}],"knowledge":{"content_sha256":"b0e0b53fc83d44d03289519644ff4c33b5bca4f1ebca24b8774b932fb97026c1","source_sha256":"e83c7816d22b39e61359de8f603603cb1bb629fd895ca2c3c11fbba15f9e42b8","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","CSV","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"directory\"></ui-component>\n\n<div style=\"width:500px;height:300px\">\n\t<ui-component name=\"importcsv\" path=\"output\" config=\"datasource:data;keys:firstname=First name,lastname=Last name,email=Email;skipfirst:0\"></ui-component>\n\t<ui-bind path=\"output\" config=\"html b:value ? value.length : 0\" class=\"block help\">Total items for import: <b>0</b></ui-bind>\n</div>\n\n<script>\n\n\tvar data = `Peter;Širka;peter@totalavengers.com\\nTomáš,Novák,tomas@totalavengers.com\\nDenis,Granec,denis@totalavengers.com`;\n\tvar output = [];\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/importer","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/importer/","markdown_url":"https://forge.totaljs.es/en/components/importer.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"ab073df1ed3a360ff232c8b6497b3a5764294b3bf5b00e14209bfa6c6a4f43ac","checked_at":"2026-10-20T12:21:52.588Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Importer","description":"j-Import is a great component for importing HTML templates, scripts or styles according to the path and condition. The component can help with SPA applications because you can easily import templates,","messages":[{"role":"user","content":"j-Importer\n\nj-Import is a great component for importing HTML templates, scripts or styles according to the path and condition. The component can help with SPA applications because you can easily import templates,"},{"role":"assistant","content":"__j-Import__ is a great component for importing HTML templates, scripts or styles according to the path and condition. The component can help with SPA applications because you can easily import templates, scripts or styles dynamically.\n\n\n__Configuration__:\n\n- `if {String}` needs to contain a value\n- `url {String}` relative/absolute URL to a subpage or subpart\n- `reload {String}` a link to method in the global scope, it's executed if the __condition__ is valid\n- `cleaner {Number}` (optional) idle time (in minutes) for running of cleaning (default: `0`)\n- `clean {String}` (optional) a link to function, it's executed before the importer is cleaned\n- `id {String}` a custom identifier for replacing `~ID~` phrases in the imported HTML (default: empty)\n- __NEW__: `singleton {Boolean}` it handles only the one instance of importer according to the `path + if + url` (default: `false`)\n- __NEW__: `parent {String}` it moves itself as a direct child of the `parent` selector\n- __NEW__: `adapt {Boolean}` if `true` it calls the `reload:?/soemthing` method for the nested plugin instead of the current plugin (default: `false`)\n\n__New update__:\n\nj-Importer can contain `<script type=\"text/html\">` with a content which will be evaluated as HTML if the condition will be valid.\n\n__Good to know 1__:\n\n- all `CLASS` and `~PATH~` phrases will be replaced by `config.path` or `config.if` automatically\n- all `~ID~` phrases will be replaced by `config.id` automatically\n- importer automatically replaces `<SCR>` elements to `<script>` elements only within `<script type=\"text/html\">` template\n\n__Good to know 2__:\n\nIf the part component isn't within scope then the value from the `config.if` will replace `?` characters in all config keys.\n\nUse it with `<ui-component name=\"importer\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div>CURRENT VALUE: <ui-bind path=\"myvalue\" config=\"html:value\"></ui-bind></div>\n<ui-component name=\"importer\" path=\"myvalue\" config=\"if:d3;url:https://d3js.org/d3.v4.min.js\"></ui-component>\n\n<script>\n\n\tvar myvalue = '';\n\n\tsetTimeout(function() {\n\t\tSET('myvalue', 'd3');\n\t}, 3000);\n\n\tsetTimeout(function() {\n\t\tSET('myvalue', null);\n\t}, 5000);\n\n\tsetTimeout(function() {\n\t\t// The component contains prevention for double import, so D3 won't be imported again.\n\t\tSET('myvalue', 'd3');\n\t}, 8000);\n\n</script>\n```"}],"knowledge":{"content_sha256":"8d223fa57188534132d8b3e14c65cff52b97ea7a47068955c24dc932146edbd3","source_sha256":"ab073df1ed3a360ff232c8b6497b3a5764294b3bf5b00e14209bfa6c6a4f43ac","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","CLASS","CURRENT","DEF","HTML","MIT","PATH","SCR","SET","SPA","URL","VALUE"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div>CURRENT VALUE: <ui-bind path=\"myvalue\" config=\"html:value\"></ui-bind></div>\n<ui-component name=\"importer\" path=\"myvalue\" config=\"if:d3;url:https://d3js.org/d3.v4.min.js\"></ui-component>\n\n<script>\n\n\tvar myvalue = '';\n\n\tsetTimeout(function() {\n\t\tSET('myvalue', 'd3');\n\t}, 3000);\n\n\tsetTimeout(function() {\n\t\tSET('myvalue', null);\n\t}, 5000);\n\n\tsetTimeout(function() {\n\t\t// The component contains prevention for double import, so D3 won't be imported again.\n\t\tSET('myvalue', 'd3');\n\t}, 8000);\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/infinitescroll","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/infinitescroll/","markdown_url":"https://forge.totaljs.es/en/components/infinitescroll.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"d9b974075cb8e1152cfa60073ab8f104a2e40b24bb236f378f4a8b668c684341","checked_at":"2026-10-20T12:21:53.475Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-InfiniteScroll","description":"j-InfiniteScroll options: parent, margin, padding, autoscroll, exec","messages":[{"role":"user","content":"j-InfiniteScroll\n\nj-InfiniteScroll options: parent, margin, padding, autoscroll, exec"},{"role":"assistant","content":"__Configuration__:\n\n- `parent {String}` a parent element due to height of container (default: `parent`)\n- `margin {Number}` a margin for `height` (default: `0` px)\n- `padding {Number}` a bottom scroll padding for performing of reading (default: `50` px)\n- `autoscroll {Number}` automatically scrolls container if the content is added (default: `100` px)\n- `exec {String}` a link to the method which is executed if the scrollbar is at the end, look into the implementation below:\n\n```javascript\nfunction method_append(next, element, init) {\n\t// @next {Function(new_content)}\n\t// @element {jQuery}\n\t// @init {Boolean} Is the component initialized?\n\n\t// element.append(...);\n\t// or\n\tnext('NEW_HTML_CONTENT_or_RAW_NODE_ELEMENT');\n}\n```\n\nUse it with `<ui-component name=\"infinitescroll\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"width:500px;height:300px;border:1px solid red\">\n\t<ui-component name=\"infinitescroll\" config=\"exec:add_new_content\">\n\t\t<div style=\"height:1000px\">Some content</div>\n\t</ui-component>\n</div>\n\n<script>\n\tfunction add_new_content(next, el, init) {\n\n\t\t// init\n\t\tif (init)\n\t\t\treturn;\n\n\t\tsetTimeout(function() {\n\t\t\t// el.append(...);\n\t\t\t// or\n\t\t\tnext('<div style=\"height:500px\">' + new Date().format('HH:mm:ss') + '</div>');\n\n\t\t\t// or if the content doesn't exist:\n\t\t\t// next();\n\t\t}, 1000);\n\t}\n</script>\n```"}],"knowledge":{"content_sha256":"dde245162a9a70250abb3c2c52168703916776ba4fd952e640463cdf64790710","source_sha256":"d9b974075cb8e1152cfa60073ab8f104a2e40b24bb236f378f4a8b668c684341","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","NEW_HTML_CONTENT_"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"width:500px;height:300px;border:1px solid red\">\n\t<ui-component name=\"infinitescroll\" config=\"exec:add_new_content\">\n\t\t<div style=\"height:1000px\">Some content</div>\n\t</ui-component>\n</div>\n\n<script>\n\tfunction add_new_content(next, el, init) {\n\n\t\t// init\n\t\tif (init)\n\t\t\treturn;\n\n\t\tsetTimeout(function() {\n\t\t\t// el.append(...);\n\t\t\t// or\n\t\t\tnext('<div style=\"height:500px\">' + new Date().format('HH:mm:ss') + '</div>');\n\n\t\t\t// or if the content doesn't exist:\n\t\t\t// next();\n\t\t}, 1000);\n\t}\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/info","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/info/","markdown_url":"https://forge.totaljs.es/en/components/info.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"a0401e5e6ab18a2801e5602139a2b21569b5f1006e99be5e766013c26c761259","checked_at":"2026-10-20T12:21:54.211Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Info","description":"j-Info: singleton; show: SETTER('info', 'show', options)","messages":[{"role":"user","content":"j-Info\n\nj-Info: singleton; show: SETTER('info', 'show', options)"},{"role":"assistant","content":"- singleton\n- show: `SETTER('info', 'show', options)`\n- hide: `SETTER('info', 'hide')`\n\n__Show options__:\n\n- `opt.element` {jQuery} an element where will be `j-Info` displayed\n- `opt.align` {String} align `left` (default), `center`, `right` or `half`\n- `opt.html` {String} a HTML content\n- `opt.name` {String} a name of Tangular template, must be defined in `j-Info` body like example below\n- `opt.value` {Object} a model for Tangular template\n- `opt.minwidth` {Number} a minimal width (default: `100`)\n- `opt.maxwidth` {Number} a maximal width (default: `280`)\n- `opt.offsetX` {Number} `x` offset (default: `0`)\n- `opt.offsetY` {Number} `y` offset (default: `0`)\n- `opt.offsetWidth` {Number} `width` offset (default: `0`)\n- `opt.callback` {Function(isvisible)} a callback is executed when the element is hidden\n- `opt.class` {String} a custom class name\n- `opt.closeclick` {Boolean} closes info when user performs mouse click (default: `false`)\n- __NEW__ `opt.position` {String} position `top` (default) or `bottom`\n- __NEW__ `opt.x` {Number} enables absolute position (`opt.y` is not required)\n- __NEW__ `opt.y` {Number} enables absolute position (`opt.x` is not required)\n\n```javascript\nvar opt = {};\nopt.element = YOUR_ELEMENT;\nopt.html = 'TEST';\nSETTER('info', 'show', opt);\n```\n\n__or with Tangular templates:__\n\n```html\n<ui-component name=\"info\">\n\n\t<script type=\"text/html\" name=\"template1\">\n\t\t{{ value.name }}\n\t</script>\n\n\t<script type=\"text/html\" name=\"template2\">\n\t\t<h2>{{ value.firstname }} {{ value.lastname }}</h2>\n\t</script>\n\n\t<script type=\"text/html\" name=\"template3\">\n\t\t{{ value.description }}\n\t</script>\n\n</ui-component>\n\n<script>\nfunction some_caller() {\n\tvar opt = {};\n\topt.element = YOUR_ELEMENT;\n\topt.name = 'template1';\n\topt.value = { name: 'Peter' };\n\tSETTER('info', 'show', opt);\n}\n</script>\n```\n\nUse it with `<ui-component name=\"info\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\tbody { background-color: #F0F0F0; }\n</style>\n\n<ui-component name=\"info\"></ui-component>\n\n<br />\n<div class=\"container\">\n\t<a href=\"javascript:void(0)\">Mouse hover here</a>\n</div>\n\n<script>\n\t$(document).on('mouseenter mouseleave', 'a', function(e) {\n\t\tif (e.type === 'mouseenter') {\n\t\t\tvar opt = {};\n\t\t\topt.element = this;\n\t\t\topt.html = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Voluptate nobis omnis quae commodi recusandae dolore saepe blanditiis, eligendi accusamus optio!';\n\t\t\tSETTER('info', 'show', opt);\n\t\t} else\n\t\t\tSETTER('info', 'hide');\n\t});\n</script>\n```"}],"knowledge":{"content_sha256":"22afaed060f24fad85fc7cf1081758b7efd87fa3b63ee12894997877febc322f","source_sha256":"a0401e5e6ab18a2801e5602139a2b21569b5f1006e99be5e766013c26c761259","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","F0F0F0","HTML","MIT","SETTER","TEST","YOUR_ELEMENT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\tbody { background-color: #F0F0F0; }\n</style>\n\n<ui-component name=\"info\"></ui-component>\n\n<br />\n<div class=\"container\">\n\t<a href=\"javascript:void(0)\">Mouse hover here</a>\n</div>\n\n<script>\n\t$(document).on('mouseenter mouseleave', 'a', function(e) {\n\t\tif (e.type === 'mouseenter') {\n\t\t\tvar opt = {};\n\t\t\topt.element = this;\n\t\t\topt.html = 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Voluptate nobis omnis quae commodi recusandae dolore saepe blanditiis, eligendi accusamus optio!';\n\t\t\tSETTER('info', 'show', opt);\n\t\t} else\n\t\t\tSETTER('info', 'hide');\n\t});\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/infowindows","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/infowindows/","markdown_url":"https://forge.totaljs.es/en/components/infowindows.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"dad2acc79829fcd50d18c9f7dfb16d0c54bf900c1058bde4d7a0e10663385376","checked_at":"2026-10-20T12:21:55.099Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-InfoWindows","description":"The InfoWindows component expects Array of objects.","messages":[{"role":"user","content":"j-InfoWindows\n\nThe InfoWindows component expects Array of objects."},{"role":"assistant","content":"- Supports mobile devices\n- Supports touches\n- Movable\n- Resizable\n\n__Configuration__:\n\n- `reoffsetresize {Boolean}` all windows will be re-offset according to `main` window dimension (default: `true`)\n\nThe `InfoWindows` component expects `Array` of objects.\n\n```javascript\n// Component declaration\nvar win = {\n\n\t// Component ID\n\tid: 'WINDOW_ID',\n\n\t// NEW: cachekey for similar windows\n\t// Optional, default: window ID\n\tcachekey: 'WINDOW_ID',\n\n\t// Positioning\n\t// You can define specific display size and if the display size is not specified then the window tries to find a size for larger display\n\thtml: 'THE CONTENT OF WINDOW', // or can be raw HTMLElement\n\n\toffset: { x: 200, y: 100, width: 600, height: 400, minwidth: 200, minheight: 200, maxwidth: 1000, maxheight: 1000 }, // minwidth, maxwidth, maxheight, minheight are optional\n\n\t// Default settings\n\tactions: { move: true, close: true, hide: true, resize: true, minimize: true, maximize: true, autosave: false },\n\t// GOOD TO KNOW:\n\t// \"undefined\" close or \"close:false\" and \"hide:true\" doesn't destroy the instance of window (it will be hidden only)\n\n\t// A window title\n\ttitle: 'Title for the window',\n\n\t// Handlers (optional):\n\tdestroy: function() {\n\t\t// @el {jQuery} a content of the window\n\t\t// @this {Instance}\n\n\t\t// is executed when the window is destroyed\n\t},\n\n\tmake: function(el) {\n\n\t\t// @el {jQuery} a content of the window\n\t\t// @this {Instance}\n\n\t\t// this.meta {Object} a reference to window object\n\t\t// this.element {jQuery} a content element of the window\n\t\t// this.container {jQuery} a main content element of the window\n\t\t// this.main {Object} a reference to \"Windows\" component\n\t\t// this.width {Number} a width of window\n\t\t// this.height {Number} a height of the content of the window\n\t\t// this.x {Number} offset X\n\t\t// this.y {Number} offset Y\n\n\t\t// Methods:\n\t\t// this.setsize(width, height);\n\t\t// this.setcommand(cmd); Allowed commands: \"close\", \"maximize\", \"resetmaximize\", \"togglemaximize\", \"minimize\", \"resetminimize\", \"toggleminimize\", \"resize\", \"move\", \"focus\"\n\t\t// this.setoffset(x, y);\n\n\t\t// is executed when the window is making\n\t},\n\n\tdata: function(type, data, el) {\n\n\t\t// @type {String}\n\t\t// @data {Object}\n\t\t// @el {jQuery} a content element of the window\n\t\t// @this {Instance}\n\n\t\t// is executed when data are sent via SETTER('windows', 'send', 'TYPE', 'DATA')\n\t},\n\n\tresize: function(width, height, el, x, y) {\n\n\t\t// @width {Number}\n\t\t// @height {Number}\n\t\t// @el {jQuery} a content of the window\n\t\t// @this {Instance}\n\n\t\t// is executed when the window is resized\n\t},\n\n\tmove: function(x, y, el) {\n\n\t\t// @x {Number}\n\t\t// @y {Number}\n\t\t// @el {jQuery} a content of the window\n\t\t// @this {Instance}\n\n\t\t// is executed when the window is moved\n\t},\n\n\tservice: function(counter, el) {\n\n\t\t// @counter {Number} count of calls\n\t\t// @el {jQuery} a content of the window\n\t\t// @this {Instance}\n\n\t\t// the component will execute this handler each 5 seconds\n\t}\n};\n\nPUSH('infowindows', win);\n```\n\n__Methods__:\n\n- `component.finditem(window_id)` finds the specific window according to `ID`\n- `component.show(window_id)` shows a window\n- `component.hide(window_id)` hides a window\n- `component.toggle(window_id)` toggles a window\n- `component.focus(window_id)` focuses a window\n- `component.close(window_id)` closes a window\n\nUse it with `<ui-component name=\"infowindows\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\tbody { padding: 0; margin: 0; overflow: hidden; }\n</style>\n\n<ui-component name=\"infowindows\" path=\"mywindows\"></ui-component>\n\n<script>\n\n\tvar mywindows = [];\n\n\tmywindows.push({\n\t\tid: 'A',\n\t\toffset: { x: 200, y: 100, width: 600, height: 400, minwidth: 200, minheight: 200 },\n\t\ttitle: 'A',\n\t\thtml: '<div class=\"padding\">Lorem ipsum dolor sit amet.</div>',\n\t\tactions: { minimize: true, maximize: true, move: true, resize: true, close: true }\n\t});\n\n\tmywindows.push({\n\t\tid: 'B',\n\t\toffset: { x: 600, y: 300, width: 600, height: 400, minwidth: 200, minheight: 200 },\n\t\ttitle: 'B',\n\t\thtml: '<div class=\"padding\">Lorem ipsum dolor sit amet.</div>',\n\t\tactions: { minimize: true, maximize: false, move: true, resize: true, close: true }\n\t});\n\n</script>\n```"}],"knowledge":{"content_sha256":"d17dfdcd1e370f4c64b859ac47cdfdb3dbe2578f2bc0c6be88ac0fee1410e886","source_sha256":"dad2acc79829fcd50d18c9f7dfb16d0c54bf900c1058bde4d7a0e10663385376","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","CONTENT","DATA","DEF","GOOD","HTMLE","KNOW","MIT","NEW","PUSH","SETTER","THE","TYPE","WINDOW","WINDOW_ID"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\tbody { padding: 0; margin: 0; overflow: hidden; }\n</style>\n\n<ui-component name=\"infowindows\" path=\"mywindows\"></ui-component>\n\n<script>\n\n\tvar mywindows = [];\n\n\tmywindows.push({\n\t\tid: 'A',\n\t\toffset: { x: 200, y: 100, width: 600, height: 400, minwidth: 200, minheight: 200 },\n\t\ttitle: 'A',\n\t\thtml: '<div class=\"padding\">Lorem ipsum dolor sit amet.</div>',\n\t\tactions: { minimize: true, maximize: true, move: true, resize: true, close: true }\n\t});\n\n\tmywindows.push({\n\t\tid: 'B',\n\t\toffset: { x: 600, y: 300, width: 600, height: 400, minwidth: 200, minheight: 200 },\n\t\ttitle: 'B',\n\t\thtml: '<div class=\"padding\">Lorem ipsum dolor sit amet.</div>',\n\t\tactions: { minimize: true, maximize: false, move: true, resize: true, close: true }\n\t});\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/inlinedatepicker","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/inlinedatepicker/","markdown_url":"https://forge.totaljs.es/en/components/inlinedatepicker.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"07a9a83cc5451492ff82c18badce36ec689a36b9b40bfc10f3824feb89be1b6c","checked_at":"2026-10-20T12:21:55.986Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-InlineDatePicker","description":"j-InlineDatePicker options: required, firstday, today, days, months, badges, append","messages":[{"role":"user","content":"j-InlineDatePicker\n\nj-InlineDatePicker options: required, firstday, today, days, months, badges, append"},{"role":"assistant","content":"__Configuration__:\n\n- `required {Boolean}` required (default: `false`)\n- `firstday {Number}` first day in the week (default: `DEF.firstdayofweek`)\n- `today {String}` a label for `Set today` button\n- `days {String}` days e.g. `SU,MO,TU,WE,TH,FR,SA` (default).\n- `months {String}` months e.g. `January,February,...` (default).\n- `badges {String}` a link to the `function(date, append)`\n\t- `append([date1, date2, date3])` (must be array of dates)\n\nUse it with `<ui-component name=\"inlinedatepicker\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"inlinedatepicker\" path=\"form.date\" config=\"today:Set today;firstday:0\"></ui-component>\n<br />\n<ui-component name=\"inlinedatepicker\" path=\"form.date\" config=\"today:Set today;firstday:0\"></ui-component>\n\n<script>\n\tvar form = {};\n\tform.date = new Date();\n</script>\n```"}],"knowledge":{"content_sha256":"7a7b71b93643565d92642bdca99aa7c34c2f6a2e0266d76b131bc2d3ef3815f8","source_sha256":"07a9a83cc5451492ff82c18badce36ec689a36b9b40bfc10f3824feb89be1b6c","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"inlinedatepicker\" path=\"form.date\" config=\"today:Set today;firstday:0\"></ui-component>\n<br />\n<ui-component name=\"inlinedatepicker\" path=\"form.date\" config=\"today:Set today;firstday:0\"></ui-component>\n\n<script>\n\tvar form = {};\n\tform.date = new Date();\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/inlineform","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/inlineform/","markdown_url":"https://forge.totaljs.es/en/components/inlineform.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"39e3bbec0b17a125368756ac3762e6c0a0ca5c506858db492584b1e1183804c6","checked_at":"2026-10-20T12:21:56.872Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-InlineForm","description":"New version of this component supports dynamic evaluation of the content of <script type='text/html'>. The example below contains a script with HTML and the component evaluates the content if the j-In","messages":[{"role":"user","content":"j-InlineForm\n\nNew version of this component supports dynamic evaluation of the content of <script type='text/html'>. The example below contains a script with HTML and the component evaluates the content if the j-In"},{"role":"assistant","content":"- Works with Bootstrap\n- The component moves the content of this component under `<body>` tag (because of positioning)\n\n__Configuration__:\n\n- `title` {String} form title\n- `width` {Number} form max-width\n- `icon` {String} Total icon without `ti-` or the whole declaration e.g. `ti ti-home`\n- `reload` {String} a link to a global function and it's executed if the form is showing\n- `submit` {String} a link to a global function and it's executed if the submit button is pressed\n- `cancel` {String} a link to a global function and it's executed if the cancel button is pressed\n- `hide` {String} a link to a global function and it's executed if the form is hiding\n- `enter` {Boolean} captures `enter` key automatically and performs submit (default: `false`)\n- `default` {String} a short alias for `DEFAULT(default, true)`\n- `autohide` {Boolean} enables auto-hide (default: `true`)\n- `autofocus` {Boolean} performs auto-focus to nearest input (default: `false`)\n\n__Methods__:\n\n- `instance.show(opt)` - shows the form\n\t- `opt.element` {jQuery element} element\n\t- `opt.align` {String} can be `left` (default), `right` or `center`\n\t- `opt.offsetX` {Number} offsetX (default: `0`)\n\t- `opt.offsetY` {Number} offsetY (default: `0`)\n\t- `opt.position` {String} can be `top` (default) or `bottom`\n\t- `opt.callback` {Function} a user defined function `callback(hide)`, `hide` attribute is `function`\n- `instance.hide()` - hides the form\n\n__Good to know__:\n\nNew version of this component supports dynamic evaluation of the content of `<script type=\"text/html\">`. The example below contains a script with HTML and the component evaluates the content if the j-InlineForm will be displayed (only once).\n\n```html\n<div data---=\"inlineform\">\n\t<script type=\"text/html\">\n\t\tA CONTENT\n\t</script>\n</div>\n```\n\nUse it with `<ui-component name=\"inlineform\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<a href=\"javascript:void(0)\" class=\"exec\">Show inline form</a>\n\n<ui-component name=\"inlineform\" config=\"width:250;title:Title;submit:userform_submit\" class=\"hidden\">\n\t<div style=\"padding:10px 15px\">SOME CONTENT</div>\n\t<nav>\n\t\t<button name=\"submit\">SUBMIT</button>\n\t\t<button name=\"cancel\">CANCEL</button>\n\t</nav>\n</ui-component>\n\n<script>\n\n\tfunction userform_submit(hide) {\n\t\talert('SUBMIT');\n\t\thide();\n\t}\n\n\t$(document).on('click', '.exec', function() {\n\t\tvar opt = {};\n\t\topt.element = $(this);\n\t\topt.align = 'left';\n\t\tSETTER('inlineform', 'show', opt);\n\t});\n</script>\n```"}],"knowledge":{"content_sha256":"684bfd52fc5849058377eb77c47c96d5f577eba8365e45917a511cc797e6bdf2","source_sha256":"39e3bbec0b17a125368756ac3762e6c0a0ca5c506858db492584b1e1183804c6","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CANCEL","CDN","CONTENT","DEF","DEFAULT","HTML","MIT","SETTER","SOME","SUBMIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<a href=\"javascript:void(0)\" class=\"exec\">Show inline form</a>\n\n<ui-component name=\"inlineform\" config=\"width:250;title:Title;submit:userform_submit\" class=\"hidden\">\n\t<div style=\"padding:10px 15px\">SOME CONTENT</div>\n\t<nav>\n\t\t<button name=\"submit\">SUBMIT</button>\n\t\t<button name=\"cancel\">CANCEL</button>\n\t</nav>\n</ui-component>\n\n<script>\n\n\tfunction userform_submit(hide) {\n\t\talert('SUBMIT');\n\t\thide();\n\t}\n\n\t$(document).on('click', '.exec', function() {\n\t\tvar opt = {};\n\t\topt.element = $(this);\n\t\topt.align = 'left';\n\t\tSETTER('inlineform', 'show', opt);\n\t});\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/inlineproperties","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/inlineproperties/","markdown_url":"https://forge.totaljs.es/en/components/inlineproperties.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"acfd00de196e51032bdef46a4eb8628ce06f7a5574c242e8f806fd0a90d4eb41","checked_at":"2026-10-20T12:21:57.610Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-InlineProperties","description":"j-InlineProperties: easy usage for different cases; singleton","messages":[{"role":"user","content":"j-InlineProperties\n\nj-InlineProperties: easy usage for different cases; singleton"},{"role":"assistant","content":"- singleton\n\n__Configuration__:\n\n- `close {String}` a label for close button (default: `Close`)\n- `dateformat {String}` a default date format (default: `[date]`)\n- `dirsearch {String}` a default placeholder for j-Directory (default: `Search`)\n- `width {Number}` a default width (default: `200`)\n\n## Usage\n\n```js\nvar opt = {};\n\n// opt.offsetX {Number}      : adds X offset (default: 0)\n// opt.offsetY {Number}      : adds Y offset (default: 0)\n// opt.align {String}        : align \"left\" (default), \"center\" or \"right\"\n// opt.position {String}     : position \"top\" (default) or \"bottom\"\n// opt.class {String}        : adds class to the parent\n\nopt.element = YOUR_ELEMENT;\n// or if you want to use a fixed position:\n// opt.x {Number}            : \"x\" position\n// opt.y {Number}            : \"y\" position\n\nopt.items = []\nopt.items.push({ id: 'firstname', name: 'First name', type: 'string', value: 'Peter' });\nopt.items.push({ id: 'lastname', name: 'Last name', type: 'string', value: 'Širka', placeholder: 'String' });\nopt.items.push({ id: 'age', name: 'Age', type: 'number', value: 33 });\n// |---- id {String}               : identifier/a property name\n// |---- name {String}             : label\n// |---- value {Object}            : a value\n// |---- type {String}             : supported types: string, number, boolean, icon, color, date, list\n// |---- items {String/Array}      : a list for \"list\" type\n\n// If you want to assign values from a model instead of filling a value in the \"items\" field, you can use:\n// opt.value {Object}\n\nopt.callback = function(model, changed) {\n\t// @model {Object}\n\t// @changed {String Array}\n\tconsole.log(model);\n};\n\nSETTER('inlineproperties/show', opt);\n```\n\nUse it with `<ui-component name=\"inlineproperties\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\tbody { background-color: #E0E0E0; }\n</style>\n\n<ui-component name=\"LAZY directory\"></ui-component>\n<ui-component name=\"LAZY colorpicker\"></ui-component>\n<ui-component name=\"LAZY icons\"></ui-component>\n<ui-component name=\"LAZY datepicker\"></ui-component>\n<ui-component name=\"inlineproperties\" config=\"dateformat:yyyy-MM-dd\"></ui-component>\n\n<div class=\"container\">\n\t<div class=\"row\">\n\t\t<div class=\"col-xs-4 col-xs-offset-4\" style=\"text-align:center\">\n\t\t\t<a href=\"javascript:void(0)\" style=\"color:black;font-size:20px\"><i class=\"ti ti-ellipsis-v\"></i></a>\n\t\t</div>\n\t</div>\n</div>\n\n<script>\n\t$(document).on('click', 'a', function() {\n\n\t\tvar cities = [{ id: 2, name: 'Banská Bystrica' }, { id: 3, name: 'Bratislava' }];\n\n\t\tvar opt = {};\n\t\topt.items = [];\n\t\topt.items.push({ id: 'firstname', name: 'First name', type: 'string', value: 'Peter' });\n\t\topt.items.push({ id: 'lastname', name: 'Last name', type: 'string', value: 'Širka' });\n\t\topt.items.push({ id: 'age', name: 'Age', type: 'number', value: 33 });\n\t\topt.items.push({ id: 'birth', name: 'Birth date', type: 'date', value: NOW });\n\t\topt.items.push({ id: 'newsletter', name: 'Newsletter', type: 'boolean', value: true });\n\t\topt.items.push({ id: 'city', name: 'City', type: 'list', value: 2, items: cities, dirsearch: 'Search city' });\n\t\topt.items.push({ id: 'color', name: 'Color', type: 'color', value: '#e73323' });\n\t\topt.items.push({ id: 'icon', name: 'Icon', type: 'icon', value: 'ti ti-home' });\n\n\t\topt.align = 'center';\n\t\topt.element = this;\n\n\t\topt.callback = function(value) {\n\t\t\tconsole.log(value);\n\t\t};\n\n\t\tSETTER('inlineproperties/show', opt);\n\t});\n</script>\n```"}],"knowledge":{"content_sha256":"156c05cc9d01d63f82a3dc1484098588646d5d2b185c9c71e1ae64ee816a53a2","source_sha256":"acfd00de196e51032bdef46a4eb8628ce06f7a5574c242e8f806fd0a90d4eb41","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","E0E0E0","LAZY","MIT","NOW","SETTER","YOUR_ELEMENT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\tbody { background-color: #E0E0E0; }\n</style>\n\n<ui-component name=\"LAZY directory\"></ui-component>\n<ui-component name=\"LAZY colorpicker\"></ui-component>\n<ui-component name=\"LAZY icons\"></ui-component>\n<ui-component name=\"LAZY datepicker\"></ui-component>\n<ui-component name=\"inlineproperties\" config=\"dateformat:yyyy-MM-dd\"></ui-component>\n\n<div class=\"container\">\n\t<div class=\"row\">\n\t\t<div class=\"col-xs-4 col-xs-offset-4\" style=\"text-align:center\">\n\t\t\t<a href=\"javascript:void(0)\" style=\"color:black;font-size:20px\"><i class=\"ti ti-ellipsis-v\"></i></a>\n\t\t</div>\n\t</div>\n</div>\n\n<script>\n\t$(document).on('click', 'a', function() {\n\n\t\tvar cities = [{ id: 2, name: 'Banská Bystrica' }, { id: 3, name: 'Bratislava' }];\n\n\t\tvar opt = {};\n\t\topt.items = [];\n\t\topt.items.push({ id: 'firstname', name: 'First name', type: 'string', value: 'Peter' });\n\t\topt.items.push({ id: 'lastname', name: 'Last name', type: 'string', value: 'Širka' });\n\t\topt.items.push({ id: 'age', name: 'Age', type: 'number', value: 33 });\n\t\topt.items.push({ id: 'birth', name: 'Birth date', type: 'date', value: NOW });\n\t\topt.items.push({ id: 'newsletter', name: 'Newsletter', type: 'boolean', value: true });\n\t\topt.items.push({ id: 'city', name: 'City', type: 'list', value: 2, items: cities, dirsearch: 'Search city' });\n\t\topt.items.push({ id: 'color', name: 'Color', type: 'color', value: '#e73323' });\n\t\topt.items.push({ id: 'icon', name: 'Icon', type: 'icon', value: 'ti ti-home' });\n\n\t\topt.align = 'center';\n\t\topt.element = this;\n\n\t\topt.callback = function(value) {\n\t\t\tconsole.log(value);\n\t\t};\n\n\t\tSETTER('inlineproperties/show', opt);\n\t});\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/input","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/input/","markdown_url":"https://forge.totaljs.es/en/components/input.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"713f9a17406ca70be7388959bece2c45ee59830773cc84f75d0acc3055c0e90f","checked_at":"2026-10-20T12:21:58.499Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Input","description":"j-Input is multifuncional input component which supports a lot of features and it's optimized for everyday usage. It's a great alternative to j-Textbox and j-Dropdown. Works only with jComponent +v17.","messages":[{"role":"user","content":"j-Input\n\nj-Input is multifuncional input component which supports a lot of features and it's optimized for everyday usage. It's a great alternative to j-Textbox and j-Dropdown. Works only with jComponent +v17."},{"role":"assistant","content":"__j-Input__ is multifuncional `input` component which supports a lot of features and it's optimized for everyday usage. It's a great alternative to `j-Textbox` and `j-Dropdown`. Works only with jComponent `+v17`.\n\n\n__Configuration__:\n\n- `type {String}` optional, values:\n\t- empty value means default `text`\n\t- `email`,\n\t- `multiline`\n\t- `checkbox`\n\t- `phone`\n\t- `password`\n\t- `zip`\n\t- `date` (needs `datepicker` component)\n\t- __NEW__: `datetime` (needs `datepicker` component)\n\t- `time` (needs `timepicker` component)\n\t- `color` (needs `colorpicker` component)\n\t- `icon` (needs `faicons` component)\n\t- `emoji` (needs `emoji` component)\n\t- `url`\n\t- `number`\n\t- `number2` (nullable)\n\t- `search`\n\t- `lower`\n\t- `upper`\n\t- `slug`\n\t- `id`\n\t- __NEW__: `radiobutton`\n\t- __NEW__: `tags`\n\t- __NEW__: `tags-lower`\n\t- __NEW__: `tags-upper`\n\t- __NEW__: `tags-id`\n\t- __NEW__: `tags-email`\n\t- __NEW__: `tags-slug`\n\t- __NEW__: `tags-phone`\n\t- __NEW__: `tags-url`\n- `required {Boolean}` optional, enables \"required\" (default: `false`)\n- `icon {String}` optional, icon for label e.g. `home`, `cog`, etc.\n- `licon {String}` optional, left icon e.g. `home`, `cog`, etc.\n- `liconclick {String}` optional, a path to method `function(component, input)`\n- `ricon {String}` optional, right icon e.g. `home`, `cog`, etc.\n- `riconclick {String}` optional, a path to method `function(component, input)`\n- `label {String}` optional, a label (default is HTML content)\n- `autofocus {Boolean}` optional, focuses the input (default: `false`)\n- `align {String}` optional, `left` (default), `2` / `right` or `1` / `center`\n- `after {String}` optional, it means a char after label (default: `:`)\n- `autofill  {Boolean/String}` optional, enables browser's autofill feature (`string` will be used as an input name, default: `false`)\n- `placeholder {String}` optional, adds a `placeholder` text into the input\n- `maxlength {Number}` optional, sets a maximum length of chars (default: `200`)\n- `minlength {Number}` optional, sets a minimum length of chars\n- `minvalue {Number}` optional, a minimal value for `number` type\n- `maxvalue {Number}` optional, a maximal value for `number` type\n- `increment {Number}` optional, sets a value for incrementing (default: `1`)\n- `validate {String}` optional, a condition for validating of value, can contain a link to `function(value)` or `!!value.match(/[a-z]+/)`\n- `format {String}` optional, output formatting e.g. for `date` type: `yyyy-MM-dd`, for `time` type: `HH:mm`, for `number` you can define max. decimals (Default: `auto`)\n- `disabled {Boolean}` optional, disables this component\n- `error {String}` optional, adds a `string` text under the component\n- `autocomplete {String}` optional, needs to contain a link to a function, is triggered on `focus` event\n- `spaces {Boolean}` optional, enables spaces otherwise it removes them (default: `true`)\n- `innerlabel {Boolean}` optional, enables inner label (default: `true`)\n- `dirsource {String}` optional, path to a data-source or __NEW__ to `function(search, next)` (path must contain `/` or __NEW__: `id|name,id|name`\n- `dircustom {String/Boolean}` optional, can contain a path to `function(val, next(new_val))` or can be `Boolean`. This option can enable adding a custom value (value not defined in data-source)\n- `dirrender {String}` optional, a path to `function(item, text)` (must return HTML for `j-Directory`), this function can affect list of items in `j-Directory`\n- `dirminwidth {Number}` optional, a minimum width for `j-Directory`, default: `200`\n- `dirmaxwidth {Number}` optional, a maximum width for `j-Directory`\n- `diroffsety {Number}` optional, `Y` offset (default: `0`)\n- `diroffsetx {Number}` optional, `X` offset (default: `0`)\n- `dirplaceholder {String}` optional, a placeholder for `j-Directory`\n- `dirempty {String}` optional, adds an empty field for `j-Directory`\n- `dirkey {String}` optional, a key name for reading of `text` in `dirsource` (default: `name`)\n- `dirvalue {String}` optional, a key name for reading of `value` in `dirsource` (default: `id`)\n- `direxclude {Boolean}` optional, excludes a current value from `j-Directory` (default: `true`)\n- `dirsearch {Boolean|String}` optional, can disable search in `j-Directory` (default: `true`) or `{String}` (key name) can map a value for searching\n- `dirraw {Boolean}` optional, can disable escaping of items for `j-Directory` (default: `false`)\n- `dirdetail {String}` optional, a link to the function `function(val, next(NEW_TEXT))` (targeted for to `dirsource`)\n- `dirfilter {String}` optional, an inline filter conditional method, for example: `value.id !== 2` (targeted for to `dirsource`)\n- __NEW__: `dirrawrender {Boolean}` optional, if the `dirsource` doesn't include value it will render a raw value (default: `true`)\n- `mask {String}` optional, can contain a mask in the form `###/##` (`#` is replaced for a char)\n- `maskregexp {String}` optional, can contain RegExp for each char in the form `\\d,\\d,\\d,null,\\d,\\d` (`,` is delimiter)\n- `masktidy {Boolean}` optional, the component returns only raw chars without fixed chars (default: `false`)\n- `autosource {String}` a path to the search source `Array` (__NEW__) or `function(search, render(arr))` for dynamic searching\n- `autovalue {String}` a property path for the value in `autosource`, default: `name`\n- `autoexec {String}` a path method `function(item, next(value_to_input))`\n- `searchalign {Number}` can align icon on `type:search` to left (`2`) or right (`1`, default)\n- `forcevalidation {Boolean}` enables for force validation for `phone` and `email` (default: `true`)\n- `camouflage {Boolean|String}` masks the value in the input after is triggered `blur` event (default: `false`) or it can be `String` which will be used as a camouflage\n- `monospace {Boolean}` enables `monospaced` font (default: `false`)\n- `multiple {Boolean}` enables checkboxes if `dirsource` is not empty\n- `tabindex {Number}` adds a tabindex (default: `0`)\n- `tabs {Boolean}` enables tabs for multiline (default: `true`)\n- `readonly {Boolean}` block text field edition (default: `false`)\n- `transform {String}` a link to the `function(value, config)` for transforming of entered values\n- `multiline {Boolean}` works only with `radiobutton` type\n- `separator {String|Boolean}` a separator for multiple rendered values\n- `realtime {Boolean}` enables real-time binding (default: `true`)\n- `exec {String}` a link to the `function(value, element)` which will be evaluated if the input is affected\n\n__Interesting:__\n\n- `type:date` needs __`datepicker`__ component\n- `type:time` needs __`timepicker`__ component\n- `dirsource:path.to.datasource` needs __`directory`__ component\n- if `licon` or `ricon` starts with `!` then the component render a raw value instead of `icon`\n- `ui-input-ok` class is binded when the input is validated and filled\n- `SET('path', 'camouflage', 'show');` shows the value for 2000 ms\n\n```video\nhttps://www.youtube.com/watch?v=Ne3ezaP0w34\n```\n\nUse it with `<ui-component name=\"input\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"LAZY directory\" config=\"placeholder:Search...\"></ui-component>\n<ui-component name=\"LAZY datepicker\"></ui-component>\n<ui-component name=\"LAZY icons\"></ui-component>\n\n<div style=\"width:400px\">\n\t<ui-component name=\"input\" path=\"form.framework\" config=\"placeholder:Choose a framework;dirsource:frameworks\" class=\"m\">Framework</ui-component>\n\t<ui-component name=\"input\" path=\"form.terms\" config=\"type:checkbox\" class=\"m\">I agree with terms</ui-component>\n\t<ui-component name=\"input\" path=\"form.notes\" config=\"type:multiline;innerlabel:1;placeholder:Enter notes\" class=\"m\">Notes</ui-component>\n\t<ui-component name=\"input\" path=\"form.frameworks\" config=\"placeholder:Select frameworks;multiple:true;dirsource:frameworks\">Frameworks</ui-component>\n\t<br />\n\t<ui-component name=\"input\" path=\"form.city\" config=\"required:1;placeholder:Where do you live?;dirsource:cities;dircustom:true\">City</ui-component>\n\t<br />\n\t<ui-component name=\"input\" path=\"cities\" config=\"type:tags;placeholder:Type a tag and press enter\">Tags</ui-component>\n\t<br />\n\t<ui-component name=\"input\" path=\"form.date\" config=\"placeholder:Enter date;type:date;required:true;innerlabel:true\">Date</ui-component>\n\t<br />\n\t<ui-component name=\"input\" path=\"form.search\" config=\"placeholder:Search...;type:search;innerlabel:true\">Search</ui-component>\n\t<br />\n\t<ui-component name=\"input\" path=\"form.password\" config=\"placeholder:Type a password...;type:password\">Password</ui-component>\n\t<br />\n\t<ui-component name=\"input\" path=\"form.zip\" config=\"mask:###-##;maskregexp:\\d,\\d,\\d,null,\\d,\\d\">ZIP</ui-component>\n\t<br />\n\t<ui-component name=\"input\" path=\"form.email\" config=\"placeholder:example@email.com;type:email\">Email</ui-component>\n\t<br />\n\t<ui-component name=\"input\" path=\"form.phone\" config=\"placeholder:+421;type:phone\">Phone</ui-component>\n\t<br />\n\t<ui-component name=\"input\" path=\"form.radio\" config=\"type:radiobutton;dirsource:Male,Female,Unknown\">Gender</ui-component>\n\t<br />\n\t<ui-component name=\"input\" path=\"form.radio\" config=\"type:radiobutton;dirsource:Male,Female,Unknown;multiline:1\">Gender</ui-component>\n\t<br />\n\t<div style=\"width:150px\">\n\t\t<ui-component name=\"input\" path=\"form.age\" config=\"type:number;innerlabel:false\">Your age</ui-component>\n\t</div>\n</div>\n\n<script>\n\tvar frameworks = [{ id: 0, name: 'Nothing', template: '<b>{{ name }}</b>' }, { id: 1, name: 'jComponent' }, { id: 2, name: 'Angular.js' }, { id: 3, name: 'React' }];\n\tvar cities = ['Slovakia', 'Poland', 'Hungary', 'Germany'];\n\tvar form = {};\n\tform.age = 30;\n</script>\n```"}],"knowledge":{"content_sha256":"25c6758b92023ff06d8a46fceab326b61921899f46c896b6967b39004a709ec9","source_sha256":"713f9a17406ca70be7388959bece2c45ee59830773cc84f75d0acc3055c0e90f","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","HTML","LAZY","MIT","NEW_TEXT","SET","ZIP"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"LAZY directory\" config=\"placeholder:Search...\"></ui-component>\n<ui-component name=\"LAZY datepicker\"></ui-component>\n<ui-component name=\"LAZY icons\"></ui-component>\n\n<div style=\"width:400px\">\n\t<ui-component name=\"input\" path=\"form.framework\" config=\"placeholder:Choose a framework;dirsource:frameworks\" class=\"m\">Framework</ui-component>\n\t<ui-component name=\"input\" path=\"form.terms\" config=\"type:checkbox\" class=\"m\">I agree with terms</ui-component>\n\t<ui-component name=\"input\" path=\"form.notes\" config=\"type:multiline;innerlabel:1;placeholder:Enter notes\" class=\"m\">Notes</ui-component>\n\t<ui-component name=\"input\" path=\"form.frameworks\" config=\"placeholder:Select frameworks;multiple:true;dirsource:frameworks\">Frameworks</ui-component>\n\t<br />\n\t<ui-component name=\"input\" path=\"form.city\" config=\"required:1;placeholder:Where do you live?;dirsource:cities;dircustom:true\">City</ui-component>\n\t<br />\n\t<ui-component name=\"input\" path=\"cities\" config=\"type:tags;placeholder:Type a tag and press enter\">Tags</ui-component>\n\t<br />\n\t<ui-component name=\"input\" path=\"form.date\" config=\"placeholder:Enter date;type:date;required:true;innerlabel:true\">Date</ui-component>\n\t<br />\n\t<ui-component name=\"input\" path=\"form.search\" config=\"placeholder:Search...;type:search;innerlabel:true\">Search</ui-component>\n\t<br />\n\t<ui-component name=\"input\" path=\"form.password\" config=\"placeholder:Type a password...;type:password\">Password</ui-component>\n\t<br />\n\t<ui-component name=\"input\" path=\"form.zip\" config=\"mask:###-##;maskregexp:\\d,\\d,\\d,null,\\d,\\d\">ZIP</ui-component>\n\t<br />\n\t<ui-component name=\"input\" path=\"form.email\" config=\"placeholder:example@email.com;type:email\">Email</ui-component>\n\t<br />\n\t<ui-component name=\"input\" path=\"form.phone\" config=\"placeholder:+421;type:phone\">Phone</ui-component>\n\t<br />\n\t<ui-component name=\"input\" path=\"form.radio\" config=\"type:radiobutton;dirsource:Male,Female,Unknown\">Gender</ui-component>\n\t<br />\n\t<ui-component name=\"input\" path=\"form.radio\" config=\"type:radiobutton;dirsource:Male,Female,Unknown;multiline:1\">Gender</ui-component>\n\t<br />\n\t<div style=\"width:150px\">\n\t\t<ui-component name=\"input\" path=\"form.age\" config=\"type:number;innerlabel:false\">Your age</ui-component>\n\t</div>\n</div>\n\n<script>\n\tvar frameworks = [{ id: 0, name: 'Nothing', template: '<b>{{ name }}</b>' }, { id: 1, name: 'jComponent' }, { id: 2, name: 'Angular.js' }, { id: 3, name: 'React' }];\n\tvar cities = ['Slovakia', 'Poland', 'Hungary', 'Germany'];\n\tvar form = {};\n\tform.age = 30;\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/inputtags","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/inputtags/","markdown_url":"https://forge.totaljs.es/en/components/inputtags.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"d3e92dfcf00ab31331f9d84da6d45b19bf080a8f3d0eb1ab8e0635fa88e7b4a5","checked_at":"2026-10-20T12:21:59.244Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-InputTags","description":"j-InputTags: if licon or ricon starts with ! then the component render a raw value instead of icon","messages":[{"role":"user","content":"j-InputTags\n\nj-InputTags: if licon or ricon starts with ! then the component render a raw value instead of icon"},{"role":"assistant","content":"__Configuration__:\n\n- `required {Boolean}` optional, enables \"required\" (default: `false`)\n- `icon {String}` optional, icon for label e.g. `home`, `cog`, etc.\n- `licon {String}` optional, left icon e.g. `home`, `cog`, etc.\n- `liconclick {String}` optional, a path to method `function(component, input)`\n- `ricon {String}` optional, right icon e.g. `home`, `cog`, etc.\n- `riconclick {String}` optional, a path to method `function(component, input)`\n- `label {String}` optional, a label (default is HTML content)\n- `after {String}` optional, it means a char after label (default: `:`)\n- `placeholder {String}` optional, adds a `placeholder` text into the input\n- `validate {String}` optional, a condition for validating of value, can contain a link to `function(value)` or `!!value.match(/[a-z]+/)`\n- `disabled {Boolean}` optional, disables this component\n- `error {String}` optional, adds a `string` text under the component\n- `spaces {Boolean}` optional, enables spaces otherwise it removes them (default: `true`)\n- `innerlabel {Boolean}` optional, enables inner label (default: `true`)\n- `dirsource {String}` optional, path to a data-source or __NEW__ can contain a link to method `function(search, next(items_arr))`\n- `dircustom {String/Boolean}` optional, can contain a path to `function(val, next(new_val))` or can be `Boolean`. This option can enable adding a custom value (value not defined in data-source)\n- `dirrender {String}` optional, a path to `function(item, text)` (must return HTML for `j-Directory`), this function can affect list of items in `j-Directory`\n- `dirminwidth {Number}` optional, a minimum width for `j-Directory`, default: `200`\n- `dirmaxwidth {Number}` optional, a maximum width for `j-Directory`\n- `dirplaceholder {String}` optional, a placeholder for `j-Directory`\n- `dirempty {String}` optional, adds an empty field for `j-Directory`\n- `dirkey {String}` optional, a key name for reading of `text` in `dirsource` (default: `name`)\n- `dirvalue {String}` optional, a key name for reading of `value` in `dirsource` (default: `id`)\n- `autosource {String}` a path to `search` function in `autocomplete`, `function(search, render(arr))`\n- `autovalue {String}` a property path for the value in `autosource`\n- `transform {Number}` optional, transforms the text `0 - none, 1 - lowercase, 2 - uppercase, 3 - capitalize` (Default: `0`)\n- `enteronly {Boolean}` optional, appends a new value after `enter` key is pressed (default: `true`)\n\n__Interesting:__\n\n- `dirsource:path.to.datasource` needs __`directory`__ component\n- if `licon` or `ricon` starts with `!` then the component render a raw value instead of `icon`\n- `dirsource` with paths to methods can load data asynchronously\n\nUse it with `<ui-component name=\"inputtags\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"directory\" config=\"placeholder:Search...\"></ui-component>\n\n<div style=\"width:400px\">\n\t<ui-component name=\"inputtags\" path=\"form.frameworks\" config=\"required:1;placeholder:Type a framework name;dirsource:frameworks\">Framework</ui-component>\n\t<br />\n\t<ui-component name=\"inputtags\" path=\"form.countries\" config=\"innerlabel:true\">Country</ui-component>\n</div>\n\n<script>\n\tvar frameworks = [{ id: 1, name: 'jComponent' }, { id: 2, name: 'Angular.js' }, { id: 3, name: 'React' }];\n\tvar form = { framework: [1], countries: ['Slovakia', 'Poland', 'Hungary', 'Germany'] };\n</script>\n```"}],"knowledge":{"content_sha256":"9bdc3d697f00af9925dfd5162b3de5e9a6c9da7627c3c1d342315b45764ace24","source_sha256":"d3e92dfcf00ab31331f9d84da6d45b19bf080a8f3d0eb1ab8e0635fa88e7b4a5","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","HTML","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"directory\" config=\"placeholder:Search...\"></ui-component>\n\n<div style=\"width:400px\">\n\t<ui-component name=\"inputtags\" path=\"form.frameworks\" config=\"required:1;placeholder:Type a framework name;dirsource:frameworks\">Framework</ui-component>\n\t<br />\n\t<ui-component name=\"inputtags\" path=\"form.countries\" config=\"innerlabel:true\">Country</ui-component>\n</div>\n\n<script>\n\tvar frameworks = [{ id: 1, name: 'jComponent' }, { id: 2, name: 'Angular.js' }, { id: 3, name: 'React' }];\n\tvar form = { framework: [1], countries: ['Slovakia', 'Poland', 'Hungary', 'Germany'] };\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/intro","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/intro/","markdown_url":"https://forge.totaljs.es/en/components/intro.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"00383c8bb6a649a60e9f7efe5844d408205e4d9672b35c38775b24fd0fc03f36","checked_at":"2026-10-20T12:22:00.136Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Intro","description":"j-Intro: NEW: delay {Number} a delay for next button between clicks; NEW: nexticon {String} a done icon (default: ti ti-chevron-right)","messages":[{"role":"user","content":"j-Intro\n\nj-Intro: NEW: delay {Number} a delay for next button between clicks; NEW: nexticon {String} a done icon (default: ti ti-chevron-right)"},{"role":"assistant","content":"__Configuration__:\n\n- `if {String}` condition for showing of the intro, it's compared with the value within of `path`\n- `next {String}` optional, text for the next step button (default: `Next`)\n- `close {String}` optional, text for the close step button (default: `Done`)\n- `page {String}` optional, a link to method `function(page) {}` it's executed when page is changed\n- `exec {String}` optional, a link to method `function() {}` it's executed when the intro is closed\n- `check {String}` optional, a link to method `function(done) {}` it's executed when is clicked on button last button\n- `remove {Boolean}` optional, when `true` then the component will be removed after close (default: `false`)\n- `closebutton {Boolean}` shows close button at the right top corner (default: `false`)\n- `width {Number}` a maximum width (default: `400`)\n- `height {Number}` height (default: `300`)\n- __NEW__: `delay {Number}` a delay for `next` button between clicks\n- __NEW__: `nexticon {String}` a done icon (default: `ti ti-chevron-right`)\n- __NEW__: `doneicon {String}` a done icon (default: `ti ti-check-circle`)\n\n__Good to know__:\n- each button with `name=\"next\"` will be used as `next` button\n- each button with `name=\"close\"` will be used as `close` button\n- a link with `next` class provides will be used as a `next` button\n- a link with `close` class provides will be used as a `close` button\n\nUse it with `<ui-component name=\"intro\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"intro\" path=\"showme\" config=\"if:myintro\" class=\"hidden\">\n\t<figure>A</figure>\n\t<figure>B</figure>\n\t<figure>C</figure>\n\t<figure>D</figure>\n\t<figure>E</figure>\n</ui-component>\n\n<script>\n\tSET('showme', 'myintro', 2000);\n</script>\n```"}],"knowledge":{"content_sha256":"73e2696cfef57960ba8e9d20e61f28fad4e0f49b14ebf7bc41973767332a75bc","source_sha256":"00383c8bb6a649a60e9f7efe5844d408205e4d9672b35c38775b24fd0fc03f36","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","SET"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"intro\" path=\"showme\" config=\"if:myintro\" class=\"hidden\">\n\t<figure>A</figure>\n\t<figure>B</figure>\n\t<figure>C</figure>\n\t<figure>D</figure>\n\t<figure>E</figure>\n</ui-component>\n\n<script>\n\tSET('showme', 'myintro', 2000);\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/isopen","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/isopen/","markdown_url":"https://forge.totaljs.es/en/components/isopen.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"93bda4adc11a522ffcd2c404a89b5674e314c37b2d362725b5b67e9cef8c677d","checked_at":"2026-10-20T12:22:00.872Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-IsOpen","description":"j-IsOpen is a simple component for showing opening hours. The component supports auto-refresh datetime.","messages":[{"role":"user","content":"j-IsOpen\n\nj-IsOpen is a simple component for showing opening hours. The component supports auto-refresh datetime."},{"role":"assistant","content":"__j-IsOpen__ is a simple component for showing opening hours. The component supports auto-refresh `datetime`.\n\n__Tangular model properties__:\n\n```html\n{{ date }}    - (date) current date instance\n{{ month }}   - (number) current month (1=januar ... 12=december)\n{{ year }}    - (number) current year\n{{ day }}     - (number) current day number in calendar\n{{ daytype }} - (number) current day type (0=sunday ... 6=saturday)\n{{ minutes }} - (number) current minutes\n{{ hours }}   - (number) current hours\n{{ week }}    - (boolean) is weekend?\n```\n\nUse it with `<ui-component name=\"isopen\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"isopen\" config=\"class:hidden\" class=\"hidden\">\n\t<script type=\"text/html\">\n\t\t{{ if week }}<span class=\"closed\">closed</span>{{ else }}<span class=\"open\">open</span>{{ fi }}\n\t\t<div>07:00 - 09:00</div>\n\t</script>\n</ui-component>\n```"}],"knowledge":{"content_sha256":"0e9f52d1b7a58197d95b8ddabd879915fa50e3026408aac226285fd7c86eb181","source_sha256":"93bda4adc11a522ffcd2c404a89b5674e314c37b2d362725b5b67e9cef8c677d","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"isopen\" config=\"class:hidden\" class=\"hidden\">\n\t<script type=\"text/html\">\n\t\t{{ if week }}<span class=\"closed\">closed</span>{{ else }}<span class=\"open\">open</span>{{ fi }}\n\t\t<div>07:00 - 09:00</div>\n\t</script>\n</ui-component>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/jsontree","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/jsontree/","markdown_url":"https://forge.totaljs.es/en/components/jsontree.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"658a50b464b627a907a8e98f07fd8d899144f97bd55621d70012de627a1f1e96","checked_at":"2026-10-20T12:22:01.758Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-JSONTree","description":"j-JSONTree creates tree view from json data.","messages":[{"role":"user","content":"j-JSONTree\n\nj-JSONTree creates tree view from json data."},{"role":"assistant","content":"__j-JSONTree__ creates tree view from json data.\n\n\n__Configuration__:\n\n- `click {String}` a link to the `function(selected_path, component)`\n\nUse it with `<ui-component name=\"jsontree\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Martin Smola. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"jsontree\" path=\"json\" config=\"click:jsontree_click\">Sample JSON data</ui-component>\n<script>\n\n\tsetTimeout(function() {\n\t\tSET('json', '{\"datecreated\":\"2018-06-18T22:27:00.000Z\",\"name\":\"j-JSONTree\",\"color\":\"transparent\",\"version\":1,\"responsive\":true,\"picture\":\"picture.png\",\"dependencies\":\"\",\"author\":\"Martin Smola\",\"email\":\"smola.martin@gmail.com\",\"license\":\"MIT\"}');\n\t}, 2000);\n\n\tfunction jsontree_click(path) {\n\t\tconsole.log('Path:', path);\n\t};\n</script>\n```"}],"knowledge":{"content_sha256":"55726dd4e68385849c0194f01ef246909d3030fbc3019ab38f6c1a5795cdb8f2","source_sha256":"658a50b464b627a907a8e98f07fd8d899144f97bd55621d70012de627a1f1e96","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","JSON","JSONT","MIT","SET"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"jsontree\" path=\"json\" config=\"click:jsontree_click\">Sample JSON data</ui-component>\n<script>\n\n\tsetTimeout(function() {\n\t\tSET('json', '{\"datecreated\":\"2018-06-18T22:27:00.000Z\",\"name\":\"j-JSONTree\",\"color\":\"transparent\",\"version\":1,\"responsive\":true,\"picture\":\"picture.png\",\"dependencies\":\"\",\"author\":\"Martin Smola\",\"email\":\"smola.martin@gmail.com\",\"license\":\"MIT\"}');\n\t}, 2000);\n\n\tfunction jsontree_click(path) {\n\t\tconsole.log('Path:', path);\n\t};\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/kanban","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/kanban/","markdown_url":"https://forge.totaljs.es/en/components/kanban.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"803a9b57b6dfc89ea70f89ca74ec15a6955bb639bfb570ef6fd51642a9a50d40","checked_at":"2026-10-20T12:22:02.659Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Kanban","description":"Important: each item needs own ID {String}, look to example.","messages":[{"role":"user","content":"j-Kanban\n\nImportant: each item needs own ID {String}, look to example."},{"role":"assistant","content":"__Configuration__:\n\n- `parent {String}` a container with fixed height, can be `window`. Default value: `parent` element.\n- `margin {Number}` a top/bottom margin together (default: `0`)\n- `padding {Number}` a padding between panels (default: `10`)\n- `style {Number}` a style, `1` (default): boxes will have a full-height, `2` boxes will have `max-height `\n- `move {String}` a path to method `function(group, target)`, is executed if the user moves an item to another group\n- `remove {String}` a path to method `function(group)`, is executed if the user removes an item in group\n- `dblclick {String}` a path to method `function(group)`, is executed if the user performs double click\n\n__Methods__:\n\n- `component.remove(id)` can remove an item in group\n- `component.findgroup(id)` returns a group instance\n\n__Important__: each item needs own ID {String}, look to example.\n\nUse it with `<ui-component name=\"kanban\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\t.caption { line-height: 50px; border-bottom: 1px solid #E0E0E0; padding: 0 10px; font-weight: bold; }\n\t.ui-dark .caption { border-bottom-color: #404040; }\n</style>\n\n<ui-component name=\"kanban\" path=\"items\" config=\"parent:window;move:onmoveitem;margin:50\">\n\n\t<!-- Group header rendering -->\n\t<script type=\"text/html\">\n\t\t<div class=\"caption\">{{ name }}</div>\n\t</script>\n\n\t<!-- Item rendering -->\n\t<script type=\"text/html\">\n\t\t<div>{{ name }}</div>\n\t</script>\n</ui-component>\n\n<script>\n\n\tvar items = [];\n\titems.push({ id: '1', name: 'In progress', items: [{ id: '11', name: 'Better life Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ea culpa voluptatem asperiores aspernatur doloremque cupiditate error, qui sit est quas.' }] });\n\titems.push({ id: '2', name: 'Pending', items: [{ id: '21', name: 'Improve j-ChunkyScroll' }] });\n\titems.push({ id: '3', name: 'Done', items: [{ id: '31', name: 'Fix scrollbars j-DataGrid' }, { id: '32', name: 'Improve performace' }] });\n\n\tfunction onmoveitem(group, target) {\n\t\tconsole.log('MOVEMENT:', group, target);\n\t}\n\n</script>\n```"}],"knowledge":{"content_sha256":"c2a9890ca0363889432d4b5c459599a3cf7e2aba2ca892cabaeb61e8b01b2b04","source_sha256":"803a9b57b6dfc89ea70f89ca74ec15a6955bb639bfb570ef6fd51642a9a50d40","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","E0E0E0","MIT","MOVEMENT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\t.caption { line-height: 50px; border-bottom: 1px solid #E0E0E0; padding: 0 10px; font-weight: bold; }\n\t.ui-dark .caption { border-bottom-color: #404040; }\n</style>\n\n<ui-component name=\"kanban\" path=\"items\" config=\"parent:window;move:onmoveitem;margin:50\">\n\n\t<!-- Group header rendering -->\n\t<script type=\"text/html\">\n\t\t<div class=\"caption\">{{ name }}</div>\n\t</script>\n\n\t<!-- Item rendering -->\n\t<script type=\"text/html\">\n\t\t<div>{{ name }}</div>\n\t</script>\n</ui-component>\n\n<script>\n\n\tvar items = [];\n\titems.push({ id: '1', name: 'In progress', items: [{ id: '11', name: 'Better life Lorem ipsum dolor sit amet, consectetur adipisicing elit. Ea culpa voluptatem asperiores aspernatur doloremque cupiditate error, qui sit est quas.' }] });\n\titems.push({ id: '2', name: 'Pending', items: [{ id: '21', name: 'Improve j-ChunkyScroll' }] });\n\titems.push({ id: '3', name: 'Done', items: [{ id: '31', name: 'Fix scrollbars j-DataGrid' }, { id: '32', name: 'Improve performace' }] });\n\n\tfunction onmoveitem(group, target) {\n\t\tconsole.log('MOVEMENT:', group, target);\n\t}\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/keyselection","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/keyselection/","markdown_url":"https://forge.totaljs.es/en/components/keyselection.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"36dc76bb2ed34856d419a67026e83db71c8ebd9ad0b0e1e76650ff473afa4a95","checked_at":"2026-10-20T12:22:03.394Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-KeySelection","description":"This component allows user to select some items by up/down keys and confirm selection by the enter key.","messages":[{"role":"user","content":"j-KeySelection\n\nThis component allows user to select some items by up/down keys and confirm selection by the enter key."},{"role":"assistant","content":"This component allows user to select some items by up/down keys and confirm selection by the `enter` key.\n\n- supports auto-scroll\n\n__Configuration__\n\n- `selectoritem {String}` a selector for browsing items (default: `.item`)\n- `selector {String}` a default selector for capturing keys (default: `input`)\n- `class {String}` a selection class (default: `selected`)\n- `autoselect {Boolean}` it should select first item (default: `true`)\n- `autoscroll {Boolean}` enables/disables auto-scroll (default: `true`)\n- `exec {String}` a link to the `function(jqueryel)`, evaluated by the `enter` key (optional, otherwise will be trigger `click` event)\n- `delay {Number}` a delay for auto-selecting first value\n- `datasource {String}` a link to the data-source that can help you with a dynamic list\n\nUse it with `<ui-component name=\"keyselection\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\t.selected { background: #F0F0F0; }\n\t.item { line-height: 24px; padding: 0 10px; border-top: 1px solid #E0E0E0; font-size: 12px; }\n</style>\n\n<ui-component name=\"keyselection\" config=\"exec:confirmselection\">\n\t<div class=\"m\"><input type=\"text\" placeholder=\"Press UP or DOWN key\" style=\"width:300px\" /></div>\n\t<div class=\"items\">\n\t\t<div class=\"item\" data-id=\"1\">1</div>\n\t\t<div class=\"item\" data-id=\"1\">2</div>\n\t\t<div class=\"item\" data-id=\"1\">3</div>\n\t\t<div class=\"item\" data-id=\"1\">4</div>\n\t\t<div class=\"item\" data-id=\"1\">5</div>\n\t</div>\n</ui-component>\n\n<script>\n\n\tfunction confirmselection(el) {\n\t\tconsole.log('selected', el);\n\t}\n\n</script>\n```"}],"knowledge":{"content_sha256":"2729b88bf4f70616de0b0c5dbbee3ee61ad6ae3025ad56ade9a6f9e30dbd508a","source_sha256":"36dc76bb2ed34856d419a67026e83db71c8ebd9ad0b0e1e76650ff473afa4a95","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","DOWN","E0E0E0","F0F0F0","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\t.selected { background: #F0F0F0; }\n\t.item { line-height: 24px; padding: 0 10px; border-top: 1px solid #E0E0E0; font-size: 12px; }\n</style>\n\n<ui-component name=\"keyselection\" config=\"exec:confirmselection\">\n\t<div class=\"m\"><input type=\"text\" placeholder=\"Press UP or DOWN key\" style=\"width:300px\" /></div>\n\t<div class=\"items\">\n\t\t<div class=\"item\" data-id=\"1\">1</div>\n\t\t<div class=\"item\" data-id=\"1\">2</div>\n\t\t<div class=\"item\" data-id=\"1\">3</div>\n\t\t<div class=\"item\" data-id=\"1\">4</div>\n\t\t<div class=\"item\" data-id=\"1\">5</div>\n\t</div>\n</ui-component>\n\n<script>\n\n\tfunction confirmselection(el) {\n\t\tconsole.log('selected', el);\n\t}\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/keyvalue","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/keyvalue/","markdown_url":"https://forge.totaljs.es/en/components/keyvalue.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"d40c70688d0861eb59d91aeb01d690dce5fa967842a0cbccb74d2779c2d6b4ac","checked_at":"2026-10-20T12:22:04.132Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-KeyValue","description":"A simple component for create key/value pairs values.","messages":[{"role":"user","content":"j-KeyValue\n\nA simple component for create key/value pairs values."},{"role":"assistant","content":"A simple component for create key/value pairs values.\n\n\n__Configuration__:\n\n- `placeholderkey {String}` a placeholder for the key input\n- `placeholdervalue {String}` a placeholder for the value input\n- `icon {String}` a label icon without `ti-`\n- `maxlength {Number}` max. lenght for inputs\n- `label {String}` a label (default: `innerHTML`)\n- `disabled {Boolean}` can disabled this control\n- `autocomplete {String}` a link to `function(input, component)` which is executed when an `input` is focused\n\n__Other functionality__\n\n```javascript\n// Binder\nSETTER('keyvalue/binder', function(type, value) {\n    if (type === 'key')\n        return value.toUpperCase();\n    else // type === 'value'\n        return value;\n});\n```\n\nUse it with `<ui-component name=\"keyvalue\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"keyvalue\" path=\"keyvalue\" config=\"placeholderkey:Type a key;placeholdervalue:Type a value and press enter\">Key Value component</ui-component>\n\n<script>\n\tvar keyvalue = {};\n</script>\n```"}],"knowledge":{"content_sha256":"41a10b482b547525c9ae32155f9658defd0d06d5e8f050603989b2710a1e29a1","source_sha256":"d40c70688d0861eb59d91aeb01d690dce5fa967842a0cbccb74d2779c2d6b4ac","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","SETTER"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"keyvalue\" path=\"keyvalue\" config=\"placeholderkey:Type a key;placeholdervalue:Type a value and press enter\">Key Value component</ui-component>\n\n<script>\n\tvar keyvalue = {};\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/largeform","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/largeform/","markdown_url":"https://forge.totaljs.es/en/components/largeform.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"7f3748d604eddda885989701546e862e9361ce573058701f166b4ea5561de5fb","checked_at":"2026-10-20T12:22:05.019Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-LargeForm","description":"New version of this component supports dynamic evaluation of the content of <script type='text/html'>. The example below contains a script with HTML and the component evaluates the content if the j-Fo","messages":[{"role":"user","content":"j-LargeForm\n\nNew version of this component supports dynamic evaluation of the content of <script type='text/html'>. The example below contains a script with HTML and the component evaluates the content if the j-Fo"},{"role":"assistant","content":"__Configuration__:\n\n- `title {String}` form title\n- `width {Number}` form max-width\n- `if {String}` condition for showing of the form, it's compared with the value within of `path`\n- `icon {String}` Total icon without `ti-` or __new__: `ti ti-home`\n- `reload {String}` link to a global `function(largeform_component)` and it's executed if the form is displaying\n- `submit {String}` link to a global `function(hide)` and it's executed if the submit button is pressed\n- `cancel {String}` link to a global `function(hide)` and it's executed if the cancel button is pressed\n- `enter {Boolean}` captures `enter` key automatically and performs submit (default: `false`)\n- `center {Boolean}` centers the form to middle of screen\n- `autofocus {Boolean/String}` can focus an input. `String` === `jQuery selector` for the input\n- `default {String}` a short alias for `DEFAULT(default, true)`\n- `closebutton {Boolean}` can hide `x` button (default: `false`)\n- `zindex {Number}` can affect z-index (default: `12`)\n- `scrollbar {Boolean}` enables custom scrollbar (default: `true`)\n- `scrolltop {Boolean}` enables scrolling to top after form is displayed (default: `true`)\n- `scrollbarshadow {Boolean}` optional, enables a shadow in scrollbar (default: `false`)\n- `style {Number}` supports two styles (1: classic (default), 2: bottom of for is without padding)\n- `closeoutside {Boolean}` closes the form when the user clicks outside of the form (default: `false`)\n- `closeesc {Boolean}` closes the form when the user presses `ESC` key\n- `independent {Boolean}` closing doesn't depent on the condition according to the path\n- `close {String}` link to a global `function()` and it's executed if the form is closing\n- __NEW__ `hide {String}` link to a global `function()` and it's executed if the form hides\n\n__Good to know__:\n\nNew version of this component supports dynamic evaluation of the content of `<script type=\"text/html\">`. The example below contains a script with HTML and the component evaluates the content if the j-Form will be displayed (only once).\n\n```html\n<ui-component name=\"largeform\" path=\"path\" config=\"config\">\n\t<script type=\"text/html\">\n\t\tA CONTENT\n\t</script>\n</ui-component>\n```\n\nUse it with `<ui-component name=\"largeform\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"largeform\" path=\"common.form\" config=\"if:user;width:500;title:Title;submit:userform_submit\" class=\"hidden\">\n\t<div class=\"padding\">\n\t\tSOME CONTENT\n\t</div>\n\t<nav>\n\t\t<button name=\"submit\">SUBMIT</button>\n\t\t<button name=\"cancel\">CANCEL</button>\n\t</nav>\n</ui-component>\n\n<script>\n\n\tfunction userform_submit(hide) {\n\t\talert('SUBMIT');\n\t\thide();\n\t}\n\n\tvar common = {};\n\n\t// Show the form\n\tcommon.form = 'user';\n</script>\n```"}],"knowledge":{"content_sha256":"05884d5c653523aa8e34afbe39e4f60591383ec72886cc1eb368e328b2bd3b08","source_sha256":"7f3748d604eddda885989701546e862e9361ce573058701f166b4ea5561de5fb","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CANCEL","CDN","CONTENT","DEF","DEFAULT","ESC","HTML","MIT","SOME","SUBMIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"largeform\" path=\"common.form\" config=\"if:user;width:500;title:Title;submit:userform_submit\" class=\"hidden\">\n\t<div class=\"padding\">\n\t\tSOME CONTENT\n\t</div>\n\t<nav>\n\t\t<button name=\"submit\">SUBMIT</button>\n\t\t<button name=\"cancel\">CANCEL</button>\n\t</nav>\n</ui-component>\n\n<script>\n\n\tfunction userform_submit(hide) {\n\t\talert('SUBMIT');\n\t\thide();\n\t}\n\n\tvar common = {};\n\n\t// Show the form\n\tcommon.form = 'user';\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/markdown","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/markdown/","markdown_url":"https://forge.totaljs.es/en/components/markdown.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"bb815608ffb482c40733ae968abc58f3d8fc0cf05bc6a7db93f125dc667f0809","checked_at":"2026-10-20T12:27:18.987Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Declared browser assertions and component lifecycle; not full functional coverage.","attempts":1},"title":"j-Markdown","description":"This component contains only Markdown parser.","messages":[{"role":"user","content":"j-Markdown\n\nThis component contains only Markdown parser."},{"role":"assistant","content":"This component contains only Markdown parser.\n\n__Configuration__:\n\n- `highlight {Boolean}` enables auto-syntax highlighter (default: `true`)\n- `charts {Boolean}` enables Apex charts rendering (default: `false`)\n- `cdn {String}` a default CDN URL address for 3rd party dependencies (default: `DEF.cdn`)\n\n__Markdown settings__:\n\n```js\nvar opt = {};\nvar text = 'YOUR_MARKDOWN_STRING';\n\nconsole.log(text.markdown(opt));\n```\n\n- `opt.wrap = true` wraps the output with `<div class=\"markdown\">YOUR_MARKDOWN</div>`\n- `opt.linetag = 'p'` a default new line tag\n- `opt.ul = true` enables unordered lists\n- `opt.code = true` enables custom codes\n- `opt.images = true` enables images\n- `opt.links = true` enables links\n- `opt.formatting = true` enables basic text formatting\n- `opt.icons = true` enables Font-Awesome icons via `:home:` or `:cog:`\n- `opt.tables = true` enables tables\n- `opt.br = true` enables new lines via `<br>`\n- `opt.emptynewline = true` empty lines will be rendered as empty lines\n- `opt.headlines = true` enables headlines\n- `opt.hr = true` enables page breaks\n- `opt.blockquotes = true` enables blockquotes `< blockqote`\n- `opt.custom = function(line) { return line; }` a custom parser for each processed line\n- `opt.html = function(line, type) { return line; }` a custom parser for each post-processed line\n- `opt.sections = true` enables sections `> section`\n- `opt.footnotes = true` enables footnotes `#1: foot note description` and usage in links `[link](#1)`\n- `opt.urlify = true` converts URL addresses to links\n- `opt.keywords = true` parses keywords in the form `{keyword}(type)`\n- `opt.noredraw = true` skips redrawing of code/video/section/block parts (default: `false`)\n- `opt.element {jQuery}` HTML element that will be used as a default element in `FUNC.markdownredraw()`\n- `opt.bookmarks {Boolean}` enables/disables creating headline bookmarks `id=\"\"` (default: `true`)\n- `opt.prefix {String}` a prefix for bookmarks (default: `empty`)\n- __NEW__: `opt.ol = true` enables ordered lists\n\n__Good to know__:\n\n- images will be with `img-responsive` class\n- images with `![+Image description](URL)` will be formatted as an inline image\n- images with `![-Image description](URL)` will be rendered as an inline image with class `gallery` instead of `img-responsive`\n- all links are with `_target=\"_blank\"` attribute\n- markdown registers `FUNC.markdownredraw(jQuery_selector, [markdown_options])` for prerendering of Markdown dynamic elements like code highlight, videos or charts\n- secret section is defined like code with syntax `secret`\n- class `markdown-small` can decrease font sizes\n- event `ON('markdown', function(el, opt) {})` is executed if the markdown is pre-rendered (`el {jQuery}` is `<body` element in most cases)\n- components registers `Thelpers.markdown([opt])` helper\n- every rendered line contain `markdown-line` class and `data-line=\"line_number\"` attribute\n\n__Toggleable section:__\n\n```html\n::: Section name\nAnother markdown content\n:::\n```\n\n## __NEW__: Inline usage\n\n```html\n<ui-markdown config=\"code:true|false (default);small:true|false (default)\">\n\t<script type=\"text/plain\">\n\t\tYOUR MARKDOWN CONTENT\n\t</script>\n</ui-markdown>\n```\n\nUse it with `<ui-component name=\"markdown\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"info\"></ui-component>\n<ui-component name=\"markdown\" config=\"highlight:false\"></ui-component>\n\n<ui-bind path=\"markdown\" config=\"html:Thelpers.markdown(value)\" class=\"block\"></ui-bind>\n\n<hr />\n\n<ui-markdown>\n\t<script type=\"text/plain\">\n# Hello world!\n\nInline markdown defined in `<ui-markdown>` element.\n\t</script>\n</ui-markdown>\n\n<script>\n\tvar markdown = '# Hello `world`\\nLorem ipsum dolor sit amet, **consectetur** adipisicing elit. Eius [1](#1), ~~illo~~.\\n\\n> This is a simple blockquote\\n\\n< And this one is section\\n\\n```javascript \\nalert(\\'Markdown Preview\\');\\n```\\n\\n::: Toggleable section\\nHello world!\\n:::\\n\\n```secret\\nHello world\\n```\\n\\n#1: footnote';\n\n\t$(document).on('mouseenter mouseleave click', '.markdown-footnote', function(e) {\n\t\tif (e.type === 'mouseenter' || e.type === 'click') {\n\t\t\tvar el = $(this);\n\t\t\tvar opt = {};\n\t\t\topt.element = this;\n\t\t\topt.offsetX = 10;\n\t\t\topt.html = $('.markdown-footnotebody[data-id=\"{0}\"]'.format(el.attrd('id'))).html();\n\t\t\tSETTER('info/show', opt);\n\t\t} else\n\t\t\tSETTER('!info/hide', true);\n\t});\n\n</script>\n```"}],"knowledge":{"content_sha256":"999520d799fcf1c2993df48b696a78e33988ed312887e82fd8121f065fff360d","source_sha256":"bb815608ffb482c40733ae968abc58f3d8fc0cf05bc6a7db93f125dc667f0809","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","CONTENT","DEF","FUNC","HTML","MARKDOWN","MIT","SETTER","URL","YOUR","YOUR_MARKDOWN","YOUR_MARKDOWN_STRING"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"info\"></ui-component>\n<ui-component name=\"markdown\" config=\"highlight:false\"></ui-component>\n\n<ui-bind path=\"markdown\" config=\"html:Thelpers.markdown(value)\" class=\"block\"></ui-bind>\n\n<hr />\n\n<ui-markdown>\n\t<script type=\"text/plain\">\n# Hello world!\n\nInline markdown defined in `<ui-markdown>` element.\n\t</script>\n</ui-markdown>\n\n<script>\n\tvar markdown = '# Hello `world`\\nLorem ipsum dolor sit amet, **consectetur** adipisicing elit. Eius [1](#1), ~~illo~~.\\n\\n> This is a simple blockquote\\n\\n< And this one is section\\n\\n```javascript \\nalert(\\'Markdown Preview\\');\\n```\\n\\n::: Toggleable section\\nHello world!\\n:::\\n\\n```secret\\nHello world\\n```\\n\\n#1: footnote';\n\n\t$(document).on('mouseenter mouseleave click', '.markdown-footnote', function(e) {\n\t\tif (e.type === 'mouseenter' || e.type === 'click') {\n\t\t\tvar el = $(this);\n\t\t\tvar opt = {};\n\t\t\topt.element = this;\n\t\t\topt.offsetX = 10;\n\t\t\topt.html = $('.markdown-footnotebody[data-id=\"{0}\"]'.format(el.attrd('id'))).html();\n\t\t\tSETTER('info/show', opt);\n\t\t} else\n\t\t\tSETTER('!info/hide', true);\n\t});\n\n</script>"}],"test":"const results = [];\nif (typeof String.prototype.markdown !== 'function')\n  results.push('Markdown parser is available');\nelse {\n  const html = '# Title\\n\\n**Bold**'.markdown();\n  if (!/<h1[^>]*>/.test(html) || !/<(?:b|strong)>Bold<\\//.test(html))\n    results.push('Headings and emphasis render as HTML');\n}\nif (!document.querySelector('ui-markdown h1')?.textContent.includes('Hello world'))\n  results.push('Inline Markdown renders through its custom element');\nresults","test_kind":"component-browser"}
{"id":"components/layer","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/layer/","markdown_url":"https://forge.totaljs.es/en/components/layer.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"e24aca50bc9cbe1aad7f62435e66270dd8332a02a2917b534b6de1d9c86cccd5","checked_at":"2026-10-20T12:22:05.762Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Layer","description":"j-Layer: Works with Bootstrap; NEW: autofocus {Boolean/String} focuses the first input, textarea (default: false)","messages":[{"role":"user","content":"j-Layer\n\nj-Layer: Works with Bootstrap; NEW: autofocus {Boolean/String} focuses the first input, textarea (default: false)"},{"role":"assistant","content":"- Works with Bootstrap\n\n__Configuration__:\n\n- `title` {String} form title\n- `if` {String} condition for showing of layer, it's compared with the value within of `path`\n- `reload` {String} link to a global function and it's executed if the form is showing\n- `container` {String} jQuery selector for auto-height dimension (default: `.ui-layer-body`)\n- `default` {String} (optional) a short alias for `DEFAULT(default, true)`\n- `scrollbar` {Boolean} enables custom scrollbar (default: `true`)\n- __NEW__: `autofocus` {Boolean/String} focuses the first input, textarea (default: `false`)\n\nUse it with `<ui-component name=\"layer\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tenetur hic quod, asperiores dicta.</div>\n<div>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tenetur hic quod, asperiores dicta.</div>\n<div>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tenetur hic quod, asperiores dicta.</div>\n<div>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tenetur hic quod, asperiores dicta.</div>\n<div>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tenetur hic quod, asperiores dicta.</div>\n<div>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tenetur hic quod, asperiores dicta.</div>\n\n<ui-component name=\"layer\" path=\"common.layer\" config=\"if:layerA;title:Title for layer A\" class=\"hidden\">\n\t<div class=\"ui-layer-body\">\n\t\tLayer A\n\t</div>\n</ui-component>\n\n<ui-component name=\"layer\" path=\"common.layer\" config=\"if:layerB;title:Title for layer B\" class=\"hidden\">\n\t<div class=\"ui-layer-body\">\n\t\tLayer B\n\t</div>\n</ui-component>\n\n<ui-component name=\"layer\" path=\"common.layer\" config=\"if:layerC;title:Title for layer C\" class=\"hidden\">\n\t<div class=\"ui-layer-body\">\n\t\tLayer C\n\t</div>\n</ui-component>\n\n<script>\n\tvar common = {};\n\n\t// Show the layer\n\tcommon.layer = [];\n\n\tsetTimeout(function() {\n\t\tPUSH('common.layer', 'layerA');\n\t}, 1000);\n\n\tsetTimeout(function() {\n\t\tPUSH('common.layer', 'layerB');\n\t}, 3000);\n\n\tsetTimeout(function() {\n\t\tPUSH('common.layer', 'layerC');\n\t}, 5000);\n\n</script>\n```"}],"knowledge":{"content_sha256":"163cc9211b7f715dc49741961c8235517280cbe26da04a37adbee812c6aac765","source_sha256":"e24aca50bc9cbe1aad7f62435e66270dd8332a02a2917b534b6de1d9c86cccd5","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","DEFAULT","MIT","PUSH"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tenetur hic quod, asperiores dicta.</div>\n<div>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tenetur hic quod, asperiores dicta.</div>\n<div>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tenetur hic quod, asperiores dicta.</div>\n<div>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tenetur hic quod, asperiores dicta.</div>\n<div>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tenetur hic quod, asperiores dicta.</div>\n<div>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tenetur hic quod, asperiores dicta.</div>\n\n<ui-component name=\"layer\" path=\"common.layer\" config=\"if:layerA;title:Title for layer A\" class=\"hidden\">\n\t<div class=\"ui-layer-body\">\n\t\tLayer A\n\t</div>\n</ui-component>\n\n<ui-component name=\"layer\" path=\"common.layer\" config=\"if:layerB;title:Title for layer B\" class=\"hidden\">\n\t<div class=\"ui-layer-body\">\n\t\tLayer B\n\t</div>\n</ui-component>\n\n<ui-component name=\"layer\" path=\"common.layer\" config=\"if:layerC;title:Title for layer C\" class=\"hidden\">\n\t<div class=\"ui-layer-body\">\n\t\tLayer C\n\t</div>\n</ui-component>\n\n<script>\n\tvar common = {};\n\n\t// Show the layer\n\tcommon.layer = [];\n\n\tsetTimeout(function() {\n\t\tPUSH('common.layer', 'layerA');\n\t}, 1000);\n\n\tsetTimeout(function() {\n\t\tPUSH('common.layer', 'layerB');\n\t}, 3000);\n\n\tsetTimeout(function() {\n\t\tPUSH('common.layer', 'layerC');\n\t}, 5000);\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/layout","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/layout/","markdown_url":"https://forge.totaljs.es/en/components/layout.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"d167fc7be792858ee8bad2d81e1153c125fd258fa619ed19f762e403a8bcb295","checked_at":"2026-10-20T12:22:06.651Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Layout","description":"Making of layout is very easy and with a lot of possibilities. Layout must have own HTML structure. Look to example. The example below describes only meta data of the layout.","messages":[{"role":"user","content":"j-Layout\n\nMaking of layout is very easy and with a lot of possibilities. Layout must have own HTML structure. Look to example. The example below describes only meta data of the layout."},{"role":"assistant","content":"- This component can create a layout for your SPA. This component is a bit harder for understanding, but it's very helpful. `j-Layout` uses jComponent `+v17`.\n\n__Configuration__:\n\n- `parent {String}` can contain `parent`, `window` or jQuery selector for the `closest` method (default: `window`)\n- `margin {Number}` adds margin for the `height` (default: `0`)\n- `remember {Boolean}` can remember last settings of layout for resizing (default: `true`)\n- `space {Number}` internal, contains a space between panels (default: `1`)\n- `border {Number}` internal, contains a border size (default: `0`)\n- `resize {String}` internal, a path to method which it's executed when the layout is resized\n- `autoresize {Boolean}` enables auto-resize (default: `true`)\n\n__Methods__:\n\n- `component.refresh()` refreshes layout\n- `component.resize()` performs resize\n- `component.reset()` performs reset of resizing\n- `component.layout(name)` changes a layout (like two way data-binding)\n- `component.rebind(code_string)` makes a new layout configuration\n- `component.lock(type, [boolean])` locks the panel\n\n__Panel types__:\n\n- `left` left panel, resizable\n- `right` right panel, resizable\n- `right2` right panel will be next to the left panel and uses `right` key in the config, resizable\n- `top` top panel, resizable (it's panel on top of all panels)\n- `top2` top panel, resizable and uses `top` key in the config (it is between `left` and `right` panel)\n- `bottom` bottom panel, resizable (it's panel on bottom of all panels)\n- `bottom2` bottom panel, resizable and uses `bottom` key in the config (it is between `left` and `right` panel)\n- `main` main panel, __can't be resizable__ and size is counted automatically\n\n__Layout__:\n\nMaking of layout is very easy and with a lot of possibilities. Layout must have own HTML structure. Look to example. The example below describes only meta data of the layout.\n\n- `size {Number/String}` can contain a height/width of panel according to the panel type. Number `120` defines pixels and String `100%` can define percentage\n- `minsize {Number/String}` is a minimal size of the panel while the panel is resizing (default: `size` value)\n- `maxsize {Number/String}` is a maximal size of the panel while the panel is resizing (default: auto-counted)\n- `resize {Boolean}` enables resizing of the panel with except `main` panel (it's counted automatically)\n- `show {Boolean}` enables visibility (default: `false`)\n\n```js\n{\n\ttop: { size: 80, resize: true, minsize: 50 }, // top panel\n\tright: { size: 100 },\n\tbottom: { size: 100 }, // bottom panel\n\tleft: { size: 100, resize: true }, // left panel\n\tmain: {}, // main panel\n\n\t// Can contain a different template for various display types\n\t// lg = Large display, md = Medium display, sm = Small display, xs = Extra small display\n\tmd: {\n\t\ttop: { show: true, size: 80 },\n\t\tbottom: { show: false },\n\t\tleft: { show: false },\n\t\tmain: { show: true }\n\t},\n\n\t// Custom layouts\n\tdetail: {\n\n\t\t// Custom display types in custom layouts\n\t\tmd: {\n\t\t\ttop: { show: true },\n\t\t\tbottom: { show: true },\n\t\t\tmain: { show: true },\n\t\t\tleft: { show: false },\n\t\t\tright: { show: true, size: 300 }\n\t\t},\n\t\txs: {\n\t\t\ttop: { show: true },\n\t\t\tbottom: { show: true },\n\t\t\tmain: { show: false },\n\t\t\tleft: { show: false },\n\t\t\tright: { show: true, size: '100%' }\n\t\t}\n\t}\n}\n```\n\n__Good to know__:\n\n- __IMPORTANT__: this component must contain meta-data of the layout wrapped in `<script type=\"text/plain\"></script>`\n- each panel is `section` element and must contain `data-type=\"top\"` attribute with type of panel\n- define panels only which you will use\n- ordering of all `section` elements isn't important\n- `top2` uses `top` config key\n- `right2` uses `right` config key\n- `bottom2` uses `bottom` config key\n\nUse it with `<ui-component name=\"layout\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\t.ui-layout-section { font-size: 11px; }\n</style>\n\n<div style=\"width:500px;height:300px\">\n\t<ui-component name=\"layout\" path=\"mylayout\" config=\"parent:parent\" class=\"invisible\">\n\n\t\t<!-- META DATA OF THE LAYOUT -->\n\t\t<!-- VERY IMPORTANT -->\n\t\t<script type=\"text/plain\">\n\t\t\t{\n\t\t\t\ttop: { size: 80, resize: true },\n\t\t\t\tbottom: { size: 100 },\n\t\t\t\tleft: { size: 100, minsize: 50, resize: true },\n\t\t\t\tright: { size: 100 },\n\t\t\t\tmain: {},\n\t\t\t\tdetail: {\n\t\t\t\t\tleft: { show: false },\n\t\t\t\t\tright: { show: true, size: '100%' },\n\t\t\t\t\tbottom: { show: false },\n\t\t\t\t\tmain: { show: false }\n\t\t\t\t}\n\t\t\t}\n\t\t</script>\n\n\t\t<!-- PANEL IN LAYOUT -->\n\t\t<section data-type=\"top\">\n\t\t\tTOP\n\t\t</section>\n\n\t\t<!-- PANEL IN LAYOUT -->\n\t\t<section data-type=\"main\">\n\t\t\tMAIN\n\t\t</section>\n\n\t\t<!-- PANEL IN LAYOUT -->\n\t\t<section data-type=\"right\">\n\t\t\tRIGHT\n\t\t</section>\n\n\t\t<!-- PANEL IN LAYOUT -->\n\t\t<section data-type=\"left\">\n\t\t\tLEFT\n\t\t</section>\n\n\t\t<!-- PANEL IN LAYOUT -->\n\t\t<section data-type=\"bottom\">\n\t\t\tBOTTOM\n\t\t</section>\n\n\t</ui-component>\n</div>\n\n<br />\n\n<div style=\"width:500px;height:300px\">\n\t<ui-component name=\"layout\" config=\"parent:parent\" class=\"invisible\">\n\t\t<script type=\"text/plain\">\n\t\t\t{\n\t\t\t\ttop: { size: 80, resize: 1 },\n\t\t\t\tbottom: { size: 100 },\n\t\t\t\tleft: { size: 100 },\n\t\t\t\tright: { size: 100 },\n\t\t\t\tmain: {}\n\t\t\t}\n\t\t</script>\n\n\t\t<section data-type=\"top2\">\n\t\t\tTOP2\n\t\t</section>\n\n\t\t<section data-type=\"main\">\n\t\t\tMAIN\n\t\t</section>\n\n\t\t<section data-type=\"right\">\n\t\t\tRIGHT\n\t\t</section>\n\n\t\t<section data-type=\"left\">\n\t\t\tLEFT\n\t\t</section>\n\n\t\t<section data-type=\"bottom2\">\n\t\t\tBOTTOM2\n\t\t</section>\n\n\t</ui-component>\n</div>\n\n<script>\n\n\tvar mylayout = '';\n\n\tsetInterval(function() {\n\t\tSET('mylayout', mylayout === 'detail' ? '' : 'detail');\n\t}, 5000);\n\n\tsetInterval(function() {\n\t\tSETTER('layout', 'lock', 'right');\n\t}, 8000);\n\n</script>\n```"}],"knowledge":{"content_sha256":"b0c53645d08764ea6447ace7388d436af5256a381daf4d58d035cb744f290c21","source_sha256":"d167fc7be792858ee8bad2d81e1153c125fd258fa619ed19f762e403a8bcb295","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["BOTTOM","BOTTOM2","CDN","DATA","DEF","HTML","IMPORTANT","LAYOUT","LEFT","MAIN","META","MIT","PANEL","RIGHT","SET","SETTER","SPA","THE","TOP","TOP2","VERY"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\t.ui-layout-section { font-size: 11px; }\n</style>\n\n<div style=\"width:500px;height:300px\">\n\t<ui-component name=\"layout\" path=\"mylayout\" config=\"parent:parent\" class=\"invisible\">\n\n\t\t<!-- META DATA OF THE LAYOUT -->\n\t\t<!-- VERY IMPORTANT -->\n\t\t<script type=\"text/plain\">\n\t\t\t{\n\t\t\t\ttop: { size: 80, resize: true },\n\t\t\t\tbottom: { size: 100 },\n\t\t\t\tleft: { size: 100, minsize: 50, resize: true },\n\t\t\t\tright: { size: 100 },\n\t\t\t\tmain: {},\n\t\t\t\tdetail: {\n\t\t\t\t\tleft: { show: false },\n\t\t\t\t\tright: { show: true, size: '100%' },\n\t\t\t\t\tbottom: { show: false },\n\t\t\t\t\tmain: { show: false }\n\t\t\t\t}\n\t\t\t}\n\t\t</script>\n\n\t\t<!-- PANEL IN LAYOUT -->\n\t\t<section data-type=\"top\">\n\t\t\tTOP\n\t\t</section>\n\n\t\t<!-- PANEL IN LAYOUT -->\n\t\t<section data-type=\"main\">\n\t\t\tMAIN\n\t\t</section>\n\n\t\t<!-- PANEL IN LAYOUT -->\n\t\t<section data-type=\"right\">\n\t\t\tRIGHT\n\t\t</section>\n\n\t\t<!-- PANEL IN LAYOUT -->\n\t\t<section data-type=\"left\">\n\t\t\tLEFT\n\t\t</section>\n\n\t\t<!-- PANEL IN LAYOUT -->\n\t\t<section data-type=\"bottom\">\n\t\t\tBOTTOM\n\t\t</section>\n\n\t</ui-component>\n</div>\n\n<br />\n\n<div style=\"width:500px;height:300px\">\n\t<ui-component name=\"layout\" config=\"parent:parent\" class=\"invisible\">\n\t\t<script type=\"text/plain\">\n\t\t\t{\n\t\t\t\ttop: { size: 80, resize: 1 },\n\t\t\t\tbottom: { size: 100 },\n\t\t\t\tleft: { size: 100 },\n\t\t\t\tright: { size: 100 },\n\t\t\t\tmain: {}\n\t\t\t}\n\t\t</script>\n\n\t\t<section data-type=\"top2\">\n\t\t\tTOP2\n\t\t</section>\n\n\t\t<section data-type=\"main\">\n\t\t\tMAIN\n\t\t</section>\n\n\t\t<section data-type=\"right\">\n\t\t\tRIGHT\n\t\t</section>\n\n\t\t<section data-type=\"left\">\n\t\t\tLEFT\n\t\t</section>\n\n\t\t<section data-type=\"bottom2\">\n\t\t\tBOTTOM2\n\t\t</section>\n\n\t</ui-component>\n</div>\n\n<script>\n\n\tvar mylayout = '';\n\n\tsetInterval(function() {\n\t\tSET('mylayout', mylayout === 'detail' ? '' : 'detail');\n\t}, 5000);\n\n\tsetInterval(function() {\n\t\tSETTER('layout', 'lock', 'right');\n\t}, 8000);\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/layout2","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/layout2/","markdown_url":"https://forge.totaljs.es/en/components/layout2.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"fcdc96d3b3a0ff56ddb986be88d605c274ae631a44d3b83a6e4aeab8ede94c91","checked_at":"2026-10-20T12:22:07.545Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Layout2","description":"Making of layout is very easy.","messages":[{"role":"user","content":"j-Layout2\n\nMaking of layout is very easy."},{"role":"assistant","content":"- This component is a much simple component compared to `j-Layout`.\n\n\n__Configuration__:\n\n- `parent` {String} can contain `parent`, `window` or jQuery selector for the `closest` method (default: `window`)\n- `margin` {Number} adds margin for the `height` (default: `0`)\n- `autoresize` {Boolean} enables auto-resize (default: `true`)\n\n__Methods__:\n\n- __IMPORTANT__ `component.show(type)` can show a specific panel for mobile devices, `type` can be `left`, `right` or `main`\n- `component.resizescrollbar(panel_type)` resizes a scrollbar for a specific panel\n- `component.scrolltop(panel_type)` scrolls to top for a specific panel\n\n__Panel types__:\n\n- `left` left panel\n- `right` right panel\n- `top` top panel (it's panel on top of all panels)\n- `top2` top panel and uses `top` key in the config (it is between `left` and `right` panel)\n- `bottom` bottom panel (it's panel on bottom of all panels)\n- `bottom2` bottom panel and uses `bottom` key in the config (it is between `left` and `right` panel)\n- `main` main content\n\n__Layout__:\n\nMaking of layout is very easy.\n\n```html\n<div data-type=\"\" data-size=\"\" data-scrollbar=\"\"></div>\n```\n\n__Attributes__:\n\n- `data-type` must contain a type of section `top`, `top2` ,`left`, `right`, `bottom`, `bottom2` or `main`\n- `data-size` contains a size of section `LG,MD,SM,XS` --> in pixels or percentage, example: `200,200,200,0` (`0` means hidden) or `20%` (all values will inherit `20%`)\n- `data-scrollbar=\"margin:50;visible:1\"` enables scrollbar, possible values:\n\t- `selector` {String} optional, jQuery selector for scrollbar container\n\t- `margin` {Number} scrollbar margin (default: `0`)\n\t- `visible` {Boolean} shows the scrollbar\n\n__Good to know__:\n\n- each panel is `section` element and must contain `data-type=\"top\"` attribute with type of panel\n- define panels only which you will use\n- ordering of all `section` elements isn't important\n\nUse it with `<ui-component name=\"layout2\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"layout2\" config=\"parent:window\" class=\"invisible\">\n\t<div data-type=\"top\" data-size=\"44\" style=\"background-color:red\">\n\t\tTOP\n\t</div>\n\t<div data-type=\"bottom2\" data-size=\"44\" style=\"background-color:blue\">\n\t\tBOTTOM\n\t</div>\n\t<div data-type=\"left\" data-size=\"20%,20%,20%,0\" style=\"background-color:green\">\n\t\tLEFT\n\t</div>\n\t<div data-type=\"right\" data-size=\"20%,20%,20%,0\" style=\"background-color:purple\">\n\t\tRIGHT\n\t</div>\n\t<div data-type=\"main\" data-scrollbar=\"visible:true\" style=\"background-color:#F0F0F0\">\n\t</div>\n</ui-component>\n```"}],"knowledge":{"content_sha256":"d51912a9c58b3d7d7c691010eaea64c83660e1a64d2d53f50df11e7339d3b6ca","source_sha256":"fcdc96d3b3a0ff56ddb986be88d605c274ae631a44d3b83a6e4aeab8ede94c91","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["BOTTOM","CDN","DEF","F0F0F0","LEFT","MIT","RIGHT","TOP"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"layout2\" config=\"parent:window\" class=\"invisible\">\n\t<div data-type=\"top\" data-size=\"44\" style=\"background-color:red\">\n\t\tTOP\n\t</div>\n\t<div data-type=\"bottom2\" data-size=\"44\" style=\"background-color:blue\">\n\t\tBOTTOM\n\t</div>\n\t<div data-type=\"left\" data-size=\"20%,20%,20%,0\" style=\"background-color:green\">\n\t\tLEFT\n\t</div>\n\t<div data-type=\"right\" data-size=\"20%,20%,20%,0\" style=\"background-color:purple\">\n\t\tRIGHT\n\t</div>\n\t<div data-type=\"main\" data-scrollbar=\"visible:true\" style=\"background-color:#F0F0F0\">\n\t</div>\n</ui-component>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/lazycontent","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/lazycontent/","markdown_url":"https://forge.totaljs.es/en/components/lazycontent.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"7996f790378afc96e688670353c97e2003edc7bfcf7ca2add8484d9f2655fbe0","checked_at":"2026-10-20T12:22:08.288Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-LazyContent","description":"This component shows own content when it's visible. It supports helpful features as redraw and hide.","messages":[{"role":"user","content":"j-LazyContent\n\nThis component shows own content when it's visible. It supports helpful features as redraw and hide."},{"role":"assistant","content":"This component shows own content when it's visible. It supports helpful features as `redraw` and `hide`.\n\n__Configuration__:\n- `height:NUMBER_IN_PIXELS\"` optional, a default content height. __Default value__: is height of the current element\n- `init:method_or_variable\"` is executed when the element first time visible is (optional)\n- `redraw:method_or_variable\"` is executed when the element again visible is (optional)\n- `hide:method_or_variable\"` is executed when the element hidden is (optional)\n\n__Notes__:\n- `init` will be executed only one time\n- `redraw` and `hide` will be executed more times\n- if the attributes contains link to `variable` (not methods) then as value is stored `Boolean` value.\n- __important__: scroll area must be `document.body` element\n\nUse it with `<ui-component name=\"lazycontent\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div><b>SCROLL DOWN</b></div>\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<ui-component name=\"lazycontent\" config=\"init:method_init;redraw:method_redraw;hide:method_hide\">\n\t.... default content ....\n</ui-component>\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<ui-component name=\"lazycontent\" config=\"init:method_init;redraw:method_redraw;hide:method_hide\">\n\t.... default content ....\n</ui-component>\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n\n<script>\n\tfunction method_init() {\n\t\tthis.element.html('INIT');\n\t\tthis.element.css('background-color', 'green');\n\t}\n\n\tfunction method_redraw() {\n\t\tthis.element.html('REDRAW');\n\t\tthis.element.css('background-color', 'blue');\n\t}\n\n\tfunction method_hide() {\n\t\tthis.element.html('HIDDEN');\n\t\tthis.element.css('background-color', 'red');\n\t}\n</script>\n```"}],"knowledge":{"content_sha256":"34629eb3b93d9f18a8dbfd1ecc34fbb405710be49d6cb154fc9c5d4fabf81f8f","source_sha256":"7996f790378afc96e688670353c97e2003edc7bfcf7ca2add8484d9f2655fbe0","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","DOWN","HIDDEN","INIT","MIT","NUMBER_IN_PIXELS","REDRAW","SCROLL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div><b>SCROLL DOWN</b></div>\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<ui-component name=\"lazycontent\" config=\"init:method_init;redraw:method_redraw;hide:method_hide\">\n\t.... default content ....\n</ui-component>\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n<ui-component name=\"lazycontent\" config=\"init:method_init;redraw:method_redraw;hide:method_hide\">\n\t.... default content ....\n</ui-component>\n<br /><br /><br /><br /><br /><br />\n<br /><br /><br /><br /><br /><br />\n\n<script>\n\tfunction method_init() {\n\t\tthis.element.html('INIT');\n\t\tthis.element.css('background-color', 'green');\n\t}\n\n\tfunction method_redraw() {\n\t\tthis.element.html('REDRAW');\n\t\tthis.element.css('background-color', 'blue');\n\t}\n\n\tfunction method_hide() {\n\t\tthis.element.html('HIDDEN');\n\t\tthis.element.css('background-color', 'red');\n\t}\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/lazyimages","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/lazyimages/","markdown_url":"https://forge.totaljs.es/en/components/lazyimages.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"4ddde3ff77ab6555b6a17005b1d463eabbb60da83f142288713b4aa8a8e68305","checked_at":"2026-10-20T12:22:09.174Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-LazyImages","description":"The component tries to find all <img elements with data-src='' attribute and if the element is visible then the component will update src='' attribute with the value from data-src='' attribute.","messages":[{"role":"user","content":"j-LazyImages\n\nThe component tries to find all <img elements with data-src='' attribute and if the element is visible then the component will update src='' attribute with the value from data-src='' attribute."},{"role":"assistant","content":"__Good to know__:\n\nThe component tries to find all `<img` elements with `data-src=\"\"` attribute and if the element is visible then the component will update `src=\"\"` attribute with the value from `data-src=\"\"` attribute.\n\n__Configuration__:\n\n- `type {String}` a type (`position` or `offset`) for obtaining of position (default: `position`)\n- `offsetter {String}` optional, a parent selector for obtaining of position\n- `customscrollbar {Boolean}` have you used a custom scrollbar? (default: `false`)\n\nUse it with `<ui-component name=\"lazyimages\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"lazyimages\">\n\n\t<div>SCROLL DOWN</div>\n\t<div style=\"height:1000px\"></div>\n\n\t<!-- EMPTY GIF -->\n\t<!-- YOU CAN USE ENCODED SVG WITH PREDDEFINED SIZE -->\n\t<img src=\"data:image/gif;base64,R0lGODlhAQABAPAAAAAAAAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==\" data-src=\"https://cdn.pixabay.com/photo/2019/08/26/01/13/pacific-4430530_1280.jpg\" />\n\n</ui-component>\n```"}],"knowledge":{"content_sha256":"977ab4051ff8aaffd743570a30b645bab9c368982590b0a74f9ec2414fb2a6f3","source_sha256":"4ddde3ff77ab6555b6a17005b1d463eabbb60da83f142288713b4aa8a8e68305","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CAN","CDN","DEF","DOWN","EMPTY","ENCODED","GIF","MIT","PREDDEFINED","SCROLL","SIZE","SVG","USE","WITH","YOU"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"lazyimages\">\n\n\t<div>SCROLL DOWN</div>\n\t<div style=\"height:1000px\"></div>\n\n\t<!-- EMPTY GIF -->\n\t<!-- YOU CAN USE ENCODED SVG WITH PREDDEFINED SIZE -->\n\t<img src=\"data:image/gif;base64,R0lGODlhAQABAPAAAAAAAAAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==\" data-src=\"https://cdn.pixabay.com/photo/2019/08/26/01/13/pacific-4430530_1280.jpg\" />\n\n</ui-component>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/lazyload","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/lazyload/","markdown_url":"https://forge.totaljs.es/en/components/lazyload.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"9ccad4deec227fa5a741bd74fda088b5f135151076be804ee3fc94bd5bb9f303","checked_at":"2026-10-20T12:22:09.971Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-LazyLoad","description":"j-LazyLoad: easy usage; can be used for everything","messages":[{"role":"user","content":"j-LazyLoad\n\nj-LazyLoad: easy usage; can be used for everything"},{"role":"assistant","content":"- can be used for everything\n- vertically position only\n\n\n__Configuration__:\n\n- `container {String}` a selector as a scrolling container (default: `window`)\n- `selector {String}` (__important__) selector for lazy elements\n- `exec {String}` a link to method, executed if the lazy element is visible\n- `offset {Number}` this value extends the size of scrolling area top/bottom. (default: `50`)\n\n__Methods__:\n- `instance.refresh()` refreshes current state\n\nUse it with `<ui-component name=\"lazyload\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"lazyload\" config=\"selector:.lazyload;exec:lazyload\" style=\"height:1000px\">\n\t<div>SCROLL DOWN</div>\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<div class=\"lazyload\"><img src=\"https://componentator.com/img/preloader.gif\" data-src=\"https://componentator.com/img/logo.png\" /></div>\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n</ui-component>\n\n<script>\n\tfunction lazyload(el) {\n\t\tvar img = el.find('img');\n\t\timg.attr('src', img.attr('data-src'));\n\t}\n</script>\n```"}],"knowledge":{"content_sha256":"2191ce2af9166b31ea507bb7f4e814004eec086e56520cf68decaaec6eb04217","source_sha256":"9ccad4deec227fa5a741bd74fda088b5f135151076be804ee3fc94bd5bb9f303","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","DOWN","MIT","SCROLL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"lazyload\" config=\"selector:.lazyload;exec:lazyload\" style=\"height:1000px\">\n\t<div>SCROLL DOWN</div>\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<div class=\"lazyload\"><img src=\"https://componentator.com/img/preloader.gif\" data-src=\"https://componentator.com/img/logo.png\" /></div>\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n\t<br />\n</ui-component>\n\n<script>\n\tfunction lazyload(el) {\n\t\tvar img = el.find('img');\n\t\timg.attr('src', img.attr('data-src'));\n\t}\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/leaflet","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/leaflet/","markdown_url":"https://forge.totaljs.es/en/components/leaflet.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"0b11e416d8489d42289efc7d03ff7af26ce87b0203071fd48e70c5d9ca60d871","checked_at":"2026-10-20T12:22:11.317Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Leaflet","description":"This component uses [Leaflet](https://leafletjs.com/) library with the [OpenStreetMap](https://www.openstreetmap.org/) map engine. The component uses 3rd party dependencies.","messages":[{"role":"user","content":"j-Leaflet\n\nThis component uses [Leaflet](https://leafletjs.com/) library with the [OpenStreetMap](https://www.openstreetmap.org/) map engine. The component uses 3rd party dependencies."},{"role":"assistant","content":"This component uses [Leaflet](https://leafletjs.com/) library with the [OpenStreetMap](https://www.openstreetmap.org/) map engine. The component uses 3rd party dependencies.\n\n\n__Configuration__:\n\n- `height {Number/String}` fixed height or a parent element (default: `200`)\n- `margin {Number}` a margin for parent element (default: `0`)\n- `marker {Boolean}` enables marker (default: `true`)\n- `draggable {Boolean}` enables draggable marker (default: `false`)\n- `zoom {Number}` a default zoom level (default: `10`)\n- `maxzoom {Number}` a maximum zoom level (default: `19`)\n- `icon {String}` a URL to the default marker icon\n- `iconanchor {String}` optional, icon anchor in the for `x,y` (default: `undefined`)\n- `iconsize {String}` optional, icon size in the for `w,h` (default: `undefined`)\n- `move {String}` move event - a link to the function or variable `function(pos)`\n- `click {String}` a marker click event - a link to the function or variable `function(pos)`\n- `center {String}` a default location (e.g. `Bratislava, Slovakia`) if the `setter` value is empty/nullable\n- __NEW__: `exec {String}` optional, a link to the `function(value, component)` (it will be executed when the value is changed)\n\n__Methods__:\n\n- `component.search(location, callback)` searchs locality in the OpenStreetMap API (response is array)\n- `component.parse(pos)` returns parsed GPS to array `[lat, lon, zoom]`\n- `component.center(pos)` centers map to the specific position\n\nUse it with `<ui-component name=\"leaflet\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"width:400px\">\n\t<ui-component name=\"leaflet\" path=\"totalgps\" config=\"height:200\"></ui-component>\n</div>\n\n<script>\n\tvar totalgps = '48.7357552902333, 19.1465583527431';\n</script>\n```"}],"knowledge":{"content_sha256":"ba295cb1b566c08425730ecbecbab7f260aaac3d696ee7379be4d5b82963f931","source_sha256":"0b11e416d8489d42289efc7d03ff7af26ce87b0203071fd48e70c5d9ca60d871","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["API","CDN","DEF","GPS","MIT","URL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"width:400px\">\n\t<ui-component name=\"leaflet\" path=\"totalgps\" config=\"height:200\"></ui-component>\n</div>\n\n<script>\n\tvar totalgps = '48.7357552902333, 19.1465583527431';\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/linechart","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/linechart/","markdown_url":"https://forge.totaljs.es/en/components/linechart.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"2fb223c6ebb62b83f075b5e2c522e33a4da48756964919794c782200351d960c","checked_at":"2026-10-20T12:22:12.049Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-LineChart","description":"Please review an example and keep a data-source.","messages":[{"role":"user","content":"j-LineChart\n\nPlease review an example and keep a data-source."},{"role":"assistant","content":"Please review an example and keep a data-source.\n\n\n__Configuration__:\n\n- `offsetX {Number}` An offset X for value tooltip (default: `10`)\n- `offsetY {Number}` An offset Y for value tooltip (default: `10`)\n- `templateX {String}` Tangular template for X axis (default: `{{ value }}`)\n- `templateY {String}` Tangular template for Y axis (default: `{{ value | format(0) }}`)\n- `selected {String}` Tangular template for selected values (default: `{{ value | format(0) }}`)\n- `height {Number/String}` height (default: `0` = auto-height) or `string` is meant as a parent element\n- `width {Number/String}` width (default: `0` = auto-width) or `string` is meant as a parent element\n- `limit {Number}` a maximum limit of value for Y axis (default: `0`)\n- `fill {Boolean}` enables filling (default: `false`)\n- `fillopacity {Number}` filling opacity (default: `0.1`)\n- `point {Number}` size of `point` (default: `5`)\n- `pl {Number}` lines left padding (default: `10`)\n- `pr {Number}` lines right padding (default: `10`)\n- `pt {Number}` lines top padding (default: `10`)\n- `pb {Number}` lines bottom padding (default: `25`)\n- `type {String}` lines type, can be `normal`, `curves` or `step` (default: `normal`)\n- `prselected {Number}` right padding for selected value (default: `0`)\n- `avg {Number}` average value (chart renders a new black line)\n- `xmod {Number}` performs `modulo` for points on x-axis (default: `0`)\n- `yaxis {nubmer}` a count of y-axis points (default: `4`)\n- `exec {String}` a path to function and it's executed after some point is hovered\n- `marginW {Number}` a margin for `width`\n- `marginH {Number}` a margin for `height`\n\n__Good to know__:\n\nThe `templateX`, `templateY` and `selected` config options can be defined without the use of the `{{` and `}}` brackets. The component appends them automatically.\n\nUse it with `<ui-component name=\"linechart\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div class=\"container\">\n\t<div class=\"row\">\n\t\t<div class=\"col-sm-6\">\n\t\t\t<ui-component name=\"linechart\" path=\"values\"></ui-component>\n\t\t\t<br />\n\t\t</div>\n\t\t<div class=\"col-sm-6\">\n\t\t\t<ui-component name=\"linechart\" path=\"values\" config=\"fill:true\"></ui-component>\n\t\t</div>\n\t</div>\n</div>\n\n<script>\n\tvar values = [];\n\n\tvalues.push({\n\t\tname: 'Apples',\n\t\tvalues: [{ x: 2009, y: 100 }, { x: 2010, y: 120 }, { x: 2011, y: 130 }, { x: 2012, y: 150 }, { x: 2013, y: 50 }, { x: 2014, y: 200 }, { x: 2015, y: 80 }, { x: 2016, y: 170 }, { x: 2017, y: 300 }]\n\t});\n\n\tvalues.push({\n\t\tname: 'Oranges',\n\t\tvalues: [{ x: 2009, y: 40 }, { x: 2010, y: 80 }, { x: 2011, y: 400 }, { x: 2012, y: 170 }, { x: 2013, y: 90 }, { x: 2014, y: 60 }, { x: 2015, y: 160 }, { x: 2016, y: 120 }, { x: 2017, y: 180 }]\n\t});\n\n\tvalues.push({\n\t\tname: 'Bananas',\n\t\tvalues: [{ x: 2009, y: 80 }, { x: 2010, y: 40 }, { x: 2011, y: 300 }, { x: 2012, y: 200 }, { x: 2013, y: 120 }, { x: 2014, y: 150 }, { x: 2015, y: 220 }, { x: 2016, y: 40 }, { x: 2017, y: 80 }]\n\t});\n</script>\n```"}],"knowledge":{"content_sha256":"31f047d7d02801f2c38a11680386adf301c0ab8bbf412983c3612c499f81ae2e","source_sha256":"2fb223c6ebb62b83f075b5e2c522e33a4da48756964919794c782200351d960c","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div class=\"container\">\n\t<div class=\"row\">\n\t\t<div class=\"col-sm-6\">\n\t\t\t<ui-component name=\"linechart\" path=\"values\"></ui-component>\n\t\t\t<br />\n\t\t</div>\n\t\t<div class=\"col-sm-6\">\n\t\t\t<ui-component name=\"linechart\" path=\"values\" config=\"fill:true\"></ui-component>\n\t\t</div>\n\t</div>\n</div>\n\n<script>\n\tvar values = [];\n\n\tvalues.push({\n\t\tname: 'Apples',\n\t\tvalues: [{ x: 2009, y: 100 }, { x: 2010, y: 120 }, { x: 2011, y: 130 }, { x: 2012, y: 150 }, { x: 2013, y: 50 }, { x: 2014, y: 200 }, { x: 2015, y: 80 }, { x: 2016, y: 170 }, { x: 2017, y: 300 }]\n\t});\n\n\tvalues.push({\n\t\tname: 'Oranges',\n\t\tvalues: [{ x: 2009, y: 40 }, { x: 2010, y: 80 }, { x: 2011, y: 400 }, { x: 2012, y: 170 }, { x: 2013, y: 90 }, { x: 2014, y: 60 }, { x: 2015, y: 160 }, { x: 2016, y: 120 }, { x: 2017, y: 180 }]\n\t});\n\n\tvalues.push({\n\t\tname: 'Bananas',\n\t\tvalues: [{ x: 2009, y: 80 }, { x: 2010, y: 40 }, { x: 2011, y: 300 }, { x: 2012, y: 200 }, { x: 2013, y: 120 }, { x: 2014, y: 150 }, { x: 2015, y: 220 }, { x: 2016, y: 40 }, { x: 2017, y: 80 }]\n\t});\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/listbox","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/listbox/","markdown_url":"https://forge.totaljs.es/en/components/listbox.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"47cbe4f8777d7c4bdde39628a33b3d4741bd6be39117395ab1f13ed76032ea33","checked_at":"2026-10-20T12:22:12.933Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-ListBox","description":"j-ListBox: NEW dark mode; Works with Bootstrap","messages":[{"role":"user","content":"j-ListBox\n\nj-ListBox: NEW dark mode; Works with Bootstrap"},{"role":"assistant","content":"- __NEW__ dark mode\n- Works with Bootstrap\n\n__Configuration__:\n\n- `required` {Boolean} enables `required` (default: __false__)\n- `items` {String} static items `key1|value1, key2 with value, key3|value`\n- `type` {String} can be `number` (converts `String` values to `Number`)\n- `datasource` {String} path to data-source (must be array)\n- `text` {String} determines a property name for text (in data-source), default: `name`\n- `value` {String} determines a property name for value (in data-source) default: `id`\n- `empty` {String} adds an empty field\n- `disabled` {Boolean} disables this component\n- `search` {String} enables search box with this value as a placeholder\n- `height` {Number/String} height (default: `200`), `string` can contain `parent`, `window` or `.parent_selector`\n- `multiple` {Boolean} `true` expects `Array` in `path` (default: `false`)\n- `exec` {String} a link to function which is executed when the user click on the item\n\nUse it with `<ui-component name=\"listbox\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"width:400px\">\n\t<ui-component name=\"listbox\" path=\"items\" config=\"search:Search;required:true;items:Blue|1,Red|2,Green|3,Purple|4,Orange|5,Rose|6,Gray|7,Black|8;type:number\"></ui-component>\n</div>\n\n<script>\n\tvar items = 3;\n</script>\n```"}],"knowledge":{"content_sha256":"25ccccc15a7a4d7d78222d4f25585f2056b354d28ffb983c30cc06b65a49b012","source_sha256":"47cbe4f8777d7c4bdde39628a33b3d4741bd6be39117395ab1f13ed76032ea33","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"width:400px\">\n\t<ui-component name=\"listbox\" path=\"items\" config=\"search:Search;required:true;items:Blue|1,Red|2,Green|3,Purple|4,Orange|5,Rose|6,Gray|7,Black|8;type:number\"></ui-component>\n</div>\n\n<script>\n\tvar items = 3;\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/listdetail","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/listdetail/","markdown_url":"https://forge.totaljs.es/en/components/listdetail.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"3ca0d6437afb1c7d1595259564a8e4ae93be8ecd35350fb5bd4a5b4c03f25c5f","checked_at":"2026-10-20T12:22:13.827Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-ListDetail","description":"The component divides the content for listing part and detail part. Displaying of detail part depends on the path {Boolean}.","messages":[{"role":"user","content":"j-ListDetail\n\nThe component divides the content for listing part and detail part. Displaying of detail part depends on the path {Boolean}."},{"role":"assistant","content":"The component divides the content for listing part and detail part. Displaying of detail part depends on the `path {Boolean}`.\n\n\n__Configuration__:\n\n- `parent {String}` parent element for obtaining of width/height (default: `auto`)\n- `margin {Number}` a margin for height (default: `0`)\n- `width {Number/String}` a detail panel width (default: `35%`)\n- `minwidth {Number/String}` a minimal width of the detail panel (default: `20%`)\n- `maxwidth {Number/String}` a maximal width of the detail panel (default: `50%`)\n- `resize {Boolean}` enables/disables detail panel resizing (default: `true`)\n- __NEW__: `position {String}` split orientation: `vertical` (side-by-side) or `horizontal` (stacked) (default: `vertical`)\n- __NEW__: `height {Number/String}` a detail panel height (only for `position:horizontal`; when set, it overrides `width` for sizing the detail panel) (default: `null`)\n- __NEW__: `minheight {Number/String}` a minimal height of the detail panel (only for `position:horizontal` + `height`) (default: `null`)\n- __NEW__: `maxheight {Number/String}` a maximal height of the detail panel (only for `position:horizontal` + `height`) (default: `null`)\n\nParts: list + detail must be wrapped to independent `<section>` element, e.g.\n\n```html\n<ui-component name=\"listdetail__...\">\n\t<section>\n\t\tLIST\n\t</section>\n\t<section>\n\t\tDETAIL\n\t</section>\n</ui-component>\n```\n\nUse it with `<ui-component name=\"listdetail\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\thtml,body { margin: 0; padding: 0; }\n</style>\n\n<ui-component name=\"listdetail\" path=\"showdetail\" config=\"parent:window;position:vertical\" class=\"invisible\">\n\n\t<section>\n\t\tLIST\n\t</section>\n\n\t<section>\n\t\tDETAIL\n\t</section>\n\n</ui-component>\n\n<script>\n\tvar showdetail = true;\n</script>\n```"}],"knowledge":{"content_sha256":"4d0ae09df904a052289e4784c5f84ff7c7495ddc5bf0a0c30307b1c6d58c9493","source_sha256":"3ca0d6437afb1c7d1595259564a8e4ae93be8ecd35350fb5bd4a5b4c03f25c5f","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","DETAIL","LIST","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\thtml,body { margin: 0; padding: 0; }\n</style>\n\n<ui-component name=\"listdetail\" path=\"showdetail\" config=\"parent:window;position:vertical\" class=\"invisible\">\n\n\t<section>\n\t\tLIST\n\t</section>\n\n\t<section>\n\t\tDETAIL\n\t</section>\n\n</ui-component>\n\n<script>\n\tvar showdetail = true;\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/listform","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/listform/","markdown_url":"https://forge.totaljs.es/en/components/listform.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"a9e9118069ee0578a060017d4ac8ba27a9092f2fed087d77df337c190149ee26","checked_at":"2026-10-20T12:22:14.569Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-ListForm","description":"j-ListForm: NEW: delay {Number} initial delay for displaying detail (default: 1000 ms); NEW: component.add() opens a form for creating record","messages":[{"role":"user","content":"j-ListForm\n\nj-ListForm: NEW: delay {Number} initial delay for displaying detail (default: 1000 ms); NEW: component.add() opens a form for creating record"},{"role":"assistant","content":"__Configuration__:\n\n- `autofocus {String/Boolean}` focuses the first input element, `string` means a custom selector (default: `false`)\n- `empty {String}` empty list text (default: `---`)\n- `default {Boolean}` performs `DEFAULT()` method for the form scope (default: `true`)\n- `required {Boolean}` controll will be \"required\" (default: `false`)\n- `enter {Boolean}` allows to use `enter` as submit (default: `false`)\n- `disabled {Boolean}` disables the control (default: `false`)\n- `formclass {String}` a custom class for form container\n- `itemclass {String}` a custom class for item container\n- `create {String}` a link to a function `function(empty_object, callback(DEFAULT_ITEM_OBJECT), arr)` (`null` value skips creating)\n- `submit {String}` a link to a function `function(form_data, callback(NEW_DATA), arr, item_ref_in_items)` (`null` value skips updating)\n- `update {String}` a link to a function `function(form_data, callback(NEW_DATA), arr, item_ref_in_items)` (`null` value skips updating)\n- `remove {String}` a link to a function `function(remove_item, callback(really_remove?), arr)`\n- `selector {String}` a jQuery selector for custom template (outside of the component). Works like `custom template` in [data-binding](https://wiki.totaljs.com/jcomponent/08-data-binding/)\n- `footertop {Boolean}` moves the footer on the top of the component (default: `false`)\n- `move {String}` a link to a function `function(items)`\n- `newbie {String}` a property name for every new item (it will always contain `true` value)\n- `plugin {String}` a custom plugin name inside the component (default: `component.ID`)\n- `invalidform {Boolean}` disables `j-Validate` component if the `j-ListForm` is in editing mode (default: `false`)\n- __NEW__: `delay {Number}` initial delay for displaying detail (default: `1000` ms)\n\nThe component needs to have defined 3 templates wrapped in `<script type=\"text/html\">`:\n\n- `1` first - with a Tangular template for rendering of item\n- `2` second - a form with components (the form will be wrapped in isolated scope)\n- `3` footer - a footer with button for `create` item\n\n__Good to know:__\n\nThe component watches a click event on all buttons with `name` attribute:\n\n- `name=\"submit\"` performs submit/save of data to the model\n- `name=\"update\"` performs submit/save of data to the model\n- `name=\"remove\"` removes the current item from the model\n- `name=\"cancel\"` cancels editing\n- `name=\"create\"` creates an empty form\n- `form` contains `ui-listform-new` class when creating a new item\n\n__Listing buttons:__\n\n- `name=\"remove\"` removes item\n- `name=\"up\"` moves item to up\n- `name=\"down\"` moves item to down\n\n__Methods:__\n\n- __NEW__: `component.add()` opens a form for creating record\n\nUse it with `<ui-component name=\"listform\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\t.ui-listform-item { height: 26px; line-height: 26px; overflow: hidden; padding: 0 5px; }\n\tbutton { background-color: white; border-radius: 2px; border: 1px solid #E0E0E0; font-size: 12px; height: 28px; line-height: 27px; padding: 0 10px; }\n\tbutton i { margin-right: 5px; }\n\tbutton:disabled { color: gray; background-color: #F8F8F8; }\n\t.ui-dark button { background-color: #303030; border-color: #454545; }\n\t.ui-dark button:disabled { background-color: #252525; color: #404040; }\n</style>\n\n<div style=\"width:400px\">\n\n\t<ui-component name=\"listform\" path=\"users\" config=\"autofocus:1\">\n\n\t\t<!-- LIST RENDERING -->\n\t\t<script type=\"text/html\">\n\t\t\t<div>\n\t\t\t\t<div class=\"pull-right\">\n\t\t\t\t\t<button name=\"up\"><i class=\"ti ti-arrow-up\"></i></button>\n\t\t\t\t\t<button name=\"down\"><i class=\"ti ti-arrow-down\"></i></button>\n\t\t\t\t\t<button name=\"remove\"><i class=\"ti ti-remove\"></i></button>\n\t\t\t\t</div>\n\t\t\t\t{{ name }}\n\t\t\t</div>\n\t\t</script>\n\n\t\t<!-- FORM RENDERING -->\n\t\t<script type=\"text/html\">\n\t\t\t<div class=\"padding\">\n\t\t\t\t<ui-component name=\"input\" path=\"?.name\" config=\"required:1\" default=\"''\" class=\"m\">Name</ui-component>\n\t\t\t\t<ui-component name=\"validate\" path=\"?\">\n\t\t\t\t\t<button name=\"submit\"><i class=\"ti ti-save\"></i>Save</button>\n\t\t\t\t\t<button name=\"remove\">Remove</button>\n\t\t\t\t\t<button name=\"cancel\">Cancel</button>\n\t\t\t\t</ui-component>\n\t\t\t</div>\n\t\t</script>\n\n\t\t<!-- FOOTER -->\n\n\t\t<script type=\"text/html\">\n\t\t\t<div style=\"margin-top:10px\"><button name=\"create\"><i class=\"ti ti-plus-circle green\"></i>Create new</button></div>\n\t\t</script>\n\n\t</ui-component>\n\t<hr />\n\t<ui-bind path=\"users\" config=\"text:STRINGIFY(value)\" style=\"font-family:monospace\" class=\"block\"></ui-bind>\n\n</div>\n\n<script>\n\n\tvar users = [{ name: 'Total.js' }, { name: 'Flow' }, { name: 'jComponent' }];\n\n</script>\n```"}],"knowledge":{"content_sha256":"643a9afb1e999c23004c60d0e861d476857792aabf44e854599de9b65ed9561a","source_sha256":"a9e9118069ee0578a060017d4ac8ba27a9092f2fed087d77df337c190149ee26","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","DEFAULT","DEFAULT_ITEM_OBJECT","E0E0E0","F8F8F8","FOOTER","FORM","LIST","MIT","NEW_DATA","RENDERING","STRINGIFY","Total.js"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\t.ui-listform-item { height: 26px; line-height: 26px; overflow: hidden; padding: 0 5px; }\n\tbutton { background-color: white; border-radius: 2px; border: 1px solid #E0E0E0; font-size: 12px; height: 28px; line-height: 27px; padding: 0 10px; }\n\tbutton i { margin-right: 5px; }\n\tbutton:disabled { color: gray; background-color: #F8F8F8; }\n\t.ui-dark button { background-color: #303030; border-color: #454545; }\n\t.ui-dark button:disabled { background-color: #252525; color: #404040; }\n</style>\n\n<div style=\"width:400px\">\n\n\t<ui-component name=\"listform\" path=\"users\" config=\"autofocus:1\">\n\n\t\t<!-- LIST RENDERING -->\n\t\t<script type=\"text/html\">\n\t\t\t<div>\n\t\t\t\t<div class=\"pull-right\">\n\t\t\t\t\t<button name=\"up\"><i class=\"ti ti-arrow-up\"></i></button>\n\t\t\t\t\t<button name=\"down\"><i class=\"ti ti-arrow-down\"></i></button>\n\t\t\t\t\t<button name=\"remove\"><i class=\"ti ti-remove\"></i></button>\n\t\t\t\t</div>\n\t\t\t\t{{ name }}\n\t\t\t</div>\n\t\t</script>\n\n\t\t<!-- FORM RENDERING -->\n\t\t<script type=\"text/html\">\n\t\t\t<div class=\"padding\">\n\t\t\t\t<ui-component name=\"input\" path=\"?.name\" config=\"required:1\" default=\"''\" class=\"m\">Name</ui-component>\n\t\t\t\t<ui-component name=\"validate\" path=\"?\">\n\t\t\t\t\t<button name=\"submit\"><i class=\"ti ti-save\"></i>Save</button>\n\t\t\t\t\t<button name=\"remove\">Remove</button>\n\t\t\t\t\t<button name=\"cancel\">Cancel</button>\n\t\t\t\t</ui-component>\n\t\t\t</div>\n\t\t</script>\n\n\t\t<!-- FOOTER -->\n\n\t\t<script type=\"text/html\">\n\t\t\t<div style=\"margin-top:10px\"><button name=\"create\"><i class=\"ti ti-plus-circle green\"></i>Create new</button></div>\n\t\t</script>\n\n\t</ui-component>\n\t<hr />\n\t<ui-bind path=\"users\" config=\"text:STRINGIFY(value)\" style=\"font-family:monospace\" class=\"block\"></ui-bind>\n\n</div>\n\n<script>\n\n\tvar users = [{ name: 'Total.js' }, { name: 'Flow' }, { name: 'jComponent' }];\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/listing","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/listing/","markdown_url":"https://forge.totaljs.es/en/components/listing.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"64481e6236f9c95fcf629622c32758390b36aecde60463d2ce4eef6e132013f2","checked_at":"2026-10-20T12:22:15.457Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Listing","description":"j-Listing: easy usage; works with the whole Arrays only on client-side","messages":[{"role":"user","content":"j-Listing\n\nj-Listing: easy usage; works with the whole Arrays only on client-side"},{"role":"assistant","content":"- works with the whole Arrays only on client-side\n\n\n__Configuration__:\n\n- `count {Number}` Optional, items per page (default: `20`)\n- `pages {Number}` Optional, max pages in pagination (default: `3` --> half + current page + half)\n- `scrolltop {Boolean}` scrolls top automatically when the user changes a page\n- `parent {String}` jQuery selector for obtaining of height, enables fixed area with custom scrollbars\n- `margin {Number}` a margin for `parent` (default: `0`)\n- `marginxs {Number}` optional, a top/bottom margin together for `xs` screen width\n- `marginsm {Number}` optional, a top/bottom margin together for `sm` screen width\n- `marginmd {Number}` optional, a top/bottom margin together for `md` screen width\n- `marginlg {Number}` optional, a top/bottom margin together for `lg` screen width\n- `pluralizeitems {String}` a pluralization for count of all items, form: `# zero,# one,# two-four,# five and more`\n- `pluralizepages {String}` a pluralization for count of all items, form: `# zero,# one,# two-four,# five and more`\n- __NEW__: `scrollbarshadow {Boolean}` enables scrollbar shadow\n\n__Tangular layout__:\n\n- `body {String}` contains all rendered items, output is __HTML__\n- `page {Number}` current page number\n- `pages {Number}` count of pages\n- `count {Number}` count of items\n\n__Tangular item__:\n\n- `model` contains an object from `Array`\n- second model contains info about data-source `{ index: Number, page: Number, pages: Number, count: Number }`\n\nUse it with `<ui-component name=\"listing\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div class=\"row\">\n\t<div class=\"col-sm-6\">\n\t\t<h2>With layout</h2>\n\t\t<ui-component name=\"listing\" path=\"items\">\n\t\t\t<script type=\"text/html\">\n\t\t\t\t<div class=\"col-xs-4\">\n\t\t\t\t\t<div>Item: {{ name }}</div>\n\t\t\t\t</div>\n\t\t\t</script>\n\t\t\t<script type=\"text/html\">\n\t\t\t\t<div><b>Layout:</b></div>\n\t\t\t\t<br />\n\t\t\t\t<div class=\"row\">{{ body | raw }}</div>\n\t\t\t</script>\n\t\t</ui-component>\n\t\t<br />\n\t</div>\n\n\t<div class=\"col-sm-6\">\n\t\t<h2>Without layout</h2>\n\t\t<ui-component name=\"listing\" path=\"items\" config=\"count:8\">\n\t\t\t<script type=\"text/html\">\n\t\t\t\t<div>\n\t\t\t\t\t<div>Item: {{ name }}</div>\n\t\t\t\t</div>\n\t\t\t</script>\n\t\t</ui-component>\n\t\t<br />\n\t</div>\n</div>\n\n<script>\n\n\tvar items = [];\n\tfor (var i = 0; i < 100; i++)\n\t\titems.push({ name: GUID(10) });\n\n</script>\n```"}],"knowledge":{"content_sha256":"a177ebc6513285b5648c7acfabb183abf0b150a5883b3c6e8a260d9d3a114402","source_sha256":"64481e6236f9c95fcf629622c32758390b36aecde60463d2ce4eef6e132013f2","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","GUID","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div class=\"row\">\n\t<div class=\"col-sm-6\">\n\t\t<h2>With layout</h2>\n\t\t<ui-component name=\"listing\" path=\"items\">\n\t\t\t<script type=\"text/html\">\n\t\t\t\t<div class=\"col-xs-4\">\n\t\t\t\t\t<div>Item: {{ name }}</div>\n\t\t\t\t</div>\n\t\t\t</script>\n\t\t\t<script type=\"text/html\">\n\t\t\t\t<div><b>Layout:</b></div>\n\t\t\t\t<br />\n\t\t\t\t<div class=\"row\">{{ body | raw }}</div>\n\t\t\t</script>\n\t\t</ui-component>\n\t\t<br />\n\t</div>\n\n\t<div class=\"col-sm-6\">\n\t\t<h2>Without layout</h2>\n\t\t<ui-component name=\"listing\" path=\"items\" config=\"count:8\">\n\t\t\t<script type=\"text/html\">\n\t\t\t\t<div>\n\t\t\t\t\t<div>Item: {{ name }}</div>\n\t\t\t\t</div>\n\t\t\t</script>\n\t\t</ui-component>\n\t\t<br />\n\t</div>\n</div>\n\n<script>\n\n\tvar items = [];\n\tfor (var i = 0; i < 100; i++)\n\t\titems.push({ name: GUID(10) });\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/listmenu","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/listmenu/","markdown_url":"https://forge.totaljs.es/en/components/listmenu.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"e39f7711eaf1d369d10a4ec476e19894eef436489f67ba523131522a04201a95","checked_at":"2026-10-20T12:22:16.338Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-ListMenu","description":"A ListMenu component for custom list items. You need to style it.","messages":[{"role":"user","content":"j-ListMenu\n\nA ListMenu component for custom list items. You need to style it."},{"role":"assistant","content":"A ListMenu component for custom list items. You need to style it.\n\n__Configuration__:\n\n- `class` {String} CSS class for selected item (default: `selected`)\n- `selector` {String} jQuery selector for scope of rendered item (default: `a`)\n- `datasource` {String} data-source path (Array)\n- `property` {String} Property name for comparing of values (default: `id`)\n- `click` {Boolean} enables clicking on items (default: `true`)\n\nUse it with `<ui-component name=\"listmenu\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"listmenu\" path=\"listmenu.selected\" config=\"datasource:listmenu.datasource\" class=\"listmenu\">\n\t<script type=\"text/html\">\n\t\t{{ if id && id !== '-' }}\n\t\t\t<a href=\"#\">{{ name }}</a>\n\t\t{{ else }}\n\t\t\t<hr />\n\t\t{{ fi }}\n\t</script>\n</ui-component>\n\n<script>\n\n\tvar listmenu = {};\n\n\t// Default selected item\n\tlistmenu.selected = 4;\n\n\t// Data-Source\n\tlistmenu.datasource = [];\n\tlistmenu.datasource.push({ id: 1, name: 'List Item 1' });\n\tlistmenu.datasource.push({ id: '-' });\n\tlistmenu.datasource.push({ id: 2, name: 'List Item 2' });\n\tlistmenu.datasource.push({ id: 3, name: 'List Item 3' });\n\tlistmenu.datasource.push({ id: 4, name: 'List Item 4' });\n\tlistmenu.datasource.push({ id: 5, name: 'List Item 5' });\n\tlistmenu.datasource.push({});\n\tlistmenu.datasource.push({ id: 6, name: 'List Item 6' });\n\tlistmenu.datasource.push({});\n\tlistmenu.datasource.push({ id: 7, name: 'List Item 7' });\n\tlistmenu.datasource.push({ id: 8, name: 'List Item 8' });\n\tlistmenu.datasource.push({ id: 9, name: 'List Item 9' });\n\tlistmenu.datasource.push({ id: 0, name: 'List Item 0' });\n\n</script>\n```"}],"knowledge":{"content_sha256":"dc8f16e32afa775442856e30e75fd0501619d842616001db609508ddadf1f79c","source_sha256":"e39f7711eaf1d369d10a4ec476e19894eef436489f67ba523131522a04201a95","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","CSS","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"listmenu\" path=\"listmenu.selected\" config=\"datasource:listmenu.datasource\" class=\"listmenu\">\n\t<script type=\"text/html\">\n\t\t{{ if id && id !== '-' }}\n\t\t\t<a href=\"#\">{{ name }}</a>\n\t\t{{ else }}\n\t\t\t<hr />\n\t\t{{ fi }}\n\t</script>\n</ui-component>\n\n<script>\n\n\tvar listmenu = {};\n\n\t// Default selected item\n\tlistmenu.selected = 4;\n\n\t// Data-Source\n\tlistmenu.datasource = [];\n\tlistmenu.datasource.push({ id: 1, name: 'List Item 1' });\n\tlistmenu.datasource.push({ id: '-' });\n\tlistmenu.datasource.push({ id: 2, name: 'List Item 2' });\n\tlistmenu.datasource.push({ id: 3, name: 'List Item 3' });\n\tlistmenu.datasource.push({ id: 4, name: 'List Item 4' });\n\tlistmenu.datasource.push({ id: 5, name: 'List Item 5' });\n\tlistmenu.datasource.push({});\n\tlistmenu.datasource.push({ id: 6, name: 'List Item 6' });\n\tlistmenu.datasource.push({});\n\tlistmenu.datasource.push({ id: 7, name: 'List Item 7' });\n\tlistmenu.datasource.push({ id: 8, name: 'List Item 8' });\n\tlistmenu.datasource.push({ id: 9, name: 'List Item 9' });\n\tlistmenu.datasource.push({ id: 0, name: 'List Item 0' });\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/listmenueditable","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/listmenueditable/","markdown_url":"https://forge.totaljs.es/en/components/listmenueditable.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"1ea29c87483cd625f6f8c90c37b9c0c746cd686a546ec2af00b6d9aac9bec04e","checked_at":"2026-10-20T12:22:17.070Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-ListMenuEditable","description":"Component for editable list menu","messages":[{"role":"user","content":"j-ListMenuEditable\n\nComponent for editable list menu"},{"role":"assistant","content":"Component for editable list menu\n\n- You can change CSS for your needs.\n\n\n__Configuration__:\n\n- `iconremove {String}` optional, change `remove` button icon. It's fontawesome icon without `ti-` part (default: `times`)\n- `defaulticon {String}` optional, change `edit` button icon (default: `pencil-alt`)\n- `addicon {String}` optional, will change `add` button icon (default: `plus-square`)\n- `placeholder {String}` optional, a placeholder for input (default: `Write text and press ENTER`)\n- `class {String}` CSS class for selected item (default: `selected`)\n- `title {String}` optional, will add title on top of list menu (important if you want use `addicon` and `addclick`)\n- `key {String}` optional, a default `key` for `text` value (default: `name`)\n- `click {String}` optional, action after click on item in menu. Path to function(element, index)\n- `addclick {String}` optional, action after click on `add `button. Path to function() executes when user click on `add` icon\n- `editclick {String}` optional, action after click on `edit` button. Path to function(element, index)\n\n__Methods__:\n\n- `component.add(object)` - This function will push object into path (can be null)\n- `component.edit(index)` - Function will enable edit on element\n- `component.remove(index)` - Function will remove the index\n\n__Good to know__:\n\nPath __must be__ array of objects. After click on item in menu\nSupports dynamic evaluation of the content of `<script type=\"text/html\">`. The example below contains a script with HTML and the component evaluates the content when the component is creating (only once).\n\n```\n<ui-component name=\"listmenueditable\" path=\"tags\">\n\t<script type=\"text/html\">\n\t\t{{ if mycolor }}\n\t\t\t<i class=\"ti ti-circle\" style=\"color: {{ mycolor }}\"></i>\n\t\t{{ fi }}\n\t\t{{ name }}\n\t</script>\n</ui-component>\n```\n\n__Adding of custom class__:\n\n```javascript\nvar opt = {};\nopt.items = [];\nopt.push({ name: 'Total.js', classname: 'your_class_name' });\n```\n\n__Toggle editable__:\n\n```javascript\nvar opt = {};\nopt.items = [];\nopt.push({ name: 'Total.js', editable: true });\n```\n\nUse it with `<ui-component name=\"listmenueditable\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Denis Granec. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<!-- EXAMPLE DEPENDENCIES -->\n<ui-component name=\"menu\"></ui-component>\n\n<div style=\"width: 400px;\">\n\t<ui-component name=\"listmenueditable\" path=\"tags\" config=\"title:All tags;editclick:edit;addclick:add;click:click\">\n\t\t<script type=\"text/html\">\n\t\t\t{{ if mycolor }}\n\t\t\t\t<i class=\"ti ti-circle-alt\" style=\"color: {{ mycolor }}\"></i>\n\t\t\t{{ fi }}\n\t\t\t{{ name }}\n\t\t</script>\n\t</ui-component>\n</div>\n\n<script>\n\tvar tags = [{ name: 'Tag 1', mycolor: '#00B9D7', editable: true, classname: 'customclass', mycustomdata: 'ola' }, { name: 'Tag 2', mycolor: '#00A06E', editable: true, mycustomdata: 'ola2' }];\n\n\tfunction click(el, item, index) {\n\t\tconsole.log('clicked index', index, 'item', item);\n\t}\n\n\tfunction edit(el, index, icon) {\n\t\tvar opt = {};\n\t\topt.items = [{ name: 'Edit', id: 'edit' }, { name: 'Remove', icon: 'trash', id: 'remove' }];\n\t\topt.align = 'center';\n\t\topt.element = icon;\n\t\topt.callback = function(selected) {\n\t\t\tswitch (selected.id) {\n\t\t\t\tcase 'edit':\n\t\t\t\t\tSETTER('listmenueditable/edit', index);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'remove':\n\t\t\t\t\tSETTER('listmenueditable/remove', index);\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t};\n\t\tSETTER('menu/show', opt);\n\t}\n\n\tfunction add() {\n\t\tSETTER('listmenueditable/add', { name: 'New tag', mycolor: '#8e44ad', editable: true });\n\t}\n</script>\n```"}],"knowledge":{"content_sha256":"f718ede85688c86b6a32357da3633fd98e015c3316c210f37e303ca6e81e85d1","source_sha256":"1ea29c87483cd625f6f8c90c37b9c0c746cd686a546ec2af00b6d9aac9bec04e","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","CSS","DEF","DEPENDENCIES","ENTER","EXAMPLE","HTML","MIT","SETTER","Total.js"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<!-- EXAMPLE DEPENDENCIES -->\n<ui-component name=\"menu\"></ui-component>\n\n<div style=\"width: 400px;\">\n\t<ui-component name=\"listmenueditable\" path=\"tags\" config=\"title:All tags;editclick:edit;addclick:add;click:click\">\n\t\t<script type=\"text/html\">\n\t\t\t{{ if mycolor }}\n\t\t\t\t<i class=\"ti ti-circle-alt\" style=\"color: {{ mycolor }}\"></i>\n\t\t\t{{ fi }}\n\t\t\t{{ name }}\n\t\t</script>\n\t</ui-component>\n</div>\n\n<script>\n\tvar tags = [{ name: 'Tag 1', mycolor: '#00B9D7', editable: true, classname: 'customclass', mycustomdata: 'ola' }, { name: 'Tag 2', mycolor: '#00A06E', editable: true, mycustomdata: 'ola2' }];\n\n\tfunction click(el, item, index) {\n\t\tconsole.log('clicked index', index, 'item', item);\n\t}\n\n\tfunction edit(el, index, icon) {\n\t\tvar opt = {};\n\t\topt.items = [{ name: 'Edit', id: 'edit' }, { name: 'Remove', icon: 'trash', id: 'remove' }];\n\t\topt.align = 'center';\n\t\topt.element = icon;\n\t\topt.callback = function(selected) {\n\t\t\tswitch (selected.id) {\n\t\t\t\tcase 'edit':\n\t\t\t\t\tSETTER('listmenueditable/edit', index);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'remove':\n\t\t\t\t\tSETTER('listmenueditable/remove', index);\n\t\t\t\t\tbreak;\n\t\t\t}\n\t\t};\n\t\tSETTER('menu/show', opt);\n\t}\n\n\tfunction add() {\n\t\tSETTER('listmenueditable/add', { name: 'New tag', mycolor: '#8e44ad', editable: true });\n\t}\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/livestats","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/livestats/","markdown_url":"https://forge.totaljs.es/en/components/livestats.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"7aa41e93fa9cadfb29a4986b756e1fa4acf804df8f50e57b6acebc7ae0207dea","checked_at":"2026-10-20T12:22:17.951Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-LiveStats","description":"j-LiveStats: supports dark mode; responsive","messages":[{"role":"user","content":"j-LiveStats\n\nj-LiveStats: supports dark mode; responsive"},{"role":"assistant","content":"- supports __dark mode__\n- responsive\n\n\n__Configuration__:\n\n- `width {Number/String}` width of svg, you don't need to change it (default: `500`) or `{String}` jQuery selector for obtaining of height\n- `height {Number/String}` height of svg, you don't need to change it (default: `100`) or `{String}` jQuery selector for obtaining of height\n- `colors {String}` defines custom colors for each key in the form `KEY=COLOR,KEY=COLOR,KEY=COLOR`\n- `max {Number}` a max. limit (default `0` - disabled)\n- `axislines {Number/String}` a count of X axis lines (default: `20`) or `auto` automatically counts count of lines\n- `marginW {Number}` a margin for `width`\n- `marginH {Number}` a margin for `height`\n\nUse it with `<ui-component name=\"livestats\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"max-width:400px\">\n\t<ui-component name=\"livestats\" path=\"data\"></ui-component>\n</div>\n\n<script>\nvar data = {};\n\nsetInterval(function() {\n\tdata.count = Math.random() * 100 >> 0;\n\tdata.users = Math.random() * 100 >> 0;\n\tdata.orders = Math.random() * 100 >> 0;\n\tUPD('data');\n}, 500);\n</script>\n```"}],"knowledge":{"content_sha256":"a50bc5802cfe96762134533f106347e462c061fb237e263d0dc75b7b2732726b","source_sha256":"7aa41e93fa9cadfb29a4986b756e1fa4acf804df8f50e57b6acebc7ae0207dea","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","COLOR","DEF","KEY","MIT","UPD"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"max-width:400px\">\n\t<ui-component name=\"livestats\" path=\"data\"></ui-component>\n</div>\n\n<script>\nvar data = {};\n\nsetInterval(function() {\n\tdata.count = Math.random() * 100 >> 0;\n\tdata.users = Math.random() * 100 >> 0;\n\tdata.orders = Math.random() * 100 >> 0;\n\tUPD('data');\n}, 500);\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/loadcontent","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/loadcontent/","markdown_url":"https://forge.totaljs.es/en/components/loadcontent.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"ec218cf73bdcf0cb0249107ecd65d311ad41f937818545411b8e6139f9be59ad","checked_at":"2026-10-20T12:22:18.682Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-LoadContent","description":"The component loads a content from the template <script type='text/html'> and appends it into the element. It's a great solution for reusable content. Learn from example.","messages":[{"role":"user","content":"j-LoadContent\n\nThe component loads a content from the template <script type='text/html'> and appends it into the element. It's a great solution for reusable content. Learn from example."},{"role":"assistant","content":"The component loads a content from the template `<script type=\"text/html\">` and appends it into the element. It's a great solution for reusable content. Learn from example.\n\n\n__Configuration__:\n\n- `selector {String}` a jQuery selector (it will use it for entire DOM)\n- `wait {Number}` repeats finding of template in entered milliseconds (default: `0`)\n- `replace {String}` replaces entered pharses in the form: `find1=replace1,find2=replace2` (default: empty)\n- `exec {String}` a link to the `function(element)`, it's executed after the template is loaded\n\n__Good to know__:\n\n- all `SCR` pharses will be replaced to the `script` phrase\n\nUse it with `<ui-component name=\"loadcontent\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<script type=\"text/html\" id=\"template\">\n\t<div>AAA PATH CCC</div>\n\t<ui-bind path=\"cities\" config=\"template\" class=\"block\">\n\t\t<SCR type=\"text/html\">\n\t\t\t{{ foreach m in value }}\n\t\t\t\t<div>{{ m }}</div>\n\t\t\t{{ end }}\n\t\t</SCR>\n\t</ui-bind>\n</script>\n\n<ui-component name=\"loadcontent\" config=\"selector:#template;replace:PATH=111\"></ui-component>\n<br />\n<ui-component name=\"loadcontent\" config=\"selector:#template;replace:PATH=222\"></ui-component>\n<br />\n<ui-component name=\"loadcontent\" config=\"selector:#template;replace:PATH=333\"></ui-component>\n<br />\n<ui-component name=\"loadcontent\" config=\"selector:#template;replace:PATH=444\"></ui-component>\n\n<script>\n\tvar cities = ['Bratislava', 'Berlin', 'Barcelona'];\n</script>\n```"}],"knowledge":{"content_sha256":"a3a08f3b2e76e53c8f767eacfb4702d273c17e4fd37620c5c7eb3084b875ef39","source_sha256":"ec218cf73bdcf0cb0249107ecd65d311ad41f937818545411b8e6139f9be59ad","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["AAA","CCC","CDN","DEF","DOM","MIT","PATH","SCR"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<script type=\"text/html\" id=\"template\">\n\t<div>AAA PATH CCC</div>\n\t<ui-bind path=\"cities\" config=\"template\" class=\"block\">\n\t\t<SCR type=\"text/html\">\n\t\t\t{{ foreach m in value }}\n\t\t\t\t<div>{{ m }}</div>\n\t\t\t{{ end }}\n\t\t</SCR>\n\t</ui-bind>\n</script>\n\n<ui-component name=\"loadcontent\" config=\"selector:#template;replace:PATH=111\"></ui-component>\n<br />\n<ui-component name=\"loadcontent\" config=\"selector:#template;replace:PATH=222\"></ui-component>\n<br />\n<ui-component name=\"loadcontent\" config=\"selector:#template;replace:PATH=333\"></ui-component>\n<br />\n<ui-component name=\"loadcontent\" config=\"selector:#template;replace:PATH=444\"></ui-component>\n\n<script>\n\tvar cities = ['Bratislava', 'Berlin', 'Barcelona'];\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/loading","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/loading/","markdown_url":"https://forge.totaljs.es/en/components/loading.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"c30181ad4a2cefbaf27bdb6d0ee181939b9043540670e309e2b27e53d8297b46","checked_at":"2026-10-20T12:22:19.569Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Loading","description":"j-Loading: it automatically assigns flags @showloading and @hideloading; NEW: style {Number} a loading style (default: 1)","messages":[{"role":"user","content":"j-Loading\n\nj-Loading: it automatically assigns flags @showloading and @hideloading; NEW: style {Number} a loading style (default: 1)"},{"role":"assistant","content":"- it automatically assigns flags `@showloading` and `@hideloading`\n\n__Configuration__:\n\n- __NEW__: `style {Number}` a loading style (default: `1`)\n\t- `1`: basic with animated spin\n\t- `2`: a blue line at the top of window\n\n__Usage__:\n\n- `SETTER('loading/show', [text])` - shows loading (`text` optional)\n- `SETTER('loading/hide', [sleep])` - hides loading (`sleep` optional and in milliseconds)\n\nUse it with `<ui-component name=\"loading\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\tbody { background-color: #F0F0F0; }\n</style>\n\n<ui-component name=\"loading\" class=\"hidden\"></ui-component>\n\n<div>Lorem ipsum</div>\n<div>Lorem ipsum</div>\n<div>Lorem ipsum</div>\n<div>Lorem ipsum</div>\n<div>Lorem ipsum</div>\n<div>Lorem ipsum</div>\n<div>Lorem ipsum</div>\n\n<script>\n\tvar is = false;\n\tsetInterval(function() {\n\t\tis = !is;\n\t\tif (is)\n\t\t\tSETTER('loading/show');\n\t\telse\n\t\t\tSETTER('loading/hide', 1000);\n\t}, 3000);\n</script>\n```"}],"knowledge":{"content_sha256":"fca954096b9603e12fa149fac058616bcf1f34e66131ff098dd1a559471551c5","source_sha256":"c30181ad4a2cefbaf27bdb6d0ee181939b9043540670e309e2b27e53d8297b46","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","F0F0F0","MIT","SETTER"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\tbody { background-color: #F0F0F0; }\n</style>\n\n<ui-component name=\"loading\" class=\"hidden\"></ui-component>\n\n<div>Lorem ipsum</div>\n<div>Lorem ipsum</div>\n<div>Lorem ipsum</div>\n<div>Lorem ipsum</div>\n<div>Lorem ipsum</div>\n<div>Lorem ipsum</div>\n<div>Lorem ipsum</div>\n\n<script>\n\tvar is = false;\n\tsetInterval(function() {\n\t\tis = !is;\n\t\tif (is)\n\t\t\tSETTER('loading/show');\n\t\telse\n\t\t\tSETTER('loading/hide', 1000);\n\t}, 3000);\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/locale","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/locale/","markdown_url":"https://forge.totaljs.es/en/components/locale.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"05ccdf41be800960cd956bd8962b27c46969ab33c77f7739fd5a130d1f90be76","checked_at":"2026-10-20T12:22:20.452Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Locale","description":"This component sets current browser locale (number format, date format and time format) according to the navigatior.language.","messages":[{"role":"user","content":"j-Locale\n\nThis component sets current browser locale (number format, date format and time format) according to the navigatior.language."},{"role":"assistant","content":"This component sets current browser locale (number format, date format and time format) according to the `navigatior.language`.\n\n- singleton\n- `path` will contain current's browser locale\n- sets `DEF.dateformat` and it creates environment value called `[ts]`\n\n__Configuration__:\n\n- `language {String}` optional, a custom language (default: `navigator.language`)\n- `requests {Boolean}` optional, adds a language to the every request (it affects `DEF.languagehtml`, default: `false`)\n\n__Environments__:\n\n- `[ts]` full timestamp\n- `[date]` only date\n- `[time]` only time\n\n__Output__:\n\n```js\n{\n\tdt: 'yyyy–MM–dd', // date format\n\tnf: 1,  // number format: 1 = 100 000.123, 2 = 100 000,123, 3 = 100.000,123, 4 = 100,000.123\n\tfdw: 0, // first day of week\n\ttf: 24  // time format: 12, 24\n}\n```\n\n__Methods__:\n\n- `component.use(language)`\n\n__Supported special languages__:\n\n- `eu` with the dd.MM.yyyy with a 24 hour system\n- `us` with the MM-dd-yyyy with a 12 hour system\n\nUse it with `<ui-component name=\"locale\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"locale\" path=\"mylocale\"></ui-component>\n<hr />\n<pre><ui-bind path=\"mylocale\" config=\"text:JSON.stringify(value, null, '  ')\"></ui-bind></pre>\n<ui-bind path=\"testvalue\" config=\"template\" class=\"mt10 block\">\n\t<script type=\"text/html\">\n\t\t<div>{{ value.date | format }}</div>\n\t\t<div>{{ value.date | format('[ts]') }}</div>\n\t\t<div>{{ value.number | format(2) }}</div>\n\t</script>\n</ui-bind>\n\n<script>\n\tvar testvalue = { date: new Date(), number: 1234567.89 };\n</script>\n```"}],"knowledge":{"content_sha256":"3091033db97448f7ec8934ff82579a2a5db8a676f43327de1ccbb75f42b81016","source_sha256":"05ccdf41be800960cd956bd8962b27c46969ab33c77f7739fd5a130d1f90be76","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","JSON","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"locale\" path=\"mylocale\"></ui-component>\n<hr />\n<pre><ui-bind path=\"mylocale\" config=\"text:JSON.stringify(value, null, '  ')\"></ui-bind></pre>\n<ui-bind path=\"testvalue\" config=\"template\" class=\"mt10 block\">\n\t<script type=\"text/html\">\n\t\t<div>{{ value.date | format }}</div>\n\t\t<div>{{ value.date | format('[ts]') }}</div>\n\t\t<div>{{ value.number | format(2) }}</div>\n\t</script>\n</ui-bind>\n\n<script>\n\tvar testvalue = { date: new Date(), number: 1234567.89 };\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/mainprogress","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/mainprogress/","markdown_url":"https://forge.totaljs.es/en/components/mainprogress.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"e2abcce73f582ad5f4c157c2460bdb8ecfa1762260f8db52060a0bd14cd44fbb","checked_at":"2026-10-20T12:22:21.342Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-MainProgress","description":"This component is singleton. The component is visible when the value if more than 0 and less than 100. Data-binding expects Number.","messages":[{"role":"user","content":"j-MainProgress\n\nThis component is singleton. The component is visible when the value if more than 0 and less than 100. Data-binding expects Number."},{"role":"assistant","content":"This component is singleton. The component is visible when the value if more than `0` and less than `100`. Data-binding expects `Number`.\n\n\n__Configuration__:\n\n- `position {String}` optional, can be `bottom` (default: `top`)\n\nUse it with `<ui-component name=\"mainprogress\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"mainprogress\" path=\"percentage\"></ui-component>\n\n<script>\n\tvar percentage = 30;\n\tsetInterval(function() {\n\t\tSET('percentage', (Math.random() * 100) >> 0);\n\t}, 800);\n</script>\n```"}],"knowledge":{"content_sha256":"e97fbb6956c9264c306cb253aa2eba30c68c29a850ea4ac3eeb65bb446be767f","source_sha256":"e2abcce73f582ad5f4c157c2460bdb8ecfa1762260f8db52060a0bd14cd44fbb","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","SET"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"mainprogress\" path=\"percentage\"></ui-component>\n\n<script>\n\tvar percentage = 30;\n\tsetInterval(function() {\n\t\tSET('percentage', (Math.random() * 100) >> 0);\n\t}, 800);\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/map","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/map/","markdown_url":"https://forge.totaljs.es/en/components/map.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"313bc71f32d7969685312b7d99ae76dce2cbc05b00a61ebcc7c5e5b61f7ef865","checked_at":"2026-10-20T12:22:22.071Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Map","description":"j-Map: easy usage; two way binding works with GPS (so the component returns GPS coordinates into the model when the text is used instead of GPS)","messages":[{"role":"user","content":"j-Map\n\nj-Map: easy usage; two way binding works with GPS (so the component returns GPS coordinates into the model when the text is used instead of GPS)"},{"role":"assistant","content":"- `path=\"target.property\"` can contain GPS `48.727903,19.157763` or text `Viestova 28, Banská Bystrica, Slovakia`\n- two way binding works with GPS (so the component returns GPS coordinates into the model when the `text` is used instead of GPS)\n- __important__: the component element (or its `CSS`) must have defined `width` and `height` style attributes\n\n__Configuration__:\n\n- `key` __IMPORTANT__ Google API key\n- `type` (optional) has to contain a map type (`roadmap` (default), `satellite`, `hybrid`, `terrain`)\n- `draggable` (optional) for drag & drop marker (default: `false`)\n- `click` (optional) has to contain a function name and this method is evaluated when the user click on the marker\n- `zoom` (optional), default `13`\n- `icon` (optional) has to contain a URL to a new icon marker\n- `animation` (optional) has to contain a animation type (`bounce` and `drop`)\n\n__Methods__:\n\n- `component.search(lat, lng)` arguments must be numbers\n- `component.search(text)` arguments must be a string\n- `component.reset()` call `resize` event (useful for modals)\n- `component.reset(lat, lng)` arguments must be numbers\n\nUse it with `<ui-component name=\"map\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"map\" path=\"mygps\" config=\"zoom:10;draggable:false;animation:bounce\" style=\"width:300px;height:150px\"></ui-component>\n\n<script>\n\tvar mygps = '48.727903,19.157763';\n</script>\n```"}],"knowledge":{"content_sha256":"81614f13bb2b247e91557b7c1f2d27f48e0b1daba602bc896ff2f7736bcbf1f3","source_sha256":"313bc71f32d7969685312b7d99ae76dce2cbc05b00a61ebcc7c5e5b61f7ef865","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["API","CDN","CSS","DEF","GPS","MIT","URL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"map\" path=\"mygps\" config=\"zoom:10;draggable:false;animation:bounce\" style=\"width:300px;height:150px\"></ui-component>\n\n<script>\n\tvar mygps = '48.727903,19.157763';\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/mapzones","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/mapzones/","markdown_url":"https://forge.totaljs.es/en/components/mapzones.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"2706fb18d84cf227447521a467139372104e837a6a2d93adbe77336696d987bd","checked_at":"2026-10-20T12:22:23.410Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-MapZones","description":"The component for drawing and displaying zones on the map. This component uses [OpenLayers](https://openlayers.org/) library with the [OpenStreetMap](https://www.openstreetmap.org/) map engine. The co","messages":[{"role":"user","content":"j-MapZones\n\nThe component for drawing and displaying zones on the map. This component uses [OpenLayers](https://openlayers.org/) library with the [OpenStreetMap](https://www.openstreetmap.org/) map engine. The co"},{"role":"assistant","content":"The component for drawing and displaying zones on the map. This component uses [OpenLayers](https://openlayers.org/) library with the [OpenStreetMap](https://www.openstreetmap.org/) map engine. The component uses 3rd party dependencies.\n\n__Configuration__:\n\n- `height {Number/String}` fixed height or a parent element (default: `200`)\n- `color {String}` enables custom color for created polygon (default: `#fcba03`)\n- `zoom {Number}` a default zoom level (default: `11`)\n- `modify {Boolean}` enables modifiyng of the polygon (default: `true`)\n- `opacity {Number}` determines the transparency of the polygon in percentage (default: `20`)\n- __NEW__ `exec {String}` a link to the `function(value)`, it's executed when the value is changed\n\n__Data-source__ needs to contain:\n\n```js\n{\n    points: [{ lat: 48.75152530164132, lng: 19.13902142954625 }, { lat: 48.726826662905296, lng: 19.123238694044034 }, ...] // polygon points\n    color: '#FD391A', // optional\n    zoom: 13 // optional\n}\n```\n\n__Good to know__:\n\n- `SHIFT + click` on vertices (created point) will delete selected point\n\nUse it with `<ui-component name=\"mapzones\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Dodo Marton / Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"width:400px\">\n\t<ui-component name=\"mapzones\" path=\"map\" config=\"height:400;color:#FD391A\"></ui-component>\n</div>\n\n<script>\n\tvar map = { \n\t\tpoints: [\n\t\t\t{ lat: 48.75152530164132, lng: 19.13902142954625 },\n\t\t\t{ lat: 48.726826662905296, lng: 19.123238694044034 },\n\t\t\t{ lat: 48.739833820262106, lng: 19.190119425971947 },\n\t\t\t{ lat: 48.75152530164132, lng: 19.13902142954625 }\n\t\t],\n\t\tcolor: '#FD391A',\n\t\tzoom: 13 \n\t};\n</script>\n```"}],"knowledge":{"content_sha256":"287457598ac3fb791d14d216c667a776df7a5030b56e856266727d89c74213d1","source_sha256":"2706fb18d84cf227447521a467139372104e837a6a2d93adbe77336696d987bd","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","FD391A","MIT","SHIFT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"width:400px\">\n\t<ui-component name=\"mapzones\" path=\"map\" config=\"height:400;color:#FD391A\"></ui-component>\n</div>\n\n<script>\n\tvar map = { \n\t\tpoints: [\n\t\t\t{ lat: 48.75152530164132, lng: 19.13902142954625 },\n\t\t\t{ lat: 48.726826662905296, lng: 19.123238694044034 },\n\t\t\t{ lat: 48.739833820262106, lng: 19.190119425971947 },\n\t\t\t{ lat: 48.75152530164132, lng: 19.13902142954625 }\n\t\t],\n\t\tcolor: '#FD391A',\n\t\tzoom: 13 \n\t};\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/markdowneditor","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/markdowneditor/","markdown_url":"https://forge.totaljs.es/en/components/markdowneditor.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"19d463894833a92ad2305a456e2d54cc710f3480b7ab10bddad54ab49e6415dd","checked_at":"2026-10-20T12:22:26.253Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-MarkdownEditor","description":"This component use https://simplemde.com/ editor for markdown editing.","messages":[{"role":"user","content":"j-MarkdownEditor\n\nThis component use https://simplemde.com/ editor for markdown editing."},{"role":"assistant","content":"This component use `https://simplemde.com/` editor for markdown editing.\n\n__Configuration__:\n\n- `required` {Boolean} optional, default `false`\n- `label` {String} optional, default `html` of the element\n\nUse it with `<ui-component name=\"markdowneditor\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Michal Capo. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"markdowneditor\" config=\"markdown\"></ui-component>\n\n<script>\n\tvar markdown = '# Hello world\\nLorem ipsum dolor sit amet, **consectetur** adipisicing elit. Eius, ~~illo~~.\\n\\n```javascript \\nalert(\\'Markdown Preview\\');\\n```';\n</script>\n```"}],"knowledge":{"content_sha256":"db5579b1ca83932f175d7d5485fcd137fc7bf9635d491c23c711b0bdfc6dcf2b","source_sha256":"19d463894833a92ad2305a456e2d54cc710f3480b7ab10bddad54ab49e6415dd","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"markdowneditor\" config=\"markdown\"></ui-component>\n\n<script>\n\tvar markdown = '# Hello world\\nLorem ipsum dolor sit amet, **consectetur** adipisicing elit. Eius, ~~illo~~.\\n\\n```javascript \\nalert(\\'Markdown Preview\\');\\n```';\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/markdownpreview","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/markdownpreview/","markdown_url":"https://forge.totaljs.es/en/components/markdownpreview.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"ef413a24db75d68c2bb058938a5fda2dd82fe6854f72ee60ebefb0362294e666","checked_at":"2026-10-20T12:22:27.138Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-MarkdownPreview","description":"This component depends on j-Markdown component. It uses highlight.js library for syntax highlight.","messages":[{"role":"user","content":"j-MarkdownPreview\n\nThis component depends on j-Markdown component. It uses highlight.js library for syntax highlight."},{"role":"assistant","content":"This component depends on `j-Markdown` component. It uses `highlight.js` library for syntax highlight.\n\n- supports syntax highlighting\n- supports YouTube, Vimeo and custom iframes\n- supports barchart and linechart rendering via ApexCharts\n\n__Configuration__:\n\n- `showsecret` {String} a label for displaying of secret (default: `Show secret data`)\n- `hidesecret` {String} a label to hide of secret (default: `Hide secret data`)\n- `html` {Boolean} prepares inner HTML (it expects rendered Markdown) with Markdown utilities (syntax, secrets, links, etc.)\n- `headlines` {String} a link to method `method(headlines)` will be executed when the headlines will be parsed as quick bookmarks\n\n__Custom methods__:\n\n- `component.readingtime()` returns approximate reading time in minutes\n- `component.redraw(jquery_el)` applies syntax highlighting, iframes and charts for the `el` element\n\nUse it with `<ui-component name=\"markdownpreview\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"markdown\"></ui-component>\n<ui-component name=\"markdownpreview\" path=\"markdown\"></ui-component>\n\n<script>\n\tvar markdown = '# Hello world\\n\\Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eius, illo.\\n\\n```javascript\\nalert(\\'Markdown Preview\\');\\n```';\n</script>\n```"}],"knowledge":{"content_sha256":"b57b2722ddefdc7c68a1832ff9e5c78a504355420b4a5267968aa0e026551d1a","source_sha256":"ef413a24db75d68c2bb058938a5fda2dd82fe6854f72ee60ebefb0362294e666","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","HTML","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"markdown\"></ui-component>\n<ui-component name=\"markdownpreview\" path=\"markdown\"></ui-component>\n\n<script>\n\tvar markdown = '# Hello world\\n\\Lorem ipsum dolor sit amet, consectetur adipisicing elit. Eius, illo.\\n\\n```javascript\\nalert(\\'Markdown Preview\\');\\n```';\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/masonry","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/masonry/","markdown_url":"https://forge.totaljs.es/en/components/masonry.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"5e94cb931d20dee556bf8d7176d611d8ea1dfe2a691147613a529d4dc5a3db7e","checked_at":"2026-10-20T12:22:28.021Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Masonry","description":"A simple Masonry component. The component renders an Array according to the display size to columns.","messages":[{"role":"user","content":"j-Masonry\n\nA simple Masonry component. The component renders an Array according to the display size to columns."},{"role":"assistant","content":"A simple Masonry component. The component renders an Array according to the display size to columns.\n\n\n__Configuration__:\n\n- `lg {Number}` (large display) column width in percentage (default: `25`)\n- `md {Number}` (medium display) column width in percentage (default: `33.33`)\n- `sm {Number}` (small display) column width in percentage (default: `50`)\n- `xs {Number}` (extra-small display) column width in percentage (default: `100`)\n- __NEW__ `vdom {String}` optional, an item jQuery selector for virtual dom comparing\n- __NEW__ `vdomattr {String}` optional, an attribute name for comparing virtual dom elements (values from the attribute will be compared between each other)\n\n__Tangular model__:\n\n- `value {Object}` contains a raw value from the array\n- `index {Number}` a current processed row within the column\n- `column {Number}` a current column index\n- `counter {Number}` a current index of processed item within the array\n\nUse it with `<ui-component name=\"masonry\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"masonry\" path=\"pictures\" config=\"sm:33.33\">\n\t<script type=\"text/html\">\n\t\t<div style=\"margin:10px;background:#F0F0F0;padding:20px\"><img src=\"{{ value }}\" class=\"img-responsive img-rounded\" /></div>\n\t</script>\n</ui-component>\n\n<script>\n\tvar pictures = [\"https://cdn.pixabay.com/photo/2020/12/13/16/21/stork-5828727__340.jpg\",\"https://cdn.pixabay.com/photo/2020/07/31/07/59/flowers-5452263__340.jpg\",\"https://cdn.pixabay.com/photo/2020/03/14/00/23/building-4929371__340.jpg\",\"https://cdn.pixabay.com/photo/2021/02/08/16/03/dinosaur-5995333__340.png\",\"https://cdn.pixabay.com/photo/2020/12/02/00/56/mountains-5795880__340.jpg\",\"https://cdn.pixabay.com/photo/2021/01/30/14/22/women-5963963__340.jpg\",\"https://cdn.pixabay.com/photo/2021/02/05/21/10/church-5985941__340.jpg\",\"https://cdn.pixabay.com/photo/2021/02/11/08/43/couple-6004642__340.jpg\",\"https://cdn.pixabay.com/photo/2020/12/27/12/07/sunrise-5863751__340.png\",\"https://cdn.pixabay.com/photo/2020/09/26/07/05/sea-5603352__340.jpg\",\"https://cdn.pixabay.com/photo/2021/02/18/20/52/flower-6028389__340.jpg\",\"https://cdn.pixabay.com/photo/2021/01/24/20/47/big-basin-redwood-state-park-5946500__340.jpg\",\"https://cdn.pixabay.com/photo/2021/03/09/14/33/dog-6082017__340.jpg\",\"https://cdn.pixabay.com/photo/2021/01/24/20/21/cloud-5946381__340.jpg\",\"https://cdn.pixabay.com/photo/2021/02/12/09/36/sunflowers-6007847__340.jpg\",\"https://cdn.pixabay.com/photo/2020/11/26/15/28/woman-5779323__340.jpg\",\"https://cdn.pixabay.com/photo/2021/02/08/20/24/lake-5996427__340.jpg\",\"https://cdn.pixabay.com/photo/2021/01/05/06/40/boat-5889919__340.png\",\"https://cdn.pixabay.com/photo/2021/03/07/16/31/person-6076771__340.jpg\",\"https://cdn.pixabay.com/photo/2021/02/06/16/29/jay-5988657__340.jpg\", \"https://cdn.pixabay.com/photo/2021/02/08/16/03/dinosaur-5995333__340.png\",\"https://cdn.pixabay.com/photo/2020/12/27/12/07/sunrise-5863751__340.png\",\"https://cdn.pixabay.com/photo/2021/01/24/20/21/cloud-5946381__340.jpg\",\"https://cdn.pixabay.com/photo/2020/11/27/12/59/woman-5781862__340.jpg\",\"https://cdn.pixabay.com/photo/2020/12/14/15/48/light-bulb-5831252__340.jpg\",\"https://cdn.pixabay.com/photo/2021/01/13/03/27/wallpaper-5913128__340.png\",\"https://cdn.pixabay.com/photo/2020/07/21/17/48/love-5426977__340.jpg\",\"https://cdn.pixabay.com/photo/2020/01/09/01/00/eyes-4751572__340.png\",\"https://cdn.pixabay.com/photo/2021/01/30/12/19/couple-5963678__340.png\",\"https://cdn.pixabay.com/photo/2021/02/01/10/13/mandala-5970032__340.jpg\",\"https://cdn.pixabay.com/photo/2021/01/23/07/53/dogs-5941898__340.jpg\",\"https://cdn.pixabay.com/photo/2020/12/23/14/41/forest-5855196__340.jpg\",\"https://cdn.pixabay.com/photo/2019/12/29/02/33/sand-4726119__340.jpg\",\"https://cdn.pixabay.com/photo/2021/01/22/09/01/unicorn-5939500__340.png\",\"https://cdn.pixabay.com/photo/2021/01/17/02/02/planets-5923806__340.jpg\",\"https://cdn.pixabay.com/photo/2020/04/26/18/08/leaf-5096502_640.jpg\", \"https://cdn.pixabay.com/photo/2020/10/12/16/45/pattern-5649403__340.jpg\"];\n</script>\n```"}],"knowledge":{"content_sha256":"e29219a14cca8cc372282d9015607362b650652c0facabb85a077a0cc6758d0b","source_sha256":"5e94cb931d20dee556bf8d7176d611d8ea1dfe2a691147613a529d4dc5a3db7e","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","F0F0F0","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"masonry\" path=\"pictures\" config=\"sm:33.33\">\n\t<script type=\"text/html\">\n\t\t<div style=\"margin:10px;background:#F0F0F0;padding:20px\"><img src=\"{{ value }}\" class=\"img-responsive img-rounded\" /></div>\n\t</script>\n</ui-component>\n\n<script>\n\tvar pictures = [\"https://cdn.pixabay.com/photo/2020/12/13/16/21/stork-5828727__340.jpg\",\"https://cdn.pixabay.com/photo/2020/07/31/07/59/flowers-5452263__340.jpg\",\"https://cdn.pixabay.com/photo/2020/03/14/00/23/building-4929371__340.jpg\",\"https://cdn.pixabay.com/photo/2021/02/08/16/03/dinosaur-5995333__340.png\",\"https://cdn.pixabay.com/photo/2020/12/02/00/56/mountains-5795880__340.jpg\",\"https://cdn.pixabay.com/photo/2021/01/30/14/22/women-5963963__340.jpg\",\"https://cdn.pixabay.com/photo/2021/02/05/21/10/church-5985941__340.jpg\",\"https://cdn.pixabay.com/photo/2021/02/11/08/43/couple-6004642__340.jpg\",\"https://cdn.pixabay.com/photo/2020/12/27/12/07/sunrise-5863751__340.png\",\"https://cdn.pixabay.com/photo/2020/09/26/07/05/sea-5603352__340.jpg\",\"https://cdn.pixabay.com/photo/2021/02/18/20/52/flower-6028389__340.jpg\",\"https://cdn.pixabay.com/photo/2021/01/24/20/47/big-basin-redwood-state-park-5946500__340.jpg\",\"https://cdn.pixabay.com/photo/2021/03/09/14/33/dog-6082017__340.jpg\",\"https://cdn.pixabay.com/photo/2021/01/24/20/21/cloud-5946381__340.jpg\",\"https://cdn.pixabay.com/photo/2021/02/12/09/36/sunflowers-6007847__340.jpg\",\"https://cdn.pixabay.com/photo/2020/11/26/15/28/woman-5779323__340.jpg\",\"https://cdn.pixabay.com/photo/2021/02/08/20/24/lake-5996427__340.jpg\",\"https://cdn.pixabay.com/photo/2021/01/05/06/40/boat-5889919__340.png\",\"https://cdn.pixabay.com/photo/2021/03/07/16/31/person-6076771__340.jpg\",\"https://cdn.pixabay.com/photo/2021/02/06/16/29/jay-5988657__340.jpg\", \"https://cdn.pixabay.com/photo/2021/02/08/16/03/dinosaur-5995333__340.png\",\"https://cdn.pixabay.com/photo/2020/12/27/12/07/sunrise-5863751__340.png\",\"https://cdn.pixabay.com/photo/2021/01/24/20/21/cloud-5946381__340.jpg\",\"https://cdn.pixabay.com/photo/2020/11/27/12/59/woman-5781862__340.jpg\",\"https://cdn.pixabay.com/photo/2020/12/14/15/48/light-bulb-5831252__340.jpg\",\"https://cdn.pixabay.com/photo/2021/01/13/03/27/wallpaper-5913128__340.png\",\"https://cdn.pixabay.com/photo/2020/07/21/17/48/love-5426977__340.jpg\",\"https://cdn.pixabay.com/photo/2020/01/09/01/00/eyes-4751572__340.png\",\"https://cdn.pixabay.com/photo/2021/01/30/12/19/couple-5963678__340.png\",\"https://cdn.pixabay.com/photo/2021/02/01/10/13/mandala-5970032__340.jpg\",\"https://cdn.pixabay.com/photo/2021/01/23/07/53/dogs-5941898__340.jpg\",\"https://cdn.pixabay.com/photo/2020/12/23/14/41/forest-5855196__340.jpg\",\"https://cdn.pixabay.com/photo/2019/12/29/02/33/sand-4726119__340.jpg\",\"https://cdn.pixabay.com/photo/2021/01/22/09/01/unicorn-5939500__340.png\",\"https://cdn.pixabay.com/photo/2021/01/17/02/02/planets-5923806__340.jpg\",\"https://cdn.pixabay.com/photo/2020/04/26/18/08/leaf-5096502_640.jpg\", \"https://cdn.pixabay.com/photo/2020/10/12/16/45/pattern-5649403__340.jpg\"];\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/menu","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/menu/","markdown_url":"https://forge.totaljs.es/en/components/menu.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"83fce923387b170090b1f102da255bff4ef4ae859e6f0a69a7dd699a17814284","checked_at":"2026-10-20T12:22:28.753Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Menu","description":"j-Menu: easy usage for different cases; works with touches","messages":[{"role":"user","content":"j-Menu\n\nj-Menu: easy usage for different cases; works with touches"},{"role":"assistant","content":"- works with touches\n- supports __Dark mode__\n- singleton\n- __NEW__ supports large mode\n\n__Configuration__:\n\n- __NEW__ `style {Number}` changes style (supported style `2` (default) and `1`)\n\n## Usage\n\n```js\nvar opt = {};\n\n// opt.offsetX {Number}      : adds X offset (default: 0)\n// opt.offsetY {Number}      : adds Y offset (default: 0)\n// opt.align {String}        : align `left` (default), `center` or `right`\n// opt.position {String}     : position `top` (default) or `bottom`\n// opt.hide {Function}       : is executed when the menu is hidden\n// opt.class {String}        : adds class to the parent\n// opt.large {Boolean}       : it will make menu bigger\n// opt.fixedwidth {Boolean}  : NEW: enables fixed width according to the target element\n// opt.offsetWidth {Number}  : increases width of the menu\n// opt.nomobile {Boolean}    : change size on mobile screen\n\nopt.element = YOUR_ELEMENT;\n// or if you want to use a fixed position:\n// opt.x {Number}            : `x` position\n// opt.y {Number}            : `y` position\n\nopt.items = ['Label', { name: 'Item 1', icon: 'print', class: 'custom_class_name' }, { name: 'Item 2', icon: 'home', note: 'A simple description for this item' }, '-', { name: 'Item 3', icon: 'ti ti-envelope', children: [{ name: 'Submenu item 1', icon: 'cog' }, { name: 'Submenu item 2', icon: 'home' }] }, { name: 'Selected', icon: 'check-circle', selected: true }, { name: 'Item 4', icon: 'gear', disabled: true }];\n// Item properties\n// name {String}             : Item name\n// icon {String}             : Icon\n// class {String}            : class to be added to li element\n// note {String}             : text bellow Item name\n// selected {Boolean}        : item will be highlighted\n// disabled {Boolean}        : item will be disabled(not clickable)\n// children {Array}          : array of subitems\n// color {String}            : icon color\n\nopt.callback = function(item) {\n\tconsole.log(item);\n};\n\nSETTER('menu', 'show', opt);\n```\n\nUse it with `<ui-component name=\"menu\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\tbody { background-color: #E0E0E0; }\n</style>\n\n<ui-component name=\"menu\"></ui-component>\n\n<div class=\"container\">\n\t<div class=\"row\">\n\t\t<div class=\"col-xs-4 col-xs-offset-4\" style=\"text-align:center\">\n\t\t\t<a href=\"javascript:void(0)\" style=\"color:black;font-size:20px\"><i class=\"ti ti-ellipsis-v\"></i></a>\n\t\t</div>\n\t</div>\n</div>\n\n<script>\n\t$(document).on('click', 'a', function() {\n\n\t\tvar opt = {};\n\t\topt.items = ['Label', { name: 'Item 1', icon: 'print', class: 'custom_class_name' }, { name: 'Item 2', icon: 'home', note: 'A simple description for this item' }, '-', { name: 'Item 3', icon: '!ti ti-envelope', children: [{ name: 'Submenu item 1', icon: 'cog' }, { name: 'Submenu item 2', icon: 'home' }] }, { name: 'Selected', icon: 'check-circle', selected: true }];\n\t\topt.align = 'center';\n\t\topt.element = this;\n\t\topt.callback = function(selected) {\n\t\t\tconsole.log(selected);\n\t\t};\n\t\tSETTER('menu/show', opt);\n\t});\n</script>\n```"}],"knowledge":{"content_sha256":"ee1f4a3cfd3e652aa83928f8236886ed5bff25ba52bc28e9c4b27854240ae20b","source_sha256":"83fce923387b170090b1f102da255bff4ef4ae859e6f0a69a7dd699a17814284","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","E0E0E0","MIT","NEW","SETTER","YOUR_ELEMENT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\tbody { background-color: #E0E0E0; }\n</style>\n\n<ui-component name=\"menu\"></ui-component>\n\n<div class=\"container\">\n\t<div class=\"row\">\n\t\t<div class=\"col-xs-4 col-xs-offset-4\" style=\"text-align:center\">\n\t\t\t<a href=\"javascript:void(0)\" style=\"color:black;font-size:20px\"><i class=\"ti ti-ellipsis-v\"></i></a>\n\t\t</div>\n\t</div>\n</div>\n\n<script>\n\t$(document).on('click', 'a', function() {\n\n\t\tvar opt = {};\n\t\topt.items = ['Label', { name: 'Item 1', icon: 'print', class: 'custom_class_name' }, { name: 'Item 2', icon: 'home', note: 'A simple description for this item' }, '-', { name: 'Item 3', icon: '!ti ti-envelope', children: [{ name: 'Submenu item 1', icon: 'cog' }, { name: 'Submenu item 2', icon: 'home' }] }, { name: 'Selected', icon: 'check-circle', selected: true }];\n\t\topt.align = 'center';\n\t\topt.element = this;\n\t\topt.callback = function(selected) {\n\t\t\tconsole.log(selected);\n\t\t};\n\t\tSETTER('menu/show', opt);\n\t});\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/message","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/message/","markdown_url":"https://forge.totaljs.es/en/components/message.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"9229d5f6f671ad6192b3f8807aeb3f2c7862c67b0260a4a9d6cdebb7072c59b5","checked_at":"2026-10-20T12:22:29.637Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Message","description":"j-Message: singleton; easy usage","messages":[{"role":"user","content":"j-Message\n\nj-Message: singleton; easy usage"},{"role":"assistant","content":"- singleton\n__Configuration__:\n\n- `button {String}` a button label (default: `OK`)\n- `style {Number}` can change a style (default: `1`), style `2` is similiar to `j-Approve` component\n- `closeoutside {Boolean}` enables closing of message box by clicking outside of main button\n\n__Usage__:\n\n```js\nSETTER('message/warning', 'Some warning');\nSETTER('message/success', 'Some success message');\nSETTER('message/info', 'Some info message');\n```\n\n__Works with ASETTER__:\n\n- errors are handled automatically\n\n```js\n// Without callback\nAJAX('POST /api/profile/', userprofile, ASETTER('message/response', 'Profile has been saved successfully'));\n\n// Or with a callback\nAJAX('POST /api/profile/', userprofile, ASETTER('message/response', 'Profile has been saved successfully', function(response) {\n\tconsole.log(response);\n}));\n\n// Or with a callback and without message\nAJAX('POST /api/profile/', userprofile, ASETTER('message/response', function(response) {\n\tconsole.log(response);\n}));\n\n// With auto-binding\nAJAX('POST /api/profile/', userprofile, ASETTER('message/response', '?.response'));\nAJAX('POST /api/profile/', userprofile, ASETTER('message/response', 'Your message', '?.response'));\n```\n\nUse it with `<ui-component name=\"message\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"message\"></ui-component>\n\n<script>\n\tsetTimeout(function() {\n\t\tSETTER('message/success', 'Success message');\n\t\t// SETTER('message/warning', 'Warning message');\n\t}, 2000);\n</script>\n```"}],"knowledge":{"content_sha256":"a793e95708c8086f7287312baec181c47ca6d2fbd28c52615e86da59ef044d11","source_sha256":"9229d5f6f671ad6192b3f8807aeb3f2c7862c67b0260a4a9d6cdebb7072c59b5","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["AJAX","ASETTER","ASETTER__","CDN","DEF","MIT","POST","SETTER"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"message\"></ui-component>\n\n<script>\n\tsetTimeout(function() {\n\t\tSETTER('message/success', 'Success message');\n\t\t// SETTER('message/warning', 'Warning message');\n\t}, 2000);\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/minheight","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/minheight/","markdown_url":"https://forge.totaljs.es/en/components/minheight.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"4642c9cabb401bdd506f40b313af7d9e33b8042b06f18abb3df9d7548be39446","checked_at":"2026-10-20T12:22:30.518Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-MinHeight","description":"This component sets a minimal height for the parent (defined) element. The component is usefull for intranet applications.","messages":[{"role":"user","content":"j-MinHeight\n\nThis component sets a minimal height for the parent (defined) element. The component is usefull for intranet applications."},{"role":"assistant","content":"This component sets a minimal height for the `parent` (defined) element. The component is usefull for intranet applications.\n\n\n__Configuration__:\n\n- `parent {String}` a parent container which sets a minimal height (default: `parent`)\n- `margin {Number}` a margin (default: `0`)\n- `topoffset {Boolean}` applies `element.offset().top` as a margin (default: `false`)\n- `topposition {Boolean}` applies `element.position().top` as a margin (default: `false`)\n- `attr {String}` CSS attribute for setting of height (default: `min-height`)\n- `minheight {Number}` a min. height (default: not defined)\n- `disabledxs {Boolean}` disables height for `xs` display\n- `disabledsm {Boolean}` disables height for `sm` display\n- `disabledmd {Boolean}` disables height for `md` display\n- `disabledlg {Boolean}` disables height for `lg` display\n\nUse it with `<ui-component name=\"minheight\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>body{margin:0;padding:0}</style>\n<ui-component name=\"minheight\" path=\"datasource\" config=\"parent:window\" style=\"border:1px solid red\"></ui-component>\n```"}],"knowledge":{"content_sha256":"2a53bd9287bd687a374aedbcfba5075e55029fc99f5226853e0cd09379980cd0","source_sha256":"4642c9cabb401bdd506f40b313af7d9e33b8042b06f18abb3df9d7548be39446","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","CSS","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>body{margin:0;padding:0}</style>\n<ui-component name=\"minheight\" path=\"datasource\" config=\"parent:window\" style=\"border:1px solid red\"></ui-component>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/miniform","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/miniform/","markdown_url":"https://forge.totaljs.es/en/components/miniform.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"49180fcef9a4b6d9035ae11f1b992012e8473fb154708246e794236e784dd616","checked_at":"2026-10-20T12:22:31.395Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-MiniForm","description":"New version of this component supports dynamic evaluation of the content of <script type='text/html'>. The example below contains a script with HTML and the component evaluates the content if the j-Mi","messages":[{"role":"user","content":"j-MiniForm\n\nNew version of this component supports dynamic evaluation of the content of <script type='text/html'>. The example below contains a script with HTML and the component evaluates the content if the j-Mi"},{"role":"assistant","content":"__Configuration__:\n\n- `title {String}` form title\n- `width {Number}` form max-width\n- `if {String}` condition for showing of the form, it's compared with the value within of `ßpath`\n- `icon {String}` Total icon without `ti-` or __new__: `ti ti-home`\n- `reload {String}` link to a global `function(miniform_component)` and it's executed if the form is displaying\n- `submit {String}` link to a global `function(hide)` and it's executed if the submit button is pressed\n- `cancel {String}` link to a global `function(hide)` and it's executed if the cancel button is pressed\n- `enter {Boolean}` captures `enter` key automatically and performs submit (default: `false`)\n- `center {Boolean}` centers the form to middle of screen\n- `autofocus` {Boolean/String} can focus an input. `String` === `jQuery selector` for the input\n- `default {String}` a short alias for `DEFAULT(default, true)`\n- `closebutton {Boolean}` can hide `x` button (default: `false`)\n- `zindex {Number}` can affect z-index (default: `12`)\n- `scrollbar {Boolean}` enables custom scrollbar (default: `true`)\n- `closeoutside {Boolean}` closes the form when the user clicks outside of the form (default: `false`)\n- `closeesc {Boolean}` closes the form when the user presses `ESC` key\n- `independent {Boolean}` closing doesn't depent on the condition according to the path\n- `close {String}` link to a global `function()` and it's executed if the form is closing\n- __NEW__ `hide {String}` link to a global `function()` and it's executed if the form hides\n\n__Good to know__:\n\nNew version of this component supports dynamic evaluation of the content of `<script type=\"text/html\">`. The example below contains a script with HTML and the component evaluates the content if the j-MiniForm will be displayed (only once).\n\n```html\n<ui-component name=\"miniform\" path=\"path\" config=\"config\">\n\t<script type=\"text/html\">\n\t\tA CONTENT\n\t</script>\n</ui-component>\n```\n\nUse it with `<ui-component name=\"miniform\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"miniform\" path=\"common.form\" config=\"if:user;width:300;title:Title;submit:userform_submit\" class=\"hidden\">\n\t<div class=\"padding\">SOME CONTENT</div>\n\t<nav>\n\t\t<button name=\"submit\">SUBMIT</button>\n\t\t<button name=\"cancel\">CANCEL</button>\n\t</nav>\n</ui-component>\n\n<script>\n\n\tfunction userform_submit(hide) {\n\t\talert('SUBMIT');\n\t\thide();\n\t}\n\n\tvar common = {};\n\n\t// Show the form\n\tcommon.form = 'user';\n</script>\n```"}],"knowledge":{"content_sha256":"b5e45b6c6e9bb24bf2131ec352cdd6209bbff118b5c905a0e9489af839f70300","source_sha256":"49180fcef9a4b6d9035ae11f1b992012e8473fb154708246e794236e784dd616","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CANCEL","CDN","CONTENT","DEF","DEFAULT","ESC","HTML","MIT","SOME","SUBMIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"miniform\" path=\"common.form\" config=\"if:user;width:300;title:Title;submit:userform_submit\" class=\"hidden\">\n\t<div class=\"padding\">SOME CONTENT</div>\n\t<nav>\n\t\t<button name=\"submit\">SUBMIT</button>\n\t\t<button name=\"cancel\">CANCEL</button>\n\t</nav>\n</ui-component>\n\n<script>\n\n\tfunction userform_submit(hide) {\n\t\talert('SUBMIT');\n\t\thide();\n\t}\n\n\tvar common = {};\n\n\t// Show the form\n\tcommon.form = 'user';\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/mobilecarousel","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/mobilecarousel/","markdown_url":"https://forge.totaljs.es/en/components/mobilecarousel.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"52e2b116e53c955f1295ed5748963e4c933a05117e2435e77dcdd1de69813a61","checked_at":"2026-10-20T12:22:32.122Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-MobileCarousel","description":"Works only with Bootstrap grid system and in mobile devices xs.","messages":[{"role":"user","content":"j-MobileCarousel\n\nWorks only with Bootstrap grid system and in mobile devices xs."},{"role":"assistant","content":"Works only with Bootstrap grid system and in mobile devices `xs`.\n\n\n__Configuration__:\n\n- `count {Number}` count of visible items (default: `1`)\n- `selector {String}` jQuery selector (default: `.col-sm-4`)\n- `margin {Number}` left margin between items (default: `15`)\n- `snapping {Boolean}` allows snapping (default: `true`)\n- `animate {Number}` animation timeout, `0` disables animation (default: `5000`)\n\nUse it with `<ui-component name=\"mobilecarousel\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\t.m { margin-bottom: 20px; }\n\t.bg { padding: 10px; background-color: #F0F0F0; }\n</style>\n\n<ui-component name=\"mobilecarousel\" config=\"selector:.col-sm-3\">\n\t<div class=\"row\">\n\t\t<div class=\"col-sm-3 m\">\n\t\t\t<div class=\"bg\">\n\t\t\t\t<h2>Title 1</h2>\n\t\t\t\t<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis, dolores.</p>\n\t\t\t</div>\n\t\t</div>\n\t\t<div class=\"col-sm-3 m\">\n\t\t\t<div class=\"bg\">\n\t\t\t\t<h2>Title 2</h2>\n\t\t\t\t<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis, dolores.</p>\n\t\t\t</div>\n\t\t</div>\n\t\t<div class=\"col-sm-3 m\">\n\t\t\t<div class=\"bg\">\n\t\t\t\t<h2>Title 3</h2>\n\t\t\t\t<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis, dolores.</p>\n\t\t\t</div>\n\t\t</div>\n\t\t<div class=\"col-sm-3 m\">\n\t\t\t<div class=\"bg\">\n\t\t\t\t<h2>Title 4</h2>\n\t\t\t\t<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis, dolores.</p>\n\t\t\t</div>\n\t\t</div>\n\t\t<div class=\"col-sm-3 m\">\n\t\t\t<div class=\"bg\">\n\t\t\t\t<h2>Title 5</h2>\n\t\t\t\t<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis, dolores.</p>\n\t\t\t</div>\n\t\t</div>\n\t\t<div class=\"col-sm-3 m\">\n\t\t\t<div class=\"bg\">\n\t\t\t\t<h2>Title 6</h2>\n\t\t\t\t<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis, dolores.</p>\n\t\t\t</div>\n\t\t</div>\n\t\t<div class=\"col-sm-3 m\">\n\t\t\t<div class=\"bg\">\n\t\t\t\t<h2>Title 7</h2>\n\t\t\t\t<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis, dolores.</p>\n\t\t\t</div>\n\t\t</div>\n\t</div>\n</ui-component>\n```"}],"knowledge":{"content_sha256":"579950570840bbacb2b21540855f592e26e6ca26314c14b748d91440388777ca","source_sha256":"52e2b116e53c955f1295ed5748963e4c933a05117e2435e77dcdd1de69813a61","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","F0F0F0","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\t.m { margin-bottom: 20px; }\n\t.bg { padding: 10px; background-color: #F0F0F0; }\n</style>\n\n<ui-component name=\"mobilecarousel\" config=\"selector:.col-sm-3\">\n\t<div class=\"row\">\n\t\t<div class=\"col-sm-3 m\">\n\t\t\t<div class=\"bg\">\n\t\t\t\t<h2>Title 1</h2>\n\t\t\t\t<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis, dolores.</p>\n\t\t\t</div>\n\t\t</div>\n\t\t<div class=\"col-sm-3 m\">\n\t\t\t<div class=\"bg\">\n\t\t\t\t<h2>Title 2</h2>\n\t\t\t\t<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis, dolores.</p>\n\t\t\t</div>\n\t\t</div>\n\t\t<div class=\"col-sm-3 m\">\n\t\t\t<div class=\"bg\">\n\t\t\t\t<h2>Title 3</h2>\n\t\t\t\t<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis, dolores.</p>\n\t\t\t</div>\n\t\t</div>\n\t\t<div class=\"col-sm-3 m\">\n\t\t\t<div class=\"bg\">\n\t\t\t\t<h2>Title 4</h2>\n\t\t\t\t<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis, dolores.</p>\n\t\t\t</div>\n\t\t</div>\n\t\t<div class=\"col-sm-3 m\">\n\t\t\t<div class=\"bg\">\n\t\t\t\t<h2>Title 5</h2>\n\t\t\t\t<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis, dolores.</p>\n\t\t\t</div>\n\t\t</div>\n\t\t<div class=\"col-sm-3 m\">\n\t\t\t<div class=\"bg\">\n\t\t\t\t<h2>Title 6</h2>\n\t\t\t\t<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis, dolores.</p>\n\t\t\t</div>\n\t\t</div>\n\t\t<div class=\"col-sm-3 m\">\n\t\t\t<div class=\"bg\">\n\t\t\t\t<h2>Title 7</h2>\n\t\t\t\t<p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Blanditiis, dolores.</p>\n\t\t\t</div>\n\t\t</div>\n\t</div>\n</ui-component>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/modal","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/modal/","markdown_url":"https://forge.totaljs.es/en/components/modal.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"918df8979a5cf756e7484d7664ba782361215127bc496a25f6545f09757b67c7","checked_at":"2026-10-20T12:22:32.991Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Modal","description":"Is a simple alternative to j-Form.","messages":[{"role":"user","content":"j-Modal\n\nIs a simple alternative to j-Form."},{"role":"assistant","content":"Is a simple alternative to `j-Form`.\n\n- Works only with `+v17`\n- The component moves the content of this component under `<body>` tag (because of positioning)\n\n\n__Configuration__:\n\n- `title {String}` modal title (it replaces a `HTML` in label element in the header)\n- `width {Number}` modal max-width\n- `if {String}` condition for showing of the modal, it's compared with the value within of `path`\n- `icon {String}` Total icon without `ti-`\n- `reload {String}` link to a global `function(modal_component)` and it's executed if the form is displaying\n- `submit {String}` link to a global `function(hide)` and it's executed if the submit button is pressed\n- `cancel {String}` link to a global `function(hide)` and it's executed if the cancel button is pressed\n- `enter {Boolean}` captures `enter` key automatically and performs submit (default: `false`)\n- `center {Boolean}` centers the form to middle of screen\n- `autofocus {Boolean/String}` can focus an input. `String` === `jQuery selector` for the input\n- `default {String}` a short alias for `DEFAULT(default, true)`\n- `zindex {Number}` can affect z-index (default: `12`)\n- `align {Number}` aligns modal `0` centered (default), `1` right bottom, `2` left bottom, `3` left top, `4` right top\n- `height {Number}` a min. height if the content has less size than defined `height`\n- __NEW__ `bg {Boolean}` can enable/disable a background layer (default: `true`)\n- __NEW__ `scrollbar {Boolean}` can enable/disable the scrollbar (default: `false`)\n\n__Good to know__:\n\nThe content of the `j-Modal` is divided to 3 parts: `header`, `body` and `footer`. Each part must be defined. The component adds CSS classes to each part, for example first `div` will contain `ui-modal-header`, second `ui-modal-body` and third `ui-modal-footer`.\n\n---\n\nThis component supports dynamic evaluation of the content of `<script type=\"text/html\">`. The example below contains a script with HTML and the component evaluates the content if the j-Modal will be displayed (only once).\n\n```html\n<ui-component name=\"modal\" path=\"path\" config=\"config\">\n\t<script type=\"text/html\">\n\t\tA CONTENT\n\t</script>\n</ui-component>\n```\n\nUse it with `<ui-component name=\"modal\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"modal\" path=\"common.form\" config=\"if:user;width:600;submit:usermodal_submit;icon:home\" class=\"hidden\">\n\n\t<!-- HEADER -->\n\t<div>\n\t\t<span class=\"link cancel\"><i class=\"ti ti-times\"></i></span>\n\t\t<label>Your title</label>\n\t</div>\n\n\t<!-- BODY -->\n\t<div>\n\t\t<div class=\"padding\">\n\t\t\tLorem ipsum dolor sit amet, consectetur adipisicing elit. Quasi rerum, consequatur quod quisquam blanditiis voluptatum! Assumenda harum, quaerat iste nisi.\n\t\t\t<br />\n\t\t\t<br />\n\t\t\tLorem ipsum dolor sit amet, consectetur adipisicing elit. Quasi rerum, consequatur quod quisquam blanditiis voluptatum! Assumenda harum, quaerat iste nisi.\n\t\t\t<br />\n\t\t\t<br />\n\t\t\tLorem ipsum dolor sit amet, consectetur adipisicing elit. Quasi rerum, consequatur quod quisquam blanditiis voluptatum! Assumenda harum, quaerat iste nisi.\n\t\t\t<br />\n\t\t\t<br />\n\t\t\tLorem ipsum dolor sit amet, consectetur adipisicing elit. Quasi rerum, consequatur quod quisquam blanditiis voluptatum! Assumenda harum, quaerat iste nisi.\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\tLorem ipsum dolor sit amet, consectetur adipisicing elit. Quasi rerum, consequatur quod quisquam blanditiis voluptatum! Assumenda harum, quaerat iste nisi.\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\tLorem ipsum dolor sit amet, consectetur adipisicing elit. Quasi rerum, consequatur quod quisquam blanditiis voluptatum! Assumenda harum, quaerat iste nisi.\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\tLorem ipsum dolor sit amet, consectetur adipisicing elit. Quasi rerum, consequatur quod quisquam blanditiis voluptatum! Assumenda harum, quaerat iste nisi.\n\t\t</div>\n\t</div>\n\n\t<!-- FOOTER -->\n\t<div>\n\t\t<span class=\"link cancel\">Cancel</span>\n\t\t<button name=\"submit\"><i class=\"ti ti-save\"></i>Submit form</button>\n\t</div>\n\n</ui-component>\n\n<script>\n\n\tfunction usermodal_submit(hide) {\n\t\talert('SUBMIT');\n\t\thide();\n\t}\n\n\tvar common = {};\n\n\t// Show the form\n\tcommon.form = 'user';\n</script>\n```"}],"knowledge":{"content_sha256":"248c444f64eb80df7381f1a1d279ba50b8675a21f6c1f51d4eed4f22335a9748","source_sha256":"918df8979a5cf756e7484d7664ba782361215127bc496a25f6545f09757b67c7","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["BODY","CDN","CONTENT","CSS","DEF","DEFAULT","FOOTER","HEADER","HTML","MIT","SUBMIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"modal\" path=\"common.form\" config=\"if:user;width:600;submit:usermodal_submit;icon:home\" class=\"hidden\">\n\n\t<!-- HEADER -->\n\t<div>\n\t\t<span class=\"link cancel\"><i class=\"ti ti-times\"></i></span>\n\t\t<label>Your title</label>\n\t</div>\n\n\t<!-- BODY -->\n\t<div>\n\t\t<div class=\"padding\">\n\t\t\tLorem ipsum dolor sit amet, consectetur adipisicing elit. Quasi rerum, consequatur quod quisquam blanditiis voluptatum! Assumenda harum, quaerat iste nisi.\n\t\t\t<br />\n\t\t\t<br />\n\t\t\tLorem ipsum dolor sit amet, consectetur adipisicing elit. Quasi rerum, consequatur quod quisquam blanditiis voluptatum! Assumenda harum, quaerat iste nisi.\n\t\t\t<br />\n\t\t\t<br />\n\t\t\tLorem ipsum dolor sit amet, consectetur adipisicing elit. Quasi rerum, consequatur quod quisquam blanditiis voluptatum! Assumenda harum, quaerat iste nisi.\n\t\t\t<br />\n\t\t\t<br />\n\t\t\tLorem ipsum dolor sit amet, consectetur adipisicing elit. Quasi rerum, consequatur quod quisquam blanditiis voluptatum! Assumenda harum, quaerat iste nisi.\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\tLorem ipsum dolor sit amet, consectetur adipisicing elit. Quasi rerum, consequatur quod quisquam blanditiis voluptatum! Assumenda harum, quaerat iste nisi.\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\tLorem ipsum dolor sit amet, consectetur adipisicing elit. Quasi rerum, consequatur quod quisquam blanditiis voluptatum! Assumenda harum, quaerat iste nisi.\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\t<br />\n\t\t\tLorem ipsum dolor sit amet, consectetur adipisicing elit. Quasi rerum, consequatur quod quisquam blanditiis voluptatum! Assumenda harum, quaerat iste nisi.\n\t\t</div>\n\t</div>\n\n\t<!-- FOOTER -->\n\t<div>\n\t\t<span class=\"link cancel\">Cancel</span>\n\t\t<button name=\"submit\"><i class=\"ti ti-save\"></i>Submit form</button>\n\t</div>\n\n</ui-component>\n\n<script>\n\n\tfunction usermodal_submit(hide) {\n\t\talert('SUBMIT');\n\t\thide();\n\t}\n\n\tvar common = {};\n\n\t// Show the form\n\tcommon.form = 'user';\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/modificator","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/modificator/","markdown_url":"https://forge.totaljs.es/en/components/modificator.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"31dd0ff115213cff2dc06213185f6634927ed408c826239d0808f44d24e4722e","checked_at":"2026-10-20T12:22:33.862Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Modificator","description":"This component can modify behaviour of element according to the preddefined schema. The component can be hooked on a body element (because it's singleton) and it's optimized for the performance.","messages":[{"role":"user","content":"j-Modificator\n\nThis component can modify behaviour of element according to the preddefined schema. The component can be hooked on a body element (because it's singleton) and it's optimized for the performance."},{"role":"assistant","content":"This component can modify behaviour of element according to the preddefined schema. The component can be hooked on a `body` element (because it's singleton) and it's optimized for the performance.\n\n__Supported attributes__:\n\n- `data-m=\"SOME.PATH\"` - the component will watch this path\n- `data-m-schema=\"schema_name\"` - modificator uses this registered schema\n- `data-m-selector=\".ui-textbox-label\"` (optional) a target element will be an element according to the selector (default: `null`)\n- __NEW UPDATE__: `data-m=\"SOME.PATH + schema_name\"` this attribute can contain `path` and `schema_name` together\n\n__Supported classes__:\n\n- `modify` enables `click` event for this element `e.type` will contain `click` type\n\n__Registering schema__:\n\n- you can define multiple schemas\n\n```javascript\ninstance.register('schema_name', function(value, element, e) {\n\n    // value: according to the \"data-m\"\n    // element: jQuery element (target)\n    // e: contains additional data { type: 'init/click/bind' } and can be used as a repository\n\n    // here you can modify the element\n});\n```\n\n__Scopes__:\n\nj-Modificator supports jComponent scopes but each path in the scope needs to contain `?` question mark e.g. `data-m=\"?.name\"` which is replaced with a scope path. __Important:__ jComponent scope needs to contain some jComponents.\n\n```html\n<div data-scope=\"user\">\n\t<div data---=\"textbox__?.name\">Your name</div>\n\t<div data-m=\"?.name\"></div>\n</div>\n```\n\nUse it with `<ui-component name=\"modificator\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"modificator\">\n\t<div><a href=\"javascript:void(0)\" class=\"modify\" data-m=\"obj.a\" data-m-schema=\"link\">Click 1</a> second: <a href=\"javascript:void(0)\" class=\"modify\" data-m=\"obj.a\" data-m-schema=\"link\">Click 2</a></div>\n\t<br />\n\t<div><button class=\"modify\" data-m=\"obj.b\" data-m-schema=\"button\"><i></i><b>Submit</b></button></div>\n</ui-component>\n\n<script>\n\n\t// v20 STYLE(), v19 CSS()\n\t(W.STYLE?W.STYLE:W.CSS)('.sending{color:black;font-weight:bold;cursor:not-allowed}.done{color:green;font-weight:bold;}button .ti{margin-right:5px;}');\n\n\t// Register modificator schemas\n\n\tSETTER(true, 'modificator', 'register', 'link', function(value, element, e) {\n\t\tswitch (e.type) {\n\t\t\tcase 'init':\n\t\t\t\tbreak;\n\t\t\tcase 'click':\n\t\t\t\telement.aclass('sending').html('Sending...');\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tif (value != null)\n\t\t\t\t\telement.rclass().aclass('done').html(value);\n\t\t\t\tbreak;\n\t\t}\n\n\t});\n\n\tSETTER(true, 'modificator', 'register', 'button', function(value, element, e) {\n\t\tswitch (e.type) {\n\t\t\tcase 'init':\n\t\t\t\te.icon = element.find('i');\n\t\t\t\te.html = element.html();\n\t\t\t\tbreak;\n\t\t\tcase 'click':\n\t\t\t\te.icon.rclass().aclass('ti ti-spinner ti-refresh');\n\t\t\t\telement.prop('disabled', true).find('b').html('Sending...');\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\te.icon.rclass();\n\t\t\t\telement.prop('disabled', false);\n\t\t\t\telement.find('b').html(e.html);\n\t\t\t\tif (value != null)\n\t\t\t\t\te.icon.aclass('ti ' + (value ? 'ti-check-circle' : 'ti-times-circle'));\n\t\t\t\tbreak;\n\t\t}\n\t});\n\n\tvar obj = {};\n\n\tobj.a = null;\n\tobj.b = null;\n\n\t// Only for test\n\t$(document).on('click', '.modify', function(e) {\n\t\tvar path = $(this).attr('data-m');\n\t\tif (path === 'obj.a') {\n\t\t\tSET(path, 'Success', 3000);\n\t\t} else {\n\t\t\tSET(path, true, 3000);\n\t\t\tSET(path, null, 5000);\n\t\t}\n\t});\n\n\n</script>\n```"}],"knowledge":{"content_sha256":"59fb60a0293297f9d9497a58e927d17487b50d6542f8f08999b6eae1d28b6115","source_sha256":"31dd0ff115213cff2dc06213185f6634927ed408c826239d0808f44d24e4722e","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","CSS","DEF","MIT","PATH","SET","SETTER","SOME","STYLE","UPDATE__"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"modificator\">\n\t<div><a href=\"javascript:void(0)\" class=\"modify\" data-m=\"obj.a\" data-m-schema=\"link\">Click 1</a> second: <a href=\"javascript:void(0)\" class=\"modify\" data-m=\"obj.a\" data-m-schema=\"link\">Click 2</a></div>\n\t<br />\n\t<div><button class=\"modify\" data-m=\"obj.b\" data-m-schema=\"button\"><i></i><b>Submit</b></button></div>\n</ui-component>\n\n<script>\n\n\t// v20 STYLE(), v19 CSS()\n\t(W.STYLE?W.STYLE:W.CSS)('.sending{color:black;font-weight:bold;cursor:not-allowed}.done{color:green;font-weight:bold;}button .ti{margin-right:5px;}');\n\n\t// Register modificator schemas\n\n\tSETTER(true, 'modificator', 'register', 'link', function(value, element, e) {\n\t\tswitch (e.type) {\n\t\t\tcase 'init':\n\t\t\t\tbreak;\n\t\t\tcase 'click':\n\t\t\t\telement.aclass('sending').html('Sending...');\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tif (value != null)\n\t\t\t\t\telement.rclass().aclass('done').html(value);\n\t\t\t\tbreak;\n\t\t}\n\n\t});\n\n\tSETTER(true, 'modificator', 'register', 'button', function(value, element, e) {\n\t\tswitch (e.type) {\n\t\t\tcase 'init':\n\t\t\t\te.icon = element.find('i');\n\t\t\t\te.html = element.html();\n\t\t\t\tbreak;\n\t\t\tcase 'click':\n\t\t\t\te.icon.rclass().aclass('ti ti-spinner ti-refresh');\n\t\t\t\telement.prop('disabled', true).find('b').html('Sending...');\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\te.icon.rclass();\n\t\t\t\telement.prop('disabled', false);\n\t\t\t\telement.find('b').html(e.html);\n\t\t\t\tif (value != null)\n\t\t\t\t\te.icon.aclass('ti ' + (value ? 'ti-check-circle' : 'ti-times-circle'));\n\t\t\t\tbreak;\n\t\t}\n\t});\n\n\tvar obj = {};\n\n\tobj.a = null;\n\tobj.b = null;\n\n\t// Only for test\n\t$(document).on('click', '.modify', function(e) {\n\t\tvar path = $(this).attr('data-m');\n\t\tif (path === 'obj.a') {\n\t\t\tSET(path, 'Success', 3000);\n\t\t} else {\n\t\t\tSET(path, true, 3000);\n\t\t\tSET(path, null, 5000);\n\t\t}\n\t});\n\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/monacoeditor","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/monacoeditor/","markdown_url":"https://forge.totaljs.es/en/components/monacoeditor.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"81148237cac58df681604d338d116457c3c0773f43f11d2d44aa36b8df28e172","checked_at":"2026-10-20T12:22:35.276Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-MonacoEditor","description":"The component renders [Monaco editor](https://microsoft.github.io/monaco-editor/).","messages":[{"role":"user","content":"j-MonacoEditor\n\nThe component renders [Monaco editor](https://microsoft.github.io/monaco-editor/)."},{"role":"assistant","content":"The component renders [Monaco editor](https://microsoft.github.io/monaco-editor/).\n\n\n__Configuration__:\n\n- `theme {String}` a default theme (default: `vs-light`)\n- `language {String}` language / mode (default: `javascript`)\n- `height {Number}` a default/minimal height (default: `200`)\n- `parent {String}` optional, a container with fixed height, can be `window` (default value: `auto`)\n- `margin {Number}` optional, a top/bottom margin together (default: `0`)\n\nUse it with `<ui-component name=\"monacoeditor\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"monacoeditor\" path=\"common.code\" config=\"parent:window\"></ui-component>\n\n<script>\n\n\tvar common = { code: 'var platform = \"Total.js\";'};\n\n</script>\n```"}],"knowledge":{"content_sha256":"e0daa649a04c212871a6f09a6375ceba83e3fe5c6010430e4030a28ff2f941a1","source_sha256":"81148237cac58df681604d338d116457c3c0773f43f11d2d44aa36b8df28e172","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","Total.js"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"monacoeditor\" path=\"common.code\" config=\"parent:window\"></ui-component>\n\n<script>\n\n\tvar common = { code: 'var platform = \"Total.js\";'};\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/monthlycalendar","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/monthlycalendar/","markdown_url":"https://forge.totaljs.es/en/components/monthlycalendar.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"d4e168969ef4f04a48db7e1e02360b15fdb9d35b4882276b9ea505e2c8fc37d0","checked_at":"2026-10-20T12:22:36.168Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-MonthlyCalendar","description":"Month and day names are taken from MONTHS and DAYS properties defined in jComponent library.","messages":[{"role":"user","content":"j-MonthlyCalendar\n\nMonth and day names are taken from MONTHS and DAYS properties defined in jComponent library."},{"role":"assistant","content":"- supports only monthly view\n\n__Configuration__:\n\n- `parent {String}` a jQuery selector for obtaining of fixed height (default `auto`)\n- `firstday {Number}` first day in the calendar `0 = Sunday`, default `0`\n- `noborder {Boolean}` disables border (default: `false`)\n- `selectable {Boolean}` enables selecting of days (default: `true`)\n- `morebutton {String}` a Tangular template for __More button__ (default: `+{{ count }} more`)\n- `select {String}` a path to `Function(item_event, el, e)`, be careful: `item_event` can be `null`\n- `create {String}` a path to `Function({ beg: Date, end: Date }, beg_element, end_element)`\n- `dblclick {String}` a path to `Function(item_event, el, e)`\n- `dblclickdate {String}` a path to `Function(date, el, e)`\n- `contextmenu {String}` a path to `Function(item_event, el, e)`\n- `rebind {String}` a path to `Function({ dates: Array, date: Date })` is executed when the calendar is rebinded\n- `topoffset {Boolean}` subtracts `offset.top` from `height` (default: `false`)\n- `topposition {Boolean}` subtracts `position.top` from `height` (default: `false`)\n- `margin {Number}` top/bottom margin together (default: `0`)\n- `marginxs {Number}` top/bottom margin together for `xs` screen width\n- `marginsm {Number}` top/bottom margin together for `sm` screen width\n- `marginmd {Number}` top/bottom margin together for `md` screen width\n- `marginlg {Number}` top/bottom margin together for `lg` screen width\n- __NEW__: `keepselected {Boolean}` keeps selected (hovered) days (default: `false`)\n- __NEW__: `hover {String}` a path to `Function({ beg: Date, end: Date }, beg_element, end_element)` (same like `create`, but it's called on `component.hover()` and `component.unhover()` methods)\n- __NEW__: `scalewidth {Boolean}` scales the height according to the width of the parent element\n\n__Methods__:\n\n- `component.addevents(events)` adds events\n\t- `events` means Array of object below:\n\t- `[{ id: String, beg: Date, end: Date, html: String, icon: String, color: String, background: Boolean }]`\n- `component.addevent(event)` adds an event\n\t- can't be combined with `addevents` method\n\t- can be executed multiple times\n\t- event object: `{ id: String, html: String, icon: String, color: String, background: Boolean }`\n- __NEW__: `component.addbadge(date, [color])` adds a badge\n- __NEW__: `component.hover(beg, end)`\n- __NEW__: `component.unhover()`\n- `component.clear()` removes all events and badges\n\n__Localization__:\n\nMonth and day names are taken from `MONTHS` and `DAYS` properties defined in jComponent library.\n\nUse it with `<ui-component name=\"monthlycalendar\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\tbody{margin:0;padding:0;overflow:hidden;}\n</style>\n\n<ui-component name=\"monthlycalendar\" path=\"common.date\" config=\"parent:auto;noborder:1;firstday:1\"></ui-component>\n\n<script>\n\tvar common = { date: NOW };\n\n\t(function() {\n\t\tvar events = [];\n\t\tevents.push({ id: 'a', beg: NOW.add('-10 days'), end: NOW.add('2 days'), html: 'Big event', color: 'orange' });\n\t\tevents.push({ id: 'b', beg: NOW, html: 'Current', icon: 'ti ti-car', color: 'blue' });\n\t\tSETTER(true, 'monthlycalendar/addevents', events);\n\t})();\n\n</script>\n```"}],"knowledge":{"content_sha256":"00ec821af6be3d6f798094e4d44acbc5e51cb89be92a94b35940f049a822639b","source_sha256":"d4e168969ef4f04a48db7e1e02360b15fdb9d35b4882276b9ea505e2c8fc37d0","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DAYS","DEF","MIT","MONTHS","NOW","SETTER"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\tbody{margin:0;padding:0;overflow:hidden;}\n</style>\n\n<ui-component name=\"monthlycalendar\" path=\"common.date\" config=\"parent:auto;noborder:1;firstday:1\"></ui-component>\n\n<script>\n\tvar common = { date: NOW };\n\n\t(function() {\n\t\tvar events = [];\n\t\tevents.push({ id: 'a', beg: NOW.add('-10 days'), end: NOW.add('2 days'), html: 'Big event', color: 'orange' });\n\t\tevents.push({ id: 'b', beg: NOW, html: 'Current', icon: 'ti ti-car', color: 'blue' });\n\t\tSETTER(true, 'monthlycalendar/addevents', events);\n\t})();\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/movable","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/movable/","markdown_url":"https://forge.totaljs.es/en/components/movable.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"3df20cc38cc7cf75a18076f3afd2bb1ebee5bed661e01738b0ac3584022834a6","checked_at":"2026-10-20T12:22:37.054Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Movable","description":"The component handles similar draggable element and supports movement between each other. Each element according to the selector must have defined draggable='true' attribute.","messages":[{"role":"user","content":"j-Movable\n\nThe component handles similar draggable element and supports movement between each other. Each element according to the selector must have defined draggable='true' attribute."},{"role":"assistant","content":"The component handles similar `draggable` element and supports movement between each other. Each element according to the selector must have defined `draggable=\"true\"` attribute.\n\n\n__Configuration__:\n\n- `selector {String}` jQuery selector for watching\n- `exec {String}` A path to method which will be executed if the user moves element\n- `global {Boolean}` drag & drop events will be binded to `document` (default: `false`)\n- `parent {String}` selector for finding of element where will be binded drag & drop envets (default: current element)\n- `move {Boolean}` enables auto re-ordering elements (default: `true`)\n- `container {String}` a selector for finding of the main container\n- __NEW__ `check {Function(el)}` optional, a function for checking of draggability (it must return `true` or `false`)\n\n__Example of `Exec` method__:\n\n```javascript\nfunction exec_method(list, dragged, target) {\n\t// @list jQuery list\n}\n```\n\n__Good to know__:\n\n- `path` performs `CHANGE()`\n\nUse it with `<ui-component name=\"movable\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"movable\" config=\"selector:.dragme;exec:moved\">\n\t<div class=\"dragme\" draggable=\"true\" style=\"width:50px;line-height:50px;background-color:#F0F0F0;text-align:center;position:relative;display:inline-block;margin:10px\">A</div>\n\t<div class=\"dragme\" draggable=\"true\" style=\"width:50px;line-height:50px;background-color:#E0E0E0;text-align:center;position:relative;display:inline-block;margin:10px\">B</div>\n\t<div class=\"dragme\" draggable=\"true\" style=\"width:50px;line-height:50px;background-color:#D0D0D0;text-align:center;position:relative;display:inline-block;margin:10px\">C</div>\n\t<div class=\"dragme\" draggable=\"true\" style=\"width:50px;line-height:50px;background-color:#C0C0C0;text-align:center;position:relative;display:inline-block;margin:10px\">D</div>\n\t<div class=\"dragme\" draggable=\"true\" style=\"width:50px;line-height:50px;background-color:#B0B0B0;text-align:center;position:relative;display:inline-block;margin:10px\">E</div>\n\t<div class=\"dragme\" draggable=\"true\" style=\"width:50px;line-height:50px;background-color:#A0A0A0;text-align:center;position:relative;display:inline-block;margin:10px\">F</div>\n</ui-component>\n\n<script>\n\tfunction moved(list) {\n\t\tconsole.log(list);\n\t}\n</script>\n```"}],"knowledge":{"content_sha256":"04cdb48c16d006fc19c07be7c964da4009c3ca58d2704497ce49443c1634081f","source_sha256":"3df20cc38cc7cf75a18076f3afd2bb1ebee5bed661e01738b0ac3584022834a6","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["A0A0A0","B0B0B0","C0C0C0","CDN","CHANGE","D0D0D0","DEF","E0E0E0","F0F0F0","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"movable\" config=\"selector:.dragme;exec:moved\">\n\t<div class=\"dragme\" draggable=\"true\" style=\"width:50px;line-height:50px;background-color:#F0F0F0;text-align:center;position:relative;display:inline-block;margin:10px\">A</div>\n\t<div class=\"dragme\" draggable=\"true\" style=\"width:50px;line-height:50px;background-color:#E0E0E0;text-align:center;position:relative;display:inline-block;margin:10px\">B</div>\n\t<div class=\"dragme\" draggable=\"true\" style=\"width:50px;line-height:50px;background-color:#D0D0D0;text-align:center;position:relative;display:inline-block;margin:10px\">C</div>\n\t<div class=\"dragme\" draggable=\"true\" style=\"width:50px;line-height:50px;background-color:#C0C0C0;text-align:center;position:relative;display:inline-block;margin:10px\">D</div>\n\t<div class=\"dragme\" draggable=\"true\" style=\"width:50px;line-height:50px;background-color:#B0B0B0;text-align:center;position:relative;display:inline-block;margin:10px\">E</div>\n\t<div class=\"dragme\" draggable=\"true\" style=\"width:50px;line-height:50px;background-color:#A0A0A0;text-align:center;position:relative;display:inline-block;margin:10px\">F</div>\n</ui-component>\n\n<script>\n\tfunction moved(list) {\n\t\tconsole.log(list);\n\t}\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/movement","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/movement/","markdown_url":"https://forge.totaljs.es/en/components/movement.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"df38e1d17a3ff33f6cab32190f71071044ad3a580ab44bcb79f321f48b9233b6","checked_at":"2026-10-20T12:22:37.781Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Movement","description":"This component captures basic keyboard keys like arrows, enter and esc.","messages":[{"role":"user","content":"j-Movement\n\nThis component captures basic keyboard keys like arrows, enter and esc."},{"role":"assistant","content":"This component captures basic keyboard keys like `arrows`, `enter` and `esc`.\n\n\n__Methods__:\n\n- `component.assign(ondown(key, e), [onunassign])`\n\nThe `assign` method will clear previous assignments and applies new ones. We recommend calling the `assign` method to your elements when they are focused, or the user clicks on them.\n\n__Usage__:\n\n```js\nSETTER('movement/assign', function(key, e) {\n\t// @key {String}\n\t// @ {Event}\n\tswitch (key) {\n\t\tcase 'up':\n\t\tcase 'down':\n\t\tcase 'right':\n\t\tcase 'left':\n\t\tcase 'esc':\n\t\tcase 'enter':\n\t\t\tbreak;\n\t}\n});\n```\n\nUse it with `<ui-component name=\"movement\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"movement\"></ui-component>\n\n<div class=\"block exec\" data-exec=\"assignmovement\" style=\"width:400px;height:400px;padding:20px;font-size:16px;border:2px solid #E0E0E0\">\n\tClick here and press arrows/enter/esc: <b><ui-bind path=\"mykey\" config=\"text;empty\"></ui-bind></b>\n</div>\n\n<script>\n\tfunction assignmovement() {\n\t\tSETTER('movement/assign', function(key) {\n\t\t\tSET('mykey', key);\n\t\t});\n\t}\n</script>\n```"}],"knowledge":{"content_sha256":"dc36c810a5e106d8bf76743899b977eba91fef76dd6a1c75f9c2d99fc74b095a","source_sha256":"df38e1d17a3ff33f6cab32190f71071044ad3a580ab44bcb79f321f48b9233b6","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","E0E0E0","MIT","SET","SETTER"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"movement\"></ui-component>\n\n<div class=\"block exec\" data-exec=\"assignmovement\" style=\"width:400px;height:400px;padding:20px;font-size:16px;border:2px solid #E0E0E0\">\n\tClick here and press arrows/enter/esc: <b><ui-bind path=\"mykey\" config=\"text;empty\"></ui-bind></b>\n</div>\n\n<script>\n\tfunction assignmovement() {\n\t\tSETTER('movement/assign', function(key) {\n\t\t\tSET('mykey', key);\n\t\t});\n\t}\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/multioptions","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/multioptions/","markdown_url":"https://forge.totaljs.es/en/components/multioptions.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"265953a93506368014c7154e7d7855aa2f48c92d17f19a33425402a68301267e","checked_at":"2026-10-20T12:22:38.503Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-MultiOptions","description":"MultiOptions component uses this color scheme: <http://codepen.io/devi8/pen/lvIeh> and the declaration below will show all colors in the widget settings.","messages":[{"role":"user","content":"j-MultiOptions\n\nMultiOptions component uses this color scheme: <http://codepen.io/devi8/pen/lvIeh> and the declaration below will show all colors in the widget settings."},{"role":"assistant","content":"- it expects the whole object with `key`: `value`\n- HTML body of the jComponent has to be `<script type=\"text/plain>` with (JavaScript) mapping\n\n__Configuration__:\n\n- `rebind` {Boolean} rebinds a path value according to the MultiOptions schema (default: `true`)\n\n__Mapping__:\n\n- it's provided on immediately when the component is ready\n- it has to wrapped in `<script type=\"text/plain\">option();option();...</script>`\n- mapping method: `option(key, label, value, [type], [max], [min], [step])`\n\n__Basic types__:\n\n- type: `string` (when is the value `string` you don't need to define the type)\n- type: `number` (when is the value `number` you don't need to define the type)\n- type: `boolean` (when is the value `boolean` you don't need to define the type)\n- type: `date` (when is the value `date` you don't need to define the type)\n- __NEW__ type: `multiline` renders `textarea` instead of `input`\n\n__Simple Array__:\n\n```js\noption('language', 'Language', 'sk', ['sk', 'en', 'de']);\n```\n\n__Advanced Array__:\n\n```js\noption('language', 'Language', 1, [{ text: 'sk', value: 1}, { text: 'en', value: 2}, { text: 'de', value: 3}]);\n```\n\n__Array from URL__:\n\n```js\noption('language', 'Language', 1, '/my/api/codelist/');\n// It expects some \"Array\"\n```\n\n__Colors__:\n\nMultiOptions component uses this color scheme: <http://codepen.io/devi8/pen/lvIeh> and the declaration below will show all colors in the widget settings.\n\n```js\noption('background', 'Background', '#FC6E51', 'Color');\n```\n\n__Good to know__:\n- all properties not defined in mapping won't be shown\n- `date` type is linked by `Calendar` component\n- follow example\n- `component.remap(js_code)` can remap new configuration\n- `component.dependencies()` returns `Object` with all items `key/value` from dropdown lists\n\nUse it with `<ui-component name=\"multioptions\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"width:600px;margin:30px auto\">\n\t<ui-component name=\"multioptions\" path=\"options\" class=\"block\">\n\t\t<script type=\"text/plain\">\n\t\t\toption('background', 'Background Color', '#8CC152', 'Color');\n\t\t\toption('type', 'Type', 'days', ['hours', 'days', 'months', 'years']);\n\t\t\toption('showArea', 'Show area', true);\n\t\t\toption('body', 'Body', 'Here is enabled\\nmultiline.', 'MultiLine');\n\t\t\toption('showPoint', 'Show point', true);\n\t\t\toption('showLine', 'Show line', true);\n\t\t\toption('smoothCurves', 'Smooth curves', true);\n\t\t\toption('YshowLabel', 'Axis Y: Show Label', true);\n\t\t\toption('YshowGrid', 'Axis Y: Show Grid', false);\n\t\t\toption('onlyInteger', 'Whole numbers', true);\n\t\t\toption('decimals', 'Decimals', 1);\n\t\t</script>\n\t</ui-component>\n</div>\n\n<script>\n\tvar options = {};\n\toptions.background = '#8CC152';\n\toptions.type = 'years';\n\toptions.decimals = 2;\n</script>\n```"}],"knowledge":{"content_sha256":"a1409af0f37aa0e8995529bd2d89a3f5b0079e613e11b4787d0fec613165db8e","source_sha256":"265953a93506368014c7154e7d7855aa2f48c92d17f19a33425402a68301267e","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","FC6E51","HTML","MIT","URL__"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"width:600px;margin:30px auto\">\n\t<ui-component name=\"multioptions\" path=\"options\" class=\"block\">\n\t\t<script type=\"text/plain\">\n\t\t\toption('background', 'Background Color', '#8CC152', 'Color');\n\t\t\toption('type', 'Type', 'days', ['hours', 'days', 'months', 'years']);\n\t\t\toption('showArea', 'Show area', true);\n\t\t\toption('body', 'Body', 'Here is enabled\\nmultiline.', 'MultiLine');\n\t\t\toption('showPoint', 'Show point', true);\n\t\t\toption('showLine', 'Show line', true);\n\t\t\toption('smoothCurves', 'Smooth curves', true);\n\t\t\toption('YshowLabel', 'Axis Y: Show Label', true);\n\t\t\toption('YshowGrid', 'Axis Y: Show Grid', false);\n\t\t\toption('onlyInteger', 'Whole numbers', true);\n\t\t\toption('decimals', 'Decimals', 1);\n\t\t</script>\n\t</ui-component>\n</div>\n\n<script>\n\tvar options = {};\n\toptions.background = '#8CC152';\n\toptions.type = 'years';\n\toptions.decimals = 2;\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/nativenotifications","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/nativenotifications/","markdown_url":"https://forge.totaljs.es/en/components/nativenotifications.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"426cc080ade3cd1988541211da31278364a0f6c19401a7c0d67b735c45834516","checked_at":"2026-10-20T12:22:39.236Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-NativeNotifications","description":"j-NativeNotifications: easy usage; singleton","messages":[{"role":"user","content":"j-NativeNotifications\n\nj-NativeNotifications: easy usage; singleton"},{"role":"assistant","content":"- singleton\n\n\n__Configuration__\n\n- `timeout {Number}` needs to contain a timeout (milliseconds) for the expiration of notification (default: `8000`)\n- `icon {String}` (optional) an icon (default `/icon.png`)\n\n__Methods__\n- `component.append(title, message, [callback], [icon])` for creating a new notification\n\n__Usage__\n\n```javascript\nSETTER('nativenotifications/show', 'TITLE', 'MESSAGE', function() {\n    // optional, click\n}, '/img/warning-optional.png');\n```\n\nUse it with `<ui-component name=\"nativenotifications\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"nativenotifications\" path=\"allowNotifications\"></ui-component>\n\n<script>\n\tvar allowNotifications = true;\n\n\tsetTimeout(function() {\n\t\tSETTER('nativenotifications/append', 'Hello!', 'jComponent is a great solution for everyone.', null, 'https://messenger.totaljs.com/icon.png');\n\t}, 3000);\n</script>\n```"}],"knowledge":{"content_sha256":"7b27cd79d303b973bf28304c74fa69d4466cbbfbb6f6889167389edc36456dad","source_sha256":"426cc080ade3cd1988541211da31278364a0f6c19401a7c0d67b735c45834516","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MESSAGE","MIT","SETTER","TITLE"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"nativenotifications\" path=\"allowNotifications\"></ui-component>\n\n<script>\n\tvar allowNotifications = true;\n\n\tsetTimeout(function() {\n\t\tSETTER('nativenotifications/append', 'Hello!', 'jComponent is a great solution for everyone.', null, 'https://messenger.totaljs.com/icon.png');\n\t}, 3000);\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/navigation","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/navigation/","markdown_url":"https://forge.totaljs.es/en/components/navigation.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"09af3fbae4aa128095a022884d60b2caf3c38b16da375e9892d9d9d8988b8170","checked_at":"2026-10-20T12:22:40.121Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Navigation","description":"j-Navigation: a simple navigation component like Tree; easy for customization","messages":[{"role":"user","content":"j-Navigation\n\nj-Navigation: a simple navigation component like Tree; easy for customization"},{"role":"assistant","content":"- a simple navigation component like Tree\n- easy for customization\n- supports __dark mode__\n\n\n__Configuration__:\n\n- `exec {String}` a link to the function\n- `pk {String}` a primary key (default: `id`) for remembering last state of tree\n\n__Data-source__:\n\n```javascript\n[\n\t{\n\t\tid: 1,\n\t\tname: 'Text to navigation item',\n\t\ttitle: 'Optional, a title attribute',\n\t\tcollapsed: true, // only for children, default \"false\"\n\t\tchildren: [\n\t\t\t// Can contain same structure as the parent\n\t\t]\n\t}\n]\n```\n\n__Good to know__:\n\n- only items without children are clickable\n- `exec` returns an entire object\n\nUse it with `<ui-component name=\"navigation\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div class=\"row\">\n\t<div class=\"col-xs-6\">\n\t\t<div style=\"border: 1px solid #E0E0E0\">\n\t\t\t<ui-component name=\"navigation\" path=\"items\" config=\"exec:navclick\"></ui-component>\n\t\t</div>\n\t</div>\n</div>\n\n<script>\n\tvar items = [];\n\titems.push({ name: 'Home' });\n\titems.push({ name: 'Admin', collapsed: true, children: [{ name: 'Users' }, { name: 'Products' }] });\n\titems.push({ name: 'Settings' });\n\n\tfunction navclick(item) {\n\t\tconsole.log(item);\n\t}\n</script>\n```"}],"knowledge":{"content_sha256":"b90a3476ea1af22690413e9855316e52591dadb464b6cb364ad1f3e31c887eae","source_sha256":"09af3fbae4aa128095a022884d60b2caf3c38b16da375e9892d9d9d8988b8170","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","E0E0E0","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div class=\"row\">\n\t<div class=\"col-xs-6\">\n\t\t<div style=\"border: 1px solid #E0E0E0\">\n\t\t\t<ui-component name=\"navigation\" path=\"items\" config=\"exec:navclick\"></ui-component>\n\t\t</div>\n\t</div>\n</div>\n\n<script>\n\tvar items = [];\n\titems.push({ name: 'Home' });\n\titems.push({ name: 'Admin', collapsed: true, children: [{ name: 'Users' }, { name: 'Products' }] });\n\titems.push({ name: 'Settings' });\n\n\tfunction navclick(item) {\n\t\tconsole.log(item);\n\t}\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/navlayout","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/navlayout/","markdown_url":"https://forge.totaljs.es/en/components/navlayout.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"71218a5403504bf7751035ee1f491ff743cb107646d8c5a47b0b48df0c3e2fba","checked_at":"2026-10-20T12:22:41.006Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-NavLayout","description":"A simple and fully responsive navigation layout.","messages":[{"role":"user","content":"j-NavLayout\n\nA simple and fully responsive navigation layout."},{"role":"assistant","content":"A simple and fully responsive navigation layout.\n\n\n__Good to know__:\n\n- `path {Boolean}` shows the navigation panel in display modes defined in the `hide` config option\n- `<section>` must contain a navigation\n- `<main>` must contain a content\n\n__Configuration__:\n\n- `width {Number}` a size of navigation (default: `250`)\n- `hide {String}` hides navigation in defined display modes (default: `xs,sm`)\n- `autoresize {Boolean}` enables auto-resizing (default: `true`)\n- `position {String}` navigation position (default: `left`)\n- `margin {Number}` adds margin for `height` (default: `0`)\n- `parent {String}` can contain `parent`, `window` or jQuery selector for the `closest` method (default: `window`)\n\nUse it with `<ui-component name=\"navlayout\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\thtml, body { margin: 0; padding: 0; }\n</style>\n\n<ui-component name=\"navlayout\" path=\"common.menu\" config=\"parent:window;hide:xs\" class=\"invisible\">\n\n\t<section style=\"background-color:blue;color:#FFF\">\n\t\tNavigation\n\t</section>\n\n\t<main style=\"background-color:red;color:#FFF\">\n\t\tMain body\n\t</main>\n\n</ui-component>\n\n<script>\n\n\tvar common = { menu: false };\n\n\tsetInterval(function() {\n\t\tvar path = 'common.menu';\n\t\tSET(path, !GET(path));\n\t}, 3000);\n\n</script>\n```"}],"knowledge":{"content_sha256":"89914caffa1c67080f98a5bb5d4421d863c3851f5adea4bafe2fb0498ca5d73e","source_sha256":"71218a5403504bf7751035ee1f491ff743cb107646d8c5a47b0b48df0c3e2fba","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","FFF","GET","MIT","SET"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\thtml, body { margin: 0; padding: 0; }\n</style>\n\n<ui-component name=\"navlayout\" path=\"common.menu\" config=\"parent:window;hide:xs\" class=\"invisible\">\n\n\t<section style=\"background-color:blue;color:#FFF\">\n\t\tNavigation\n\t</section>\n\n\t<main style=\"background-color:red;color:#FFF\">\n\t\tMain body\n\t</main>\n\n</ui-component>\n\n<script>\n\n\tvar common = { menu: false };\n\n\tsetInterval(function() {\n\t\tvar path = 'common.menu';\n\t\tSET(path, !GET(path));\n\t}, 3000);\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/noscrollbar","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/noscrollbar/","markdown_url":"https://forge.totaljs.es/en/components/noscrollbar.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"a386164ae8cdb7d80be0c0bb70e8c12fb8fab023b158a344ecc8b8ca4208ee19","checked_at":"2026-10-20T12:22:41.892Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-NoScrollbar","description":"j-NoScrollbar: this component is something like j-ViewBox without native/custom scrollbar","messages":[{"role":"user","content":"j-NoScrollbar\n\nj-NoScrollbar: this component is something like j-ViewBox without native/custom scrollbar"},{"role":"assistant","content":"- this component is something like `j-ViewBox` without native/custom scrollbar\n\n__Configuration__:\n\n- `parent` {String} optional, a container with fixed height, can be `window`. Default value: `parent` element.\n- `minheight` {Number} a minimal height in pixels (default: `0` - disabled)\n- `delay` {Number} `msec.` resizes the box again if the value is changed according to the path (default: `100`)\n- `scrollto` {String} scrolls to top/bottom automatically if the `path` is changed (default: `EMPTY`, can be `top` or `bottom`)\n- `margin` {Number} optional, a top/bottom margin together (supports `auto` which is counted from `offset.top`, default: `0`)\n- `marginxs` {Number} optional, a top/bottom margin together for `xs` screen width\n- `marginsm` optional, a top/bottom margin together for `sm` screen width\n- `marginmd` optional, a top/bottom margin together for `md` screen width\n- `marginlg` optional, a top/bottom margin together for `lg` screen width\n- `invisible` {Boolean} if `true` then viewbox sets `invisible` class when is rendering (default: `true`)\n- `initdelay` {Number} removes `inivisible` class after specified time (default: `250` ms)\n\n__Methods__:\n\n- `component.scrolltop(val)` scrolls Y\n- `component.scrollbottom(val)` scrolls Y from bottom side\n- `component.resize()` resizes container (it reacts on `resize` events automatically)\n\nUse it with `<ui-component name=\"noscrollbar\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"width:300px;height:300px;background-color:#F5F5F5\">\n\t<ui-component name=\"noscrollbar\" config=\"parent:parent\" class=\"invisible\">\n\t\tBEGIN\n\t\t<div style=\"height:1000px;background-color:red\"></div>\n\t\tEND\n\t</ui-component>\n</div>\n```"}],"knowledge":{"content_sha256":"61f3dae55d7cc917bdd96fcf796de95a76b4985992f20bb4fb3e2a5b6f3a0d08","source_sha256":"a386164ae8cdb7d80be0c0bb70e8c12fb8fab023b158a344ecc8b8ca4208ee19","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["BEGIN","CDN","DEF","EMPTY","END","F5F5F5","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"width:300px;height:300px;background-color:#F5F5F5\">\n\t<ui-component name=\"noscrollbar\" config=\"parent:parent\" class=\"invisible\">\n\t\tBEGIN\n\t\t<div style=\"height:1000px;background-color:red\"></div>\n\t\tEND\n\t</ui-component>\n</div>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/nosqlcounter","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/nosqlcounter/","markdown_url":"https://forge.totaljs.es/en/components/nosqlcounter.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"d654c62232bc9e7a09baa3a7ff61d3849059db9e88fe676bf23a871d02e210ac","checked_at":"2026-10-20T12:22:42.778Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-NoSQLcounter","description":"j-NoSQLcounter: easy usage; datasource must be in the form: [{ id: 201702, year: 2017, month: 2, value: 1690 }]","messages":[{"role":"user","content":"j-NoSQLcounter\n\nj-NoSQLcounter: easy usage; datasource must be in the form: [{ id: 201702, year: 2017, month: 2, value: 1690 }]"},{"role":"assistant","content":"- datasource must be in the form: `[{ id: 201702, year: 2017, month: 2, value: 1690 }]`\n\n__Configuration__:\n\n- `count` {Number} a maximum bars in the chart (default: `0` === `auto-count`)\n- `lastvalues` {Boolean} shows only the last values (default: `false`)\n- `months` {String} months e.g. `January,February,etc.`\n- __NEW__ `limit` {Number} a maximum limit for `value` (default: `0` - disabled)\n\nUse it with `<ui-component name=\"nosqlcounter\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"width:400px\">\n\t<ui-component name=\"nosqlcounter\" path=\"stats\" style=\"height:40px\"></ui-component>\n</div>\n\n<script>\n\tvar stats = [{\"id\":\"201702\",\"year\":2017,\"month\":2,\"value\":1690},{\"id\":\"201703\",\"year\":2017,\"month\":3,\"value\":1199},{\"id\":\"201704\",\"year\":2017,\"month\":4,\"value\":393}];\n</script>\n```"}],"knowledge":{"content_sha256":"eb6c0718bebd5b2754b7ec18bde27720b8425332c64b7bea4f93c54fcb396d8f","source_sha256":"d654c62232bc9e7a09baa3a7ff61d3849059db9e88fe676bf23a871d02e210ac","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"width:400px\">\n\t<ui-component name=\"nosqlcounter\" path=\"stats\" style=\"height:40px\"></ui-component>\n</div>\n\n<script>\n\tvar stats = [{\"id\":\"201702\",\"year\":2017,\"month\":2,\"value\":1690},{\"id\":\"201703\",\"year\":2017,\"month\":3,\"value\":1199},{\"id\":\"201704\",\"year\":2017,\"month\":4,\"value\":393}];\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/notifications","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/notifications/","markdown_url":"https://forge.totaljs.es/en/components/notifications.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"0740b88d647c09b1da439b6c977f0aee5a60e499569d019ae0bebe2c582ec1c8","checked_at":"2026-10-20T12:22:43.666Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Notifications","description":"j-Notifications: easy usage; singleton","messages":[{"role":"user","content":"j-Notifications\n\nj-Notifications: easy usage; singleton"},{"role":"assistant","content":"- singleton\n\n__Configuration__:\n\n- `date` {String} a date format (default `yyyy-MM-dd HH:mm`)\n- `timeout` {Number} has to contain a timeout (milliseconds) for the expiration of notification (default: `8000`)\n- `native` {Boolean} enables native notifications when is not focused window (default: `false`)\n\n__Methods__\n- `component.append(icon, message, [datetime], [callback], [color])` for creating a new notification\n\nUse it with `<ui-component name=\"notifications\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\tbody { font-family: Arial; background-color: #F5F5F5; }\n</style>\n\n<ui-component name=\"notifications\" config=\"date:dd.MM.yyyy HH:mm;native:true\"></ui-component>\n\nLorem ipsum dolor sit amet, consectetur adipisicing elit. Quia harum accusamus debitis aut sunt, deserunt magni alias neque unde reprehenderit ipsa ea laboriosam eveniet! Distinctio pariatur sed, a aut nobis.\n\n<script>\n\n\tFIND('notifications', function(component) {\n\t\tcomponent.append('ti-lightbulb', 'This is a new notification component.');\n\t});\n\n\tsetTimeout(function() {\n\t\tFIND('notifications').append('ti-calendar', 'You have a meeting in the Central Park.', function() {\n\t\t\talert('You clicked on the notification');\n\t\t}, 'red');\n\t}, 3000);\n\n\tsetTimeout(function() {\n\t\tFIND('notifications').append('ti-github', 'Please support <a href=\"https://github.com/petersirka/jComponent\" target=\"_blank\">jComponent on GitHub</a>.');\n\t}, 6000);\n\n\tsetTimeout(function() {\n\t\tFIND('notifications').append('ti-link', 'Total.js is node.js web application framework, please support it on social networks.');\n\t}, 10000);\n\n</script>\n```"}],"knowledge":{"content_sha256":"e993244f72ce544c610f0171e1bf345611b9b79c12d843d0c4f7cf93b162db7d","source_sha256":"0740b88d647c09b1da439b6c977f0aee5a60e499569d019ae0bebe2c582ec1c8","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","F5F5F5","FIND","MIT","Total.js"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\tbody { font-family: Arial; background-color: #F5F5F5; }\n</style>\n\n<ui-component name=\"notifications\" config=\"date:dd.MM.yyyy HH:mm;native:true\"></ui-component>\n\nLorem ipsum dolor sit amet, consectetur adipisicing elit. Quia harum accusamus debitis aut sunt, deserunt magni alias neque unde reprehenderit ipsa ea laboriosam eveniet! Distinctio pariatur sed, a aut nobis.\n\n<script>\n\n\tFIND('notifications', function(component) {\n\t\tcomponent.append('ti-lightbulb', 'This is a new notification component.');\n\t});\n\n\tsetTimeout(function() {\n\t\tFIND('notifications').append('ti-calendar', 'You have a meeting in the Central Park.', function() {\n\t\t\talert('You clicked on the notification');\n\t\t}, 'red');\n\t}, 3000);\n\n\tsetTimeout(function() {\n\t\tFIND('notifications').append('ti-github', 'Please support <a href=\"https://github.com/petersirka/jComponent\" target=\"_blank\">jComponent on GitHub</a>.');\n\t}, 6000);\n\n\tsetTimeout(function() {\n\t\tFIND('notifications').append('ti-link', 'Total.js is node.js web application framework, please support it on social networks.');\n\t}, 10000);\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/notificationspanel","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/notificationspanel/","markdown_url":"https://forge.totaljs.es/en/components/notificationspanel.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"1881da407b923a4bec088fd23d1b9139463ea4f03a702510e298f12e4e213399","checked_at":"2026-10-20T12:22:44.559Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-NotificationsPanel","description":"j-NotificationsPanel: easy usage; singleton","messages":[{"role":"user","content":"j-NotificationsPanel\n\nj-NotificationsPanel: easy usage; singleton"},{"role":"assistant","content":"- singleton\n- learn from the example\n\n\n__Configuration__:\n\n- `top {Number}` top offset (default: `0`)\n- `visibleY {Boolean}` shows Y scrollbars (default: `true`)\n- `title {String}` title in notifications panel (default: `Notifications`)\n- `autoremove {Boolean}` removes notifications after click on it (default: `true`)\n- `click {String}` a path to method `function(obj, el)` when the user clicks on the notifications\n- `exec {String}` a path to method `function(render(ARR_OF_OBJECT))` when the user wants to show the notifications\n\nUse it with `<ui-component name=\"notificationspanel\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"notificationspanel\" path=\"shownotifications\" config=\"exec:loadnotifications;click:clicknotificiation\">\n\t<script type=\"text/html\">\n\t\t<h2>{{ name }}</h2>\n\t\t<p>{{ body }}</p>\n\t\t<hr />\n\t</script>\n</ui-component>\n\n<script>\n\n\tvar shownotifications = false;\n\n\t// Shows notifications panel after 2 seconds\n\tSET('shownotifications', true, 2000);\n\n\t// The method will be executed automatically before the notifications panel will be visible\n\tfunction loadnotifications(render) {\n\t\tvar arr = [];\n\t\tarr.push({ name: 'Name of notifications 1', body: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quos, architecto!' });\n\t\tarr.push({ name: 'Name of notifications 2', body: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quos, architecto!' });\n\t\tarr.push({ name: 'Name of notifications 3', body: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quos, architecto!' });\n\n\t\t// IMPORTANT:\n\t\t// This renders notifications onto notifications panel\n\t\trender(arr);\n\t}\n\n\tfunction clicknotificiation(obj) {\n\t\tconsole.log('CLICK', obj);\n\t}\n\n</script>\n```"}],"knowledge":{"content_sha256":"fb77d7cd8ef85e3c8a61bff0046eddb89006533e8ec6d63633e58b56cf342b68","source_sha256":"1881da407b923a4bec088fd23d1b9139463ea4f03a702510e298f12e4e213399","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["ARR_OF_OBJECT","CDN","CLICK","DEF","IMPORTANT","MIT","SET"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"notificationspanel\" path=\"shownotifications\" config=\"exec:loadnotifications;click:clicknotificiation\">\n\t<script type=\"text/html\">\n\t\t<h2>{{ name }}</h2>\n\t\t<p>{{ body }}</p>\n\t\t<hr />\n\t</script>\n</ui-component>\n\n<script>\n\n\tvar shownotifications = false;\n\n\t// Shows notifications panel after 2 seconds\n\tSET('shownotifications', true, 2000);\n\n\t// The method will be executed automatically before the notifications panel will be visible\n\tfunction loadnotifications(render) {\n\t\tvar arr = [];\n\t\tarr.push({ name: 'Name of notifications 1', body: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quos, architecto!' });\n\t\tarr.push({ name: 'Name of notifications 2', body: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quos, architecto!' });\n\t\tarr.push({ name: 'Name of notifications 3', body: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Quos, architecto!' });\n\n\t\t// IMPORTANT:\n\t\t// This renders notifications onto notifications panel\n\t\trender(arr);\n\t}\n\n\tfunction clicknotificiation(obj) {\n\t\tconsole.log('CLICK', obj);\n\t}\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/notify","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/notify/","markdown_url":"https://forge.totaljs.es/en/components/notify.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"5eae2e6bf505f5c09ee2c96fc4fa587718e51670debfc704e9685a9f1f836866","checked_at":"2026-10-20T12:22:45.452Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Notify","description":"j-Notify: errors are handled automatically","messages":[{"role":"user","content":"j-Notify\n\nj-Notify: errors are handled automatically"},{"role":"assistant","content":"__Configuration__:\n\n- `timeout {Number}` has to contain a timeout (milliseconds) for the expiration of notification (default: `3000`)\n- `position {String}` can be `top`, `top left`, `top right` (default), `bottom` , `bottom left` and `bottom right`\n\n__Methods__:\n\n```js\nSETTER('notify/warning', 'Some warning');\nSETTER('notify/success', 'Some success message');\nSETTER('notify/info', 'Some info message');\n```\n\n__Works with ASETTER__:\n\n- errors are handled automatically\n\n```js\n// Without callback\nAJAX('POST /api/profile/', userprofile, ASETTER('notify/response', 'Profile has been saved successfully'));\n\n// Or with a callback\nAJAX('POST /api/profile/', userprofile, ASETTER('notify/response', 'Profile has been saved successfully', function(response) {\n\tconsole.log(response);\n}));\n\n// Or with a callback and without notify\nAJAX('POST /api/profile/', userprofile, ASETTER('notify/response', function(response) {\n\tconsole.log(response);\n}));\n\n// With auto-binding\nAJAX('POST /api/profile/', userprofile, ASETTER('notify/response', '?.response'));\nAJAX('POST /api/profile/', userprofile, ASETTER('notify/response', 'Your message', '?.response'));\n```\n\nUse it with `<ui-component name=\"notify\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\tbody { font-family: Arial; background-color: #F5F5F5; }\n</style>\n\n<ui-component name=\"notify\"></ui-component>\n\nLorem ipsum dolor sit amet, consectetur adipisicing elit. Quia harum accusamus debitis aut sunt, deserunt magni alias neque unde reprehenderit ipsa ea laboriosam eveniet! Distinctio pariatur sed, a aut nobis.\n\n<script>\n\tsetTimeout(function() {\n\t\tSETTER('notify/success', 'This is \"success\" message.');\n\t}, 1000);\n\n\tsetTimeout(function() {\n\t\tSETTER('notify/warning', 'This is \"warning\" message.', 2);\n\t}, 2000);\n\n\tsetTimeout(function() {\n\t\tSETTER('notify/info', 'This is \"info\" message.', 3);\n\t}, 3000);\n</script>\n```"}],"knowledge":{"content_sha256":"05e4211faaf007b46dd7f30f5e9e5bf7639bda3307147c18d7fc681f2086f9a8","source_sha256":"5eae2e6bf505f5c09ee2c96fc4fa587718e51670debfc704e9685a9f1f836866","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["AJAX","ASETTER","ASETTER__","CDN","DEF","F5F5F5","MIT","POST","SETTER"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\tbody { font-family: Arial; background-color: #F5F5F5; }\n</style>\n\n<ui-component name=\"notify\"></ui-component>\n\nLorem ipsum dolor sit amet, consectetur adipisicing elit. Quia harum accusamus debitis aut sunt, deserunt magni alias neque unde reprehenderit ipsa ea laboriosam eveniet! Distinctio pariatur sed, a aut nobis.\n\n<script>\n\tsetTimeout(function() {\n\t\tSETTER('notify/success', 'This is \"success\" message.');\n\t}, 1000);\n\n\tsetTimeout(function() {\n\t\tSETTER('notify/warning', 'This is \"warning\" message.', 2);\n\t}, 2000);\n\n\tsetTimeout(function() {\n\t\tSETTER('notify/info', 'This is \"info\" message.', 3);\n\t}, 3000);\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/notifybar","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/notifybar/","markdown_url":"https://forge.totaljs.es/en/components/notifybar.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"2473a1be3edaffb0bafb52d1d09b9238d6a61be62b92833071146232c1ca1bf6","checked_at":"2026-10-20T12:22:46.196Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-NotifyBar","description":"j-NotifyBar: singleton; success message SETTER('notifybar/success', 'MESSAGE')","messages":[{"role":"user","content":"j-NotifyBar\n\nj-NotifyBar: singleton; success message SETTER('notifybar/success', 'MESSAGE')"},{"role":"assistant","content":"- singleton\n- success message `SETTER('notifybar/success', 'MESSAGE')`\n- warning message `SETTER('notifybar/warning', 'MESSAGE')`\n- info message `SETTER('notifybar/info', 'MESSAGE', '[BUTTON_LABEL]', [callback_dismiss])`\n\n\n__Configuration__:\n\n- `timeout` {Number} A timeout in milliseconds (default: `4000`)\n\n__Methods__:\n\n- `component.success(message)` appends success message\n- `component.warning(message)` appends warning message\n- `component.info(message)` appends info message\n- `component.hide()` hides the bar\n- `component.show()` shows last message\n\n__Works with ASETTER__:\n\n- errors are handled automatically\n\n```javascript\n// Without callback\nAJAX('POST /api/profile/', userprofile, ASETTER('notifybar/response', 'Profile has been saved successfully'));\n\n// Or with a callback\nAJAX('POST /api/profile/', userprofile, ASETTER('notifybar/response', 'Profile has been saved successfully', function(response) {\n\tconsole.log(response);\n}));\n\n// Or with a callback and without message\nAJAX('POST /api/profile/', userprofile, ASETTER('notifybar/response', function(response) {\n\tconsole.log(response);\n}));\n\n// With auto-binding\nAJAX('POST /api/profile/', userprofile, ASETTER('notifybar/response', '?.response'));\nAJAX('POST /api/profile/', userprofile, ASETTER('notifybar/response', 'Your message', '?.response'));\n```\n\nUse it with `<ui-component name=\"notifybar\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"notifybar\"></ui-component>\n\nLorem ipsum dolor sit amet, consectetur adipisicing elit. Quia harum accusamus debitis aut sunt, deserunt magni alias neque unde reprehenderit ipsa ea laboriosam eveniet! Distinctio pariatur sed, a aut nobis.\n\n<script>\n\tsetTimeout(function() {\n\t\tSETTER('notifybar/success', 'Lorem ipsum dolor sit amet.');\n\t}, 1000);\n\n\tsetTimeout(function() {\n\t\tSETTER('notifybar/warning', 'Lorem ipsum dolor sit amet.');\n\t}, 2000);\n</script>\n```"}],"knowledge":{"content_sha256":"9ce8beaab5cce3e04360e6ae1123f88c381381c043c313a474a1c072e4e398c1","source_sha256":"2473a1be3edaffb0bafb52d1d09b9238d6a61be62b92833071146232c1ca1bf6","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["AJAX","ASETTER","ASETTER__","BUTTON_LABEL","CDN","DEF","MESSAGE","MIT","POST","SETTER"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"notifybar\"></ui-component>\n\nLorem ipsum dolor sit amet, consectetur adipisicing elit. Quia harum accusamus debitis aut sunt, deserunt magni alias neque unde reprehenderit ipsa ea laboriosam eveniet! Distinctio pariatur sed, a aut nobis.\n\n<script>\n\tsetTimeout(function() {\n\t\tSETTER('notifybar/success', 'Lorem ipsum dolor sit amet.');\n\t}, 1000);\n\n\tsetTimeout(function() {\n\t\tSETTER('notifybar/warning', 'Lorem ipsum dolor sit amet.');\n\t}, 2000);\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/objecteditor","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/objecteditor/","markdown_url":"https://forge.totaljs.es/en/components/objecteditor.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"40b8432667aa84d938235f59616272aeafd2a34ba61323ec739863a9ce87d26c","checked_at":"2026-10-20T12:22:47.091Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-ObjectEditor","description":"j-ObjectEditor: NEW: exec {String} a path to the function(key, value) and it's executed if the value is changed","messages":[{"role":"user","content":"j-ObjectEditor\n\nj-ObjectEditor: NEW: exec {String} a path to the function(key, value) and it's executed if the value is changed"},{"role":"assistant","content":"__Configuration__:\n\n- `null {Boolean}` optional, shows `null` values (default: `true`)\n- `skip {String}` optional, skips properties divided by commas\n- `dateformat {String}` optional, a default date/time format (default: `yyyy-MM-dd HH:mm:ss`)\n- __NEW__: `exec {String}` a path to the `function(key, value)` and it's executed if the value is changed\n\nUse it with `<ui-component name=\"objecteditor\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"objecteditor\" path=\"obj\"></ui-component>\n\n<script>\n\tvar obj = {};\n\tobj.name = 'Peter';\n\tobj.age = 32;\n\tobj.newsletter = true;\n\tobj.terms = true;\n\tobj.created = new Date();\n\tobj.tags = ['Total.js', 'Node.js', 'Tags'];\n\tobj.data = null;\n\tobj.priority = [1, 2, 3, 4, 5];\n</script>\n```"}],"knowledge":{"content_sha256":"8f1bd1247816e720a44ac09075a8c879b519a0198f26d6da8ff5385c202d2a7d","source_sha256":"40b8432667aa84d938235f59616272aeafd2a34ba61323ec739863a9ce87d26c","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","Total.js"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"objecteditor\" path=\"obj\"></ui-component>\n\n<script>\n\tvar obj = {};\n\tobj.name = 'Peter';\n\tobj.age = 32;\n\tobj.newsletter = true;\n\tobj.terms = true;\n\tobj.created = new Date();\n\tobj.tags = ['Total.js', 'Node.js', 'Tags'];\n\tobj.data = null;\n\tobj.priority = [1, 2, 3, 4, 5];\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/objecttree","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/objecttree/","markdown_url":"https://forge.totaljs.es/en/components/objecttree.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"d27a1c75aac36f2165fbb1523020db6ac0f40f48e5dd9521b226e1f99babcf79","checked_at":"2026-10-20T12:22:47.976Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-ObjectTree","description":"Expandable/Collabsible object tree.","messages":[{"role":"user","content":"j-ObjectTree\n\nExpandable/Collabsible object tree."},{"role":"assistant","content":"Expandable/Collabsible object tree.\n\n\n__Configuration__:\n\n- `exec {String}` function to call when a user clicks on icon\n\n__Usage__:\n\n```js\nfunction exec_function(type, value) {\n    type; // 'path' or 'data'\n    value;// copied path(string) or property value(any datatype)\n}\n```\n\nUse it with `<ui-component name=\"objecttree\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Martin Smola. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"objecttree\" path=\"window.location\"></ui-component>\n```"}],"knowledge":{"content_sha256":"54e1d303dd6930646d95f189f0232d14b0b377bf3ca28ea697edb4595bf7ad62","source_sha256":"d27a1c75aac36f2165fbb1523020db6ac0f40f48e5dd9521b226e1f99babcf79","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"objecttree\" path=\"window.location\"></ui-component>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/offline","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/offline/","markdown_url":"https://forge.totaljs.es/en/components/offline.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"00597efef82adaa9a4ba87db194478dcb2ed4db29a48edf2a2003b9834163bbc","checked_at":"2026-10-20T12:22:48.876Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Offline","description":"This component displays a message box when there is no internet connection.","messages":[{"role":"user","content":"j-Offline\n\nThis component displays a message box when there is no internet connection."},{"role":"assistant","content":"This component displays a message box when there is no internet connection.\n\n- singleton\n\nUse it with `<ui-component name=\"offline\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"offline\" class=\"hidden\">\n\t<div style=\"font-size:60px\"><i class=\"ti ti-globe red\"></i></div>\n\t<b>No internet connection</b>\n\t<div style=\"font-size:12px\">Please check your network connection and try again.</div>\n</ui-component>\n\n<div class=\"padding\">Use the Web Developer Tools to turn off the internet via the Network tab.</div>\n```"}],"knowledge":{"content_sha256":"9c4338a57c0c6766fc5c0f0b97967e0bd07847ba2d90f0246b5ce1516c7f0b77","source_sha256":"00597efef82adaa9a4ba87db194478dcb2ed4db29a48edf2a2003b9834163bbc","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"offline\" class=\"hidden\">\n\t<div style=\"font-size:60px\"><i class=\"ti ti-globe red\"></i></div>\n\t<b>No internet connection</b>\n\t<div style=\"font-size:12px\">Please check your network connection and try again.</div>\n</ui-component>\n\n<div class=\"padding\">Use the Web Developer Tools to turn off the internet via the Network tab.</div>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/onetime","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/onetime/","markdown_url":"https://forge.totaljs.es/en/components/onetime.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"3f6453ab3d0c48b9d9bc0c40d6148f9646d76c4a0c17f875c375219584d13814","checked_at":"2026-10-20T12:22:49.761Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-OneTime","description":"This component executes a method only once and it waits for expiration.","messages":[{"role":"user","content":"j-OneTime\n\nThis component executes a method only once and it waits for expiration."},{"role":"assistant","content":"This component executes a method only once and it waits for expiration.\n\n- Download templates: https://github.com/totaljs/templates\n\n__Configuration__:\n\n- __required__ `exec {String}` a path to the method that will be executed\n- `expire {String}` expiration, default `1 year`\n- `delay {Number}` a delay for executing method in milliseconds, default: `1000` ms\n\nUse it with `<ui-component name=\"onetime\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"onetime\" config=\"exec:onetimealert;expire:1 month\"></ui-component>\n\n<script>\n\n\tfunction onetimealert() {\n\t\talert('After a month, this message will appear again.');\n\t}\n\n</script>\n```"}],"knowledge":{"content_sha256":"46b792a460652756486b7bf8fdce0de9f2b5e4ee8901f75b9b6c81008d660709","source_sha256":"3f6453ab3d0c48b9d9bc0c40d6148f9646d76c4a0c17f875c375219584d13814","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"onetime\" config=\"exec:onetimealert;expire:1 month\"></ui-component>\n\n<script>\n\n\tfunction onetimealert() {\n\t\talert('After a month, this message will appear again.');\n\t}\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/packeta","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/packeta/","markdown_url":"https://forge.totaljs.es/en/components/packeta.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"e31687c709be6ecbece0caa8e53be24c064181716ada9f297a18980555762601","checked_at":"2026-10-20T12:22:50.649Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Packeta","description":"This component allows customers to select a [Packeta](https://www.packeta.com/) pickup point. After selection, a complete pickup point object is retrieved, which can be processed using a callback func","messages":[{"role":"user","content":"j-Packeta\n\nThis component allows customers to select a [Packeta](https://www.packeta.com/) pickup point. After selection, a complete pickup point object is retrieved, which can be processed using a callback func"},{"role":"assistant","content":"This component allows customers to select a [Packeta](https://www.packeta.com/) pickup point. After selection, a complete pickup point object is retrieved, which can be processed using a callback function.\n\n- https://www.packeta.com\n- singleton\n\n__How to call it?__\n\n```js\nvar opt = {};\nopt.callback = function(point) {\n\tconsole.log(point);\n};\nSETTER('packeta/show', opt);\n```\n\n__Output__:\n\n```json\n{\n\t\"businessDaysOpenLunchtime\": false,\n\t\"businessDaysOpenUpTo\": 0,\n\t\"city\": \"Badín\",\n\t\"claimAssistant\": false,\n\t\"country\": \"sk\",\n\t\"creditCardPayment\": true,\n\t\"currency\": \"EUR\",\n\t\"directions\": \"<p>Z-BOX sa nachádza v obci Badín vedľa obecného úradu.</p>\\n\\n<p><strong>Dobré vedieť:</strong></p>\\n\\n<p>Štandardná úložná doba zásielok je 2 dni s možnosťou predĺženia o ďalší 1 deň.</p>\\n\\n<p><strong>Čo potrebujete na vyzdvihnutie zásielky?</strong></p>\\n\\n<p>Aktuálnu verziu mobilnej aplikácie Packeta</p>\\n\\n<ul><li>zapnite si Bluetooth a polohové služby</li><li>uistite sa, že v mobilnej aplikácii aj v objednávke zásielky máte uvedené rovnaké telefónne číslo</li><li>dobierku uhradenú vopred</li></ul>\\n\\n<p>Vyzdvihujete zásielku na dobierku? Tú je treba uhradiť vopred priamo v aplikácii alebo cez odkaz na sledovanie zásielky, ktorý nájdete v e-maile.</p>\",\n\t\"directionsCar\": \"\",\n\t\"directionsPublic\": \"\",\n\t\"error\": null,\n\t\"gps\": {\n\t\t\"lat\": 48.6629,\n\t\t\"lon\": 19.12581\n\t},\n\t\"holidayEnd\": null,\n\t\"holidayStart\": null,\n\t\"exceptionDays\": [],\n\t\"externalId\": \"I_15491\",\n\t\"id\": \"15491\",\n\t\"isNew\": false,\n\t\"maxWeight\": 15,\n\t\"name\": \"Z-BOX Badín, Sládkovičova 4\",\n\t\"nameStreet\": \"Z-BOX Badín, Sládkovičova 4\",\n\t\"openingHours\": {\n\t\t\"compactShort\": \"<strong>Po–Ne</strong> Nonstop\",\n\t\t\"compactLong\": \"<strong>Po–Ne</strong> Nonstop\",\n\t\t\"tableLong\": \"<table class='packetery-hours'><tr><th>Po</th><td>Nonstop</td></tr><tr><th>Ut</th><td>Nonstop</td></tr><tr><th>St</th><td>Nonstop</td></tr><tr><th>Št</th><td>Nonstop</td></tr><tr><th>Pi</th><td>Nonstop</td></tr><tr><th>So</th><td>Nonstop</td></tr><tr><th>Ne</th><td>Nonstop</td></tr></table>\",\n\t\t\"exceptions\": [],\n\t\t\"regular\": {\n\t\t\t\"monday\": \"Nonstop\",\n\t\t\t\"tuesday\": \"Nonstop\",\n\t\t\t\"wednesday\": \"Nonstop\",\n\t\t\t\"thursday\": \"Nonstop\",\n\t\t\t\"friday\": \"Nonstop\",\n\t\t\t\"saturday\": \"Nonstop\",\n\t\t\t\"sunday\": \"Nonstop\"\n\t\t}\n\t},\n\t\"packetConsignment\": true,\n\t\"photos\": [\n\t\t{\n\t\t\t\"thumbnail\": \"https://files.packeta.com/points/thumb/point_15491_334b78451cdf.jpg\",\n\t\t\t\"normal\": \"https://files.packeta.com/points/normal/point_15491_334b78451cdf.jpg\"\n\t\t},\n\t\t{\n\t\t\t\"thumbnail\": \"https://files.packeta.com/points/thumb/point_15491_4041603c5372.jpg\",\n\t\t\t\"normal\": \"https://files.packeta.com/points/normal/point_15491_4041603c5372.jpg\"\n\t\t},\n\t\t{\n\t\t\t\"thumbnail\": \"https://files.packeta.com/points/thumb/point_15491_425a950aa53a.jpg\",\n\t\t\t\"normal\": \"https://files.packeta.com/points/normal/point_15491_425a950aa53a.jpg\"\n\t\t}\n\t],\n\t\"photo\": [\n\t\t{\n\t\t\t\"thumbnail\": \"https://files.packeta.com/points/thumb/point_15491_334b78451cdf.jpg\",\n\t\t\t\"normal\": \"https://files.packeta.com/points/normal/point_15491_334b78451cdf.jpg\"\n\t\t},\n\t\t{\n\t\t\t\"thumbnail\": \"https://files.packeta.com/points/thumb/point_15491_4041603c5372.jpg\",\n\t\t\t\"normal\": \"https://files.packeta.com/points/normal/point_15491_4041603c5372.jpg\"\n\t\t},\n\t\t{\n\t\t\t\"thumbnail\": \"https://files.packeta.com/points/thumb/point_15491_425a950aa53a.jpg\",\n\t\t\t\"normal\": \"https://files.packeta.com/points/normal/point_15491_425a950aa53a.jpg\"\n\t\t}\n\t],\n\t\"place\": \"Z-BOX\",\n\t\"recommended\": \"quickBox\",\n\t\"saturdayOpenTo\": 0,\n\t\"special\": \"\",\n\t\"street\": \"Sládkovičova 4\",\n\t\"sundayOpenTo\": 0,\n\t\"url\": \"https://www.zasilkovna.cz/pobocky/z-box-badin-sladkovicova-4\",\n\t\"branchCode\": \"z-box-badin-sladkovicova-4\",\n\t\"warning\": \"almostFullBox\",\n\t\"wheelchairAccessible\": false,\n\t\"zip\": \"976 32\",\n\t\"pickupPointType\": \"internal\",\n\t\"carrierPickupPointId\": null,\n\t\"carrierId\": null,\n\t\"routingCode\": \"S96-301-15491\",\n\t\"routingName\": \"Z-BOX Badín, Sládkovičova 4\",\n\t\"group\": \"zbox\"\n}\n```\n\nUse it with `<ui-component name=\"packeta\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Dodo Marton. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"packeta\"></ui-component>\n\n<button>Select pickup point</button>\n\n<ui-bind path=\"!response\" config=\"html pre:JSON.stringify(value,null,'\\t').encode()\"><pre></pre></ui-bind>\n\n<script>\n\n\t$(document).on('click', 'button', function() {\n\t\tvar opt = {};\n\t\topt.callback = function(point) {\n\t\t\tSET('response', point);\n\t\t};\n\t\tSETTER('packeta/show', opt);\n\t});\n\n</script>\n```"}],"knowledge":{"content_sha256":"fbfc6d2efbabc4053ed3af2e686fa61c74046e7e5601d6843680d5eb65ca465f","source_sha256":"e31687c709be6ecbece0caa8e53be24c064181716ada9f297a18980555762601","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["BOX","CDN","DEF","EUR","I_15491","JSON","MIT","S96","SET","SETTER"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"packeta\"></ui-component>\n\n<button>Select pickup point</button>\n\n<ui-bind path=\"!response\" config=\"html pre:JSON.stringify(value,null,'\\t').encode()\"><pre></pre></ui-bind>\n\n<script>\n\n\t$(document).on('click', 'button', function() {\n\t\tvar opt = {};\n\t\topt.callback = function(point) {\n\t\t\tSET('response', point);\n\t\t};\n\t\tSETTER('packeta/show', opt);\n\t});\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/page","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/page/","markdown_url":"https://forge.totaljs.es/en/components/page.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"31d5a978f67c12963794122bcdc265708f3e734e593b629103610f4251362e14","checked_at":"2026-10-20T12:22:51.536Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Page","description":"This component can handle different contents asynchronously. The component has same functionality like j-Part. If the element doesn't contain any content then the component downloads the content accor","messages":[{"role":"user","content":"j-Page\n\nThis component can handle different contents asynchronously. The component has same functionality like j-Part. If the element doesn't contain any content then the component downloads the content accor"},{"role":"assistant","content":"This component can handle different contents asynchronously. The component has same functionality like `j-Part`. __If the element doesn't contain any content__ then the component downloads the content according to the `url` defined in configuration.\n\n- __NEW__ `path` can contain `{String Array}`\n\n__Configuration__:\n\n- `url {String}` required, a relative URL address\n- `if {String}` required, condition, it's compared with the value within of `path` (__NEW__: supports multiple paths divided by the `comma`)\n- `reload {String}` optional, a link to function `function(init) {}`, it's executed when the page is visible (always)\n- `hidden {String}` optional, a link to function, it's executed when the page is hidden (always)\n- `init {String}` optional, a link to function, it's executed when the page is visible and onetime\n- `default {String}` optional, a short alias for `DEFAULT(default)`\n- `hide {Boolean}` optional, auto-hide element if the `if` condition is not valid (default: `true`)\n- `cleaner {Number}` optional, idle time (in minutes) for running of cleaning (default: `0`)\n- `clean {String}` optional, a link to function, it's executed before the page is cleaned\n- `loading {Boolean}` optional, enables loading via `SETTER('loading')` (default: `true`)\n- `path {String}` optional, the component replace all `~PATH~` and `CLASS` phrases for the value of the `path` in the downloaded template\n- `replace {String}` optional, a link to method `function(content) { return content }` which can modify downloaded template\n- `absolute {Boolean}` optional, enables absolute position (default `false`)\n- `check {String}` optional, a link to function `function(next) { next(); }`, it's executed when the page is wanting to show\n- `invisible {Boolean}` enables adding of `invisible` class when the page is going to hide (default: `false`)\n- `delay {Number}` a delay (in ms) for removing of `invisible` class and hiding of `loading` (default: `500`)\n- `autofocus {Boolean/String}` focuses the first input, textarea (default: `false`)\n- `delayloading {Number}` a delay (in ms) for hiding of of `loading` (default: `800`)\n- `id {String}` a custom identifier for replacing `~ID~` phrases in the imported HTML (default: empty)\n\n__Good to know 1__:\n\n- all `CLASS` and `~PATH~` phrases will be replaced by `config.path` or `config.if` automatically\n- all `~ID~` phrases will be replaced by `config.id` automatically\n\n__Good to know 2__:\n\nPage component emits an `event` in the form: `pages.` + `config.if` for extending of pages. This can be very usefull for some plugin systems. Example:\n\n__Good to know 3__:\n\nIf the page component isn't within scope then the value from the `config.if` will replace `?` characters in all config keys.\n\n```js\nON('pages.YOURIFCONDITION', function(element, pagecomponent) {\n\t// It's executed only when the page is initialized\n});\n```\n\nUse it with `<ui-component name=\"page\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"page\" path=\"currentpage\" config=\"if:home;url:/bla/bla/home.html\"></ui-component>\n<ui-component name=\"page\" path=\"currentpage\" config=\"if:services;url:/bla/bla/services.html;reload:services_reload\"></ui-component>\n<ui-component name=\"page\" path=\"currentpage\" config=\"if:contact;url:/bla/bla/contact.html;reload:contact_reload\"></ui-component>\n\n<pre><code>SET(&#39;currentpage&#39;, &#39;home&#39;);</code></pre>\n\n<script>\n\tvar currentpage = '';\n</script>\n```"}],"knowledge":{"content_sha256":"4c9bc3a6b340c84cf238488d0ffc2d0c9cd1f737c3ad50b2f5d0a66615ebc92a","source_sha256":"31d5a978f67c12963794122bcdc265708f3e734e593b629103610f4251362e14","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","CLASS","DEF","DEFAULT","HTML","MIT","PATH","SET","SETTER","URL","YOURIFCONDITION"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"page\" path=\"currentpage\" config=\"if:home;url:/bla/bla/home.html\"></ui-component>\n<ui-component name=\"page\" path=\"currentpage\" config=\"if:services;url:/bla/bla/services.html;reload:services_reload\"></ui-component>\n<ui-component name=\"page\" path=\"currentpage\" config=\"if:contact;url:/bla/bla/contact.html;reload:contact_reload\"></ui-component>\n\n<pre><code>SET(&#39;currentpage&#39;, &#39;home&#39;);</code></pre>\n\n<script>\n\tvar currentpage = '';\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/pages","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/pages/","markdown_url":"https://forge.totaljs.es/en/components/pages.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"6f053ed478260935024d7365a1a2d5af473e7e3033716aa084e72a78438350ab","checked_at":"2026-10-20T12:22:52.418Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Pages","description":"The component is a better alternative to the j-Section component. As a result, you can easily make a multi-page app with dynamic content loading when needed. The result looks very intuitive for mobile","messages":[{"role":"user","content":"j-Pages\n\nThe component is a better alternative to the j-Section component. As a result, you can easily make a multi-page app with dynamic content loading when needed. The result looks very intuitive for mobile"},{"role":"assistant","content":"The component is a better alternative to the `j-Section` component. As a result, you can easily make a multi-page app with dynamic content loading when needed. The result looks very intuitive for mobile devices or desktop computers.\n\n\n__Main configuration__:\n\n- `margin {Number/String}` a top margin (default: `0`)\n\t- `auto` will applied `offset.top` value\n- `margintype {String}` can contain `offset` (default) or `position` value\n- `delay {Number}` animation delay (default: `220`)\n- `parent` {String} optional, a selector to an element with a fixed height (default: `parent` element)\n- `scrollbarshadow {Boolean}` enables shadow for the scrollbar (default: `false`)\n- `loading {String}` optional, a link to the `function(show_loading {Boolean})`\n\n__Section attribute configuration `data-config`__:\n\n- `if {String}` a condition for displaying of page\n- `url {String}` URL address for getting HTML content\n- `path {String}` the value will replace `~PATH~` pharses (default: `config.if`)\n- `id {String}` the value will replace `~ID~` pharses\n- `anim {String}` animation type, supported values `left` (default), `right`, `top` or `bottom`\n- `scrollbar {Boolean}` appends a custom scrollbar into the content (default: `true`)\n- `scrolltop {Boolean}` enables auto-scroll\n- `reload {String}` a link to the `function(el)`, it's executed when the page is displayed\n- `hide {String}` a link to the `function(el)`, it's executed when the page is hidden\n- `check {String}` a link to the `function(next, el)`\n\t- `next {Function}` continues with page displaying\n- `autofocus {Boolean}` performs autofocus for the current page\n\nUse it with `<ui-component name=\"pages\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\tbody { padding: 0; margin: 0; overflow: hidden; }\n</style>\n\n<ui-component name=\"pages\" path=\"common.page\" config=\"parent:window\">\n\n\t<section class=\"hidden invisible\" data-config=\"if:page1;anim:left\">\n\t\t<div class=\"padding\">\n\t\t\t<div style=\"background-color:#F0F0F0;margin:30px auto;padding:30px;max-width:600px;border-radius:10px\">\n\t\t\t\tPAGE 1\n\t\t\t</div>\n\t\t</div>\n\t</section>\n\n\t<section class=\"hidden invisible\" data-config=\"if:page2;anim:right\">\n\t\t<div class=\"padding\">\n\t\t\t<div style=\"background-color:#F0F0F0;margin:30px auto;padding:30px;max-width:600px;border-radius:10px\">\n\t\t\t\tPAGE 2\n\t\t\t</div>\n\t\t</div>\n\t</section>\n\n\t<section class=\"hidden invisible\" data-config=\"if:page3;anim:bottom\">\n\t\t<div class=\"padding\">\n\t\t\t<div style=\"background-color:#F0F0F0;margin:30px auto;padding:30px;max-width:600px;border-radius:10px\">\n\t\t\t\tPAGE 3\n\t\t\t</div>\n\t\t</div>\n\t</section>\n</ui-component>\n\n\n<script>\n\n\tvar common = {};\n\n\tfunction togglepage() {\n\n\t\tvar val = common.page;\n\n\t\tswitch (val) {\n\t\t\tcase 'page1':\n\t\t\t\tval = 'page2';\n\t\t\t\tbreak;\n\t\t\tcase 'page2':\n\t\t\t\tval = 'page3';\n\t\t\t\tbreak;\n\t\t\tcase 'page3':\n\t\t\tdefault:\n\t\t\t\tval = 'page1';\n\t\t\t\tbreak;\n\t\t}\n\n\t\tSET('common.page', val);\n\t}\n\n\tsetInterval(togglepage, 2000);\n\ttogglepage();\n\n</script>\n```"}],"knowledge":{"content_sha256":"6fc87ca08d4fc1bc13e3789f7f73aa6203c260485656fea12680fcaf5f910c74","source_sha256":"6f053ed478260935024d7365a1a2d5af473e7e3033716aa084e72a78438350ab","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","F0F0F0","HTML","MIT","PAGE","PATH","SET","URL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\tbody { padding: 0; margin: 0; overflow: hidden; }\n</style>\n\n<ui-component name=\"pages\" path=\"common.page\" config=\"parent:window\">\n\n\t<section class=\"hidden invisible\" data-config=\"if:page1;anim:left\">\n\t\t<div class=\"padding\">\n\t\t\t<div style=\"background-color:#F0F0F0;margin:30px auto;padding:30px;max-width:600px;border-radius:10px\">\n\t\t\t\tPAGE 1\n\t\t\t</div>\n\t\t</div>\n\t</section>\n\n\t<section class=\"hidden invisible\" data-config=\"if:page2;anim:right\">\n\t\t<div class=\"padding\">\n\t\t\t<div style=\"background-color:#F0F0F0;margin:30px auto;padding:30px;max-width:600px;border-radius:10px\">\n\t\t\t\tPAGE 2\n\t\t\t</div>\n\t\t</div>\n\t</section>\n\n\t<section class=\"hidden invisible\" data-config=\"if:page3;anim:bottom\">\n\t\t<div class=\"padding\">\n\t\t\t<div style=\"background-color:#F0F0F0;margin:30px auto;padding:30px;max-width:600px;border-radius:10px\">\n\t\t\t\tPAGE 3\n\t\t\t</div>\n\t\t</div>\n\t</section>\n</ui-component>\n\n\n<script>\n\n\tvar common = {};\n\n\tfunction togglepage() {\n\n\t\tvar val = common.page;\n\n\t\tswitch (val) {\n\t\t\tcase 'page1':\n\t\t\t\tval = 'page2';\n\t\t\t\tbreak;\n\t\t\tcase 'page2':\n\t\t\t\tval = 'page3';\n\t\t\t\tbreak;\n\t\t\tcase 'page3':\n\t\t\tdefault:\n\t\t\t\tval = 'page1';\n\t\t\t\tbreak;\n\t\t}\n\n\t\tSET('common.page', val);\n\t}\n\n\tsetInterval(togglepage, 2000);\n\ttogglepage();\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/panel","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/panel/","markdown_url":"https://forge.totaljs.es/en/components/panel.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"ddd9c8058041a7c8e7e64aba4d7e03f2abe51829f79a136c504485dac1b88796","checked_at":"2026-10-20T12:22:53.304Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Panel","description":"New version of this component supports dynamic evaluation of the content of <script type='text/html'>. The example below contains a script with HTML and the component evaluates the content if the j-Pa","messages":[{"role":"user","content":"j-Panel\n\nNew version of this component supports dynamic evaluation of the content of <script type='text/html'>. The example below contains a script with HTML and the component evaluates the content if the j-Pa"},{"role":"assistant","content":"- supports __dark mode__\n\n__Configuration__:\n\n- `title {String}` window title\n- `if {String}` condition for showing of the window, it's compared with the value within of `path`\n- `icon {String}` Total icon without `ti-`\n- `reload {String}` link to a global function and it's executed if the form is displaying\n- `enter {Boolean}` optional, captures `enter` key automatically and performs submit (default: `false`)\n- `autofocus {Boolean/String}` optional, can focus an input. `String` === `jQuery selector` for the input\n- `default {String}` optional, a short alias for `DEFAULT(default, true)`\n- `closebutton {Boolean}` optional, can hide `x` button (default: `false`)\n- `width {Number}` optional, a default width of the panel (default: `300`)\n- `bg {Boolean}` optional, can disable `background` (default: `true`)\n- `bgclose {Boolean}` optional, if the user click on the background then the panel will be closed (default: `false`)\n- `zindex {Number}` optional, can affect z-index (default: `12`)\n- `close {String}` optional, path/method is executed as `path_to_method(com)` when the user closes the panel manually via close button\n- `scrollbar {Boolean}` optional, enables custom scrollbar (default: `true`)\n- `scrollbarY {Boolean}` optional, shows vertical custom scrollbar (default: `true`)\n- `scrollbarshadow {Boolean}` optional, enables a shadow in scrollbar (default: `false`)\n- `menu {String}` optional, a link to function (panel will show a button with burger icon in title bar)\n- `container {String}` optional, jQuery selector (this container means a container for scrollbar)\n- `margin {Number}` optional, a margin for `container` and its `scrollbar` (it works with `container` only)\n- `closeicon {String}` optional, Font-Awesome icon (default: `ti ti-times`)\n- __NEW__ `closeesc {Boolean}` closes the form when the user presses `ESC` key\n\n__Good to know__:\n\nNew version of this component supports dynamic evaluation of the content of `<script type=\"text/html\">`. The example below contains a script with HTML and the component evaluates the content if the j-Panel will be displayed (only once).\n\n```html\n<ui-component name=\"panel\" path=\"path\" config=\"config\">\n\t<script type=\"text/html\">\n\t\tA CONTENT\n\t</script>\n</ui-component>\n```\n\nUse it with `<ui-component name=\"panel\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"panel\" path=\"common.panel\" config=\"if:user;title:Title of this panel\" class=\"hidden\">\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n</ui-component>\n\n<script>\n\tvar common = {};\n\tcommon.panel = 'user';\n</script>\n```"}],"knowledge":{"content_sha256":"327abb9fbd3b241a1d728ff0b50f140b54bbbb76ac7933cf098e3ad60538b5e8","source_sha256":"ddd9c8058041a7c8e7e64aba4d7e03f2abe51829f79a136c504485dac1b88796","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","CONTENT","DEF","DEFAULT","ESC","HTML","MIT","SOME"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"panel\" path=\"common.panel\" config=\"if:user;title:Title of this panel\" class=\"hidden\">\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n</ui-component>\n\n<script>\n\tvar common = {};\n\tcommon.panel = 'user';\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/parameters","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/parameters/","markdown_url":"https://forge.totaljs.es/en/components/parameters.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"ddb3cace0e8c8b14530eb303e7b7aa9abec8107427c15c2818d6c09211ecc362","checked_at":"2026-10-20T12:22:54.051Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Parameters","description":"This component can changed internal parameters.","messages":[{"role":"user","content":"j-Parameters\n\nThis component can changed internal parameters."},{"role":"assistant","content":"This component can changed internal parameters.\n\n\n__Configuration__:\n\n- `dateformat {String}` optional, a date format (default: `yyyy-MM-dd`)\n- `search {String}` optional, placeholder for search input (default: `Search`)\n- `height {Number}` optional, a height of the element (default: `undefined`)\n- `parent {String}` optional, jQuery selector for auto setup of height (can be `window`, `parent` or `custom`)\n- `hidetype {Boolean}` optional, hides type (default: `false`)\n- `margin {Number}` optional, vertical margin (default: `0`)\n\n__Data declaration__:\n\n```javascript\nvar form = [];\nform.push({ name: 'user.firstname', label: 'First name', type: 'string', value: 'Peter Sirka' });\nform.push({ name: 'user.age', type: 'number', value: 33, min: 18, max: 50 });\nform.push({ name: 'user.birth', type: 'date', value: NOW });\nform.push({ name: 'user.newsletter', type: 'boolean', value: true });\n```\n\n__Allowed types in data__:\n\n- `string`\n- `boolean`\n- `number`\n- `date`\n\nUse it with `<ui-component name=\"parameters\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"parameters\" path=\"form\" config=\"height:200\"></ui-component>\n\n<script>\n\tvar form = [];\n\tform.push({ name: 'user.firstname', type: 'string', value: 'Peter' });\n\tform.push({ name: 'user.lastname', type: 'string', placeholder: 'String' });\n\tform.push({ name: 'user.age', type: 'number', value: 33 });\n\tform.push({ name: 'user.birth', type: 'date', value: NOW });\n\tform.push({ name: 'user.newsletter', type: 'boolean', value: true });\n</script>\n```"}],"knowledge":{"content_sha256":"1111e4d3ac7d5b918a0113e35abcec5a16a17672b77ef380f464a7ca698d4183","source_sha256":"ddb3cace0e8c8b14530eb303e7b7aa9abec8107427c15c2818d6c09211ecc362","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","NOW"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"parameters\" path=\"form\" config=\"height:200\"></ui-component>\n\n<script>\n\tvar form = [];\n\tform.push({ name: 'user.firstname', type: 'string', value: 'Peter' });\n\tform.push({ name: 'user.lastname', type: 'string', placeholder: 'String' });\n\tform.push({ name: 'user.age', type: 'number', value: 33 });\n\tform.push({ name: 'user.birth', type: 'date', value: NOW });\n\tform.push({ name: 'user.newsletter', type: 'boolean', value: true });\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/part","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/part/","markdown_url":"https://forge.totaljs.es/en/components/part.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"ec7c2a0bc1e608bad98abbbc499ed6ca87e0529c911b892fa045e49815ee1074","checked_at":"2026-10-20T12:22:54.959Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Part","description":"This component can handle different contents asynchronously. If the element doesn't contain any content then the component downloads the content according to the url defined in configuration.","messages":[{"role":"user","content":"j-Part\n\nThis component can handle different contents asynchronously. If the element doesn't contain any content then the component downloads the content according to the url defined in configuration."},{"role":"assistant","content":"This component can handle different contents asynchronously. __If the element doesn't contain any content__ then the component downloads the content according to the `url` defined in configuration.\n\n- __NEW__ `path` can contain `{String Array}`\n\n__Configuration__:\n\n- `url {String}` required, a relative URL address\n- `if {String}` required, condition, it's compared with the value within of `path` (__NEW__: supports multiple paths divided by the `comma`)\n- `reload {String}` optional, a link to function `function(init) {}`, it's executed when the part is visible (always)\n- `hidden {String}` optional, a link to function, it's executed when the part is hidden (always)\n- `init {String}` optional, a link to function, it's executed when the part is visible and onetime\n- `default {String}` optional, a short alias for `DEFAULT(default)`\n- `hide {Boolean}` optional, auto-hide element if the `if` condition is not valid (default: `true`)\n- `cleaner {Number}` optional, idle time (in minutes) for running of cleaning (default: `0`)\n- `clean {String}` optional, a link to function, it's executed before the part is cleaned\n- `loading {Boolean}` optional, enables loading via `SETTER('loading')` (default: `true`)\n- `path {String}` optional, the component replace all `~PATH~` and `CLASS` phrases for the value of the `path` in the downloaded template\n- `replace {String}` optional, a link to method `function(content) { return content }` which can modify downloaded template\n- `absolute {Boolean}` optional, enables absolute position (default `false`)\n- `check {String}` optional, a link to function `function(next) { next(); }`, it's executed when the part is wanting to show\n- `invisible {Boolean}` enables adding of `invisible` class when the part is going to hide (default: `false`)\n- `delay {Number}` a delay (in ms) for removing of `invisible` class and hiding of `loading` (default: `500`)\n- `autofocus {Boolean/String}` focuses the first input, textarea (default: `false`)\n- `delayloading {Number}` a delay (in ms) for hiding of of `loading` (default: `800`)\n- `id {String}` a custom identifier for replacing `~ID~` phrases in the imported HTML (default: empty)\n\n__Good to know 1__:\n\n- all `CLASS` and `~PATH~` phrases will be replaced by `config.path` or `config.if` automatically\n- all `~ID~` phrases will be replaced by `config.id` automatically\n\n__Good to know 2__:\n\nPart component emits an `event` in the form: `parts.` + `config.if` for extending of parts. This can be very usefull for some plugin systems. Example:\n\n__Good to know 3__:\n\nIf the part component isn't within scope then the value from the `config.if` will replace `?` characters in all config keys.\n\n```js\nON('parts.YOURIFCONDITION', function(element, pagecomponent) {\n\t// It's executed only when the part is initialized\n});\n```\n\nUse it with `<ui-component name=\"part\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"part\" path=\"currentpart\" config=\"if:home;url:/bla/bla/home.html\"></ui-component>\n<ui-component name=\"part\" path=\"currentpart\" config=\"if:services;url:/bla/bla/services.html;reload:services_reload\"></ui-component>\n<ui-component name=\"part\" path=\"currentpart\" config=\"if:contact;url:/bla/bla/contact.html;reload:contact_reload\"></ui-component>\n\n<pre><code>SET(&#39;currentpart&#39;, &#39;home&#39;);</code></pre>\n\n<script>\n\tvar currentpart = '';\n</script>\n```"}],"knowledge":{"content_sha256":"62c58b5dc9ad6380b08cca03fa06d8f66c25bd1b34c41d2fbca3df357975abe0","source_sha256":"ec7c2a0bc1e608bad98abbbc499ed6ca87e0529c911b892fa045e49815ee1074","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","CLASS","DEF","DEFAULT","HTML","MIT","PATH","SET","SETTER","URL","YOURIFCONDITION"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"part\" path=\"currentpart\" config=\"if:home;url:/bla/bla/home.html\"></ui-component>\n<ui-component name=\"part\" path=\"currentpart\" config=\"if:services;url:/bla/bla/services.html;reload:services_reload\"></ui-component>\n<ui-component name=\"part\" path=\"currentpart\" config=\"if:contact;url:/bla/bla/contact.html;reload:contact_reload\"></ui-component>\n\n<pre><code>SET(&#39;currentpart&#39;, &#39;home&#39;);</code></pre>\n\n<script>\n\tvar currentpart = '';\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/parts","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/parts/","markdown_url":"https://forge.totaljs.es/en/components/parts.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"79ccec26c2510ef3d86d6f2da391f91d17c7f517b21ec27bcc728abdde7ddfc4","checked_at":"2026-10-20T12:22:55.698Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Parts","description":"This content can handle multiple parts stored in the Array and focus on one specific part. It simulates something like browser tabs.","messages":[{"role":"user","content":"j-Parts\n\nThis content can handle multiple parts stored in the Array and focus on one specific part. It simulates something like browser tabs."},{"role":"assistant","content":"This content can handle multiple `parts` stored in the `Array` and focus on one specific part. It simulates something like browser tabs.\n\n\n__Configuration__:\n\n- `parent {String}` parent area due to part size (default: `auto`)\n- `margin {Number}` vertical margin (default: `0`)\n- `create {String}` a link to the `function(part)`, it's executed if the part is created (called once)\n- `focus {String}` a link to the `function(part)`, it's executed if the part is focused\n- `close {String}` a link to the `function(part)`, it's executed if the part is closed\n\n__Good to know 1__:\n\n- all `~PATH~` phrases will be replaced by `item.path` automatically\n- all `~ID~` phrases will be replaced by `item.id` automatically\n\n__Item specification__:\n\n- `item.id {String}` very important, part identifier\n- `item.blur {String/Function(el, item)}` optional, a link to the `function(element, item)` if the part is focused out\n- `item.focus {String/Function(el, item)}` or `item.reload {String}` optional, a link to the `function(element, item)` if the part is focused\n- `item.remove {String/Function(el, item)}` optional, a link to the `function(element, item)` if the part is removed\n- `item.html {String}` a HTML content or use `item.url`\n- `item.import {String}` URL address for the part content\n- `item.path {String}` optional, it replaces all `~PATH~` phrases automatically\n- `item.delay {Number}` a delay for removing of `invisible` class\n- __NEW__ `item.attrd {Object}` adds `data-key` attributes into the item element\n\n__Extendend by the component__:\n\n- `item.focused {Boolean}` determines if the part is focused or not\n- `item.element {jQuery}` part element\n\n__Methods__:\n\n- `component.close(id)` closes part\n- `component.focus(id)` focuses part\n- `component.rename(id, name, [icon])` renames part\n\nUse it with `<ui-component name=\"parts\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"exec\">\n\n\t<ui-bind path=\"myparts\" config=\"template\" class=\"block\">\n\t\t<script type=\"text/html\">\n\t\t\t{{ foreach m in value }}\n\t\t\t<button class=\"exec\" data-exec=\"changepart\" data-id=\"{{ m.id }}\"><i class=\"ti ti-times exec mr5\" data-prevent=\"true\" data-exec=\"closepart\"></i>{{ m.name }}</button>\n\t\t\t{{ end }}\n\t\t</script>\n\t</ui-bind>\n\t<br />\n\n\t<div style=\"width:600px;height:300px;border:1px solid #E0E0E0\">\n\t\t<ui-component name=\"parts\" path=\"myparts\" config=\"parent:auto\"></ui-component>\n\t</div>\n\n</ui-component>\n\n<script>\n\tvar myparts = [];\n\n\tmyparts.push({ id: '1', name: 'Part 1', html: '<div class=\"padding\">Part 1</div>' });\n\tmyparts.push({ id: '2', name: 'Part 2', html: '<div class=\"padding\">Part 2</div>' });\n\tmyparts.push({ id: '3', name: 'Part 3', html: '<div class=\"padding\">Part 3</div>' });\n\n\tfunction changepart(el ,e) {\n\t\tvar id = ATTRD(el);\n\t\tSETTER('parts/focus', id);\n\t}\n\n\tfunction closepart(el, e) {\n\t\tvar id = ATTRD(el);\n\t\tSETTER('parts/close', id);\n\t}\n\n</script>\n```"}],"knowledge":{"content_sha256":"f2f1ea1ed934bd8920a87933c31abd87111e4b20626007d94ad7537f82ebcdd8","source_sha256":"79ccec26c2510ef3d86d6f2da391f91d17c7f517b21ec27bcc728abdde7ddfc4","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["ATTRD","CDN","DEF","E0E0E0","HTML","MIT","PATH","SETTER","URL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"exec\">\n\n\t<ui-bind path=\"myparts\" config=\"template\" class=\"block\">\n\t\t<script type=\"text/html\">\n\t\t\t{{ foreach m in value }}\n\t\t\t<button class=\"exec\" data-exec=\"changepart\" data-id=\"{{ m.id }}\"><i class=\"ti ti-times exec mr5\" data-prevent=\"true\" data-exec=\"closepart\"></i>{{ m.name }}</button>\n\t\t\t{{ end }}\n\t\t</script>\n\t</ui-bind>\n\t<br />\n\n\t<div style=\"width:600px;height:300px;border:1px solid #E0E0E0\">\n\t\t<ui-component name=\"parts\" path=\"myparts\" config=\"parent:auto\"></ui-component>\n\t</div>\n\n</ui-component>\n\n<script>\n\tvar myparts = [];\n\n\tmyparts.push({ id: '1', name: 'Part 1', html: '<div class=\"padding\">Part 1</div>' });\n\tmyparts.push({ id: '2', name: 'Part 2', html: '<div class=\"padding\">Part 2</div>' });\n\tmyparts.push({ id: '3', name: 'Part 3', html: '<div class=\"padding\">Part 3</div>' });\n\n\tfunction changepart(el ,e) {\n\t\tvar id = ATTRD(el);\n\t\tSETTER('parts/focus', id);\n\t}\n\n\tfunction closepart(el, e) {\n\t\tvar id = ATTRD(el);\n\t\tSETTER('parts/close', id);\n\t}\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/passwordmeter","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/passwordmeter/","markdown_url":"https://forge.totaljs.es/en/components/passwordmeter.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"56b6812d4156efdc0a821c70a9f8501f07eee7e38a960528cb2cf9ba3b7b7037","checked_at":"2026-10-20T12:22:56.437Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-PasswordMeter","description":"This component evaluates password according to the rating. Rating is counted as a percentage: 100% = Strong, 50% = Good, 0% = Weak.","messages":[{"role":"user","content":"j-PasswordMeter\n\nThis component evaluates password according to the rating. Rating is counted as a percentage: 100% = Strong, 50% = Good, 0% = Weak."},{"role":"assistant","content":"__Configuration__:\n\n- `numbers {Boolean}` password must contain min. 1 numbers (default: `true`)\n- `chars {Boolean}` password must contain min. 1 chars (default: `true`)\n- `special {Boolean}` password must contain min. 1 special char (default: `false`)\n- `min {Number}` password must have min. length of characters (default: `4`)\n- `casesensitive {Boolean}` password must contain min. 1 lower and upper char (default: `true`)\n- `weak {String}` a label for weak password (default: `Weak`)\n- `good {String}` a label for good password (default: `Good`)\n- `strong {String}` a label for strong password (default: `Strong`)\n- `short {String}` a label for short password (default: `Short password`)\n- `text {String}` a label for label (default: `Password strength`)\n- `valid {Number}` min. valid criteria in percentage (default: `40`)\n- __NEW__ `showprogress {Boolean}` shows a progress bar (default: `true`)\n- __NEW__ `progress {String}` a path to `function({ progress: Number, rating: Number, numbers: Number, chars: Number, special: Number, upper: Number, lower: Number, unicode: Number, points: Number })`\n\n__Good to know__:\nThis component evaluates password according to the rating. Rating is counted as a percentage: `100%` = Strong, `50%` = Good, `0%` = Weak.\n\n- `weak` rating is lower than `30`\n- `good` rating is between `30` and `70`\n- `strong` rating is greater than `70`\n\nUse it with `<ui-component name=\"passwordmeter\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"width:300px\">\n\t<ui-component name=\"passwordmeter\" path=\"mypassword1\"></ui-component>\n\t<br />\n\t<ui-component name=\"passwordmeter\" path=\"mypassword2\"></ui-component>\n\t<br />\n\t<ui-component name=\"passwordmeter\" path=\"mypassword3\"></ui-component>\n</div>\n\n<script>\n\tvar mypassword1 = '123456';\n\tvar mypassword2 = 'abcd';\n\tvar mypassword3 = 'Ad35*t84';\n</script>\n```"}],"knowledge":{"content_sha256":"7ab771cae19b0f95e46f6f02f065c4fc86106277c99458332d5229ead3412936","source_sha256":"56b6812d4156efdc0a821c70a9f8501f07eee7e38a960528cb2cf9ba3b7b7037","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"width:300px\">\n\t<ui-component name=\"passwordmeter\" path=\"mypassword1\"></ui-component>\n\t<br />\n\t<ui-component name=\"passwordmeter\" path=\"mypassword2\"></ui-component>\n\t<br />\n\t<ui-component name=\"passwordmeter\" path=\"mypassword3\"></ui-component>\n</div>\n\n<script>\n\tvar mypassword1 = '123456';\n\tvar mypassword2 = 'abcd';\n\tvar mypassword3 = 'Ad35*t84';\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/paste","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/paste/","markdown_url":"https://forge.totaljs.es/en/components/paste.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"4a86d689a6d5b0096364d4c2fe62c74146fc4c6d7f91467afe59eea9ff11dd57","checked_at":"2026-10-20T12:22:57.339Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Paste","description":"The component captures paste event.","messages":[{"role":"user","content":"j-Paste\n\nThe component captures paste event."},{"role":"assistant","content":"The component captures `paste` event.\n\n```js\nON('paste', function(text, e) {\n\t// @text {String}\n});\n```\n\nUse it with `<ui-component name=\"paste\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"paste\"></ui-component>\n\n<script>\n    ON('paste', function(text, e) {\n        alert(text);\n    });\n</script>\n```"}],"knowledge":{"content_sha256":"eee3f0ef9cf099751bd9ab197b66008e609f12abe453739d8e197f3fec3a0e35","source_sha256":"4a86d689a6d5b0096364d4c2fe62c74146fc4c6d7f91467afe59eea9ff11dd57","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"paste\"></ui-component>\n\n<script>\n    ON('paste', function(text, e) {\n        alert(text);\n    });\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/period","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/period/","markdown_url":"https://forge.totaljs.es/en/components/period.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"2ceb8453ca3f4458d2175091f1f3898c785a2e5addfcd6518e51ad657c7417f7","checked_at":"2026-10-20T12:23:04.506Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Period","description":"The component for picking a time period.","messages":[{"role":"user","content":"j-Period\n\nThe component for picking a time period."},{"role":"assistant","content":"The component for picking a time period.\n\n- singleton\n\n__Methods__:\n\nMethod: `component.show(options)`\n\n- `options.element` {Element/jQuery Element} - all allignment calculations will be relative to this element\n- `options.align` Align the component relatively to the element in `options.element` property. Available properties: 'center', 'left' and 'right'. The default value - 'right'\n- `options.callback` Function, which will be executed by clicking on 'Apply' button of the component - receives two parameters: start and end date objects\n- `options.start`, `options.end` {Date} - In order to predifine selected period, **both** of these properties should be specified. \n\n\n__Configuration__:\n\n- `firstday` - {String} - Allows to set the first day of the week to Monday. The defaul value is 'monday' - Days of the week start from Monday. 'sunday' -  days of the week start from Sunday.\n- `dateformat` - {String} - Specifies the date format. The default value - `DEF.dateformat`\n- `apply` - {String} - Label for \"Apply\" button\n- `cancel` - {String} - Label for \"Cancel\" button\n- `custom` - {String} - Label for \"Custom\" filter button\n- `month` - {String} - Label for \"Current month\" filter button\n- `thisY` - {String} - Label for \"This year\" filter button\n- `lastY` - {String} - Label for \"Last year\" filter button\n\nUse it with `<ui-component name=\"period\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Kirill Zlobin. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"LAZY period\"></ui-component>\n\n<button class=\"btn\">Click</button>\n\n<script>\n\n    $(document).on('click', '.btn', function() {\n        var opt = {};\n        opt.element = $(this);\n        opt.align = 'left';\n        opt.callback = function(start, end) {\n            console.log(start);\n            console.log(end);\n        };\n        SETTER('period/show', opt);\n    });\n\n\n</script>\n```"}],"knowledge":{"content_sha256":"189fe23bf900cdd3bc9fd779fddfb2e2b5f012d90e7508c4f7e657d3ea71f614","source_sha256":"2ceb8453ca3f4458d2175091f1f3898c785a2e5addfcd6518e51ad657c7417f7","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","LAZY","MIT","SETTER"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"LAZY period\"></ui-component>\n\n<button class=\"btn\">Click</button>\n\n<script>\n\n    $(document).on('click', '.btn', function() {\n        var opt = {};\n        opt.element = $(this);\n        opt.align = 'left';\n        opt.callback = function(start, end) {\n            console.log(start);\n            console.log(end);\n        };\n        SETTER('period/show', opt);\n    });\n\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/permissions","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/permissions/","markdown_url":"https://forge.totaljs.es/en/components/permissions.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"2ece4fa07953edd6188a123c5e8b4fc65619344c569ac730c3d0d408dc5b1f59","checked_at":"2026-10-20T12:23:05.436Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Permissions","description":"j-Permissions: - jComponent v19|v20; NEW: exec {String} optional, it executes a method (path.to.method) when the user changes the value","messages":[{"role":"user","content":"j-Permissions\n\nj-Permissions: - jComponent v19|v20; NEW: exec {String} optional, it executes a method (path.to.method) when the user changes the value"},{"role":"assistant","content":"- - jComponent `v19|v20`\n\n__Configuration__:\n\n- `placeholder {String}` a placeholder for `j-Directory` component (default: `Search`)\n- `types {String}` permission types (only one character per operation!!!!) (array delimited by comma, default: `R,W`)\n- `dirsource {String}` optional, path to a data-source or __NEW__: `id|name,id|name`\n- `empty {String}` optional, adds a text into the empty permissions\n- `disabled {Boolean}` optional, disables this component\n- `default {String}` default value of `type` (default: `R`)\n- `dirraw {Boolean}` optional, disables HTML encoding for text in `j-Directory` (default: `false`)\n- `autoremove {Boolean}` optional, it automatically removes non-existent permissions (default: `true`)\n- `autoexclude {Boolean}` optional, it excludes the used permissions from the `dirsource` (default: `true`)\n- __NEW__: `exec {String}` optional, it executes a method (`path.to.method`) when the user changes the value\n- __NEW__: `button {String}` optional, a button label (default: `Add`)\n\n__Source__:\n\n```js\n[\n\t{\n\t\tid: \"123456\",\n\t\tname: 'Peter'\n\t},\n\t{\n\t\tid: \"654321\",\n\t\tname: 'John'\n\t}\n]\n```\n\n__Input & Output__:\n\n```js\n['R123456', 'W123456', 'W654321']\n```\n\nUse it with `<ui-component name=\"permissions\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"directory\"></ui-component>\n<ui-component name=\"permissions\" path=\"mypermissions\" config=\"dirsource:datasource;types:R|Read,U|Update;empty:Set permissions\">Permissions</ui-component>\n\n<div class=\"mt10\"><ui-bind path=\"mypermissions\" config=\"text:JSON.stringify(value)\"></ui-bind></div>\n\n<script>\n\tvar mypermissions = ['Radmin', 'Remployee', 'Uaccountant'];\n\tvar datasource = [{ id: 'admin', name: 'Admin' }, { id: 'employee', name: 'Employee' }, { id: 'accountant', name: 'Accountant' }];\n</script>\n```"}],"knowledge":{"content_sha256":"846a3e1a9fcb22a99979e6269ab8a51ab821a7a3e1aecffa7c0840d882b2bf92","source_sha256":"2ece4fa07953edd6188a123c5e8b4fc65619344c569ac730c3d0d408dc5b1f59","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","HTML","JSON","MIT","R123456","W123456","W654321"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"directory\"></ui-component>\n<ui-component name=\"permissions\" path=\"mypermissions\" config=\"dirsource:datasource;types:R|Read,U|Update;empty:Set permissions\">Permissions</ui-component>\n\n<div class=\"mt10\"><ui-bind path=\"mypermissions\" config=\"text:JSON.stringify(value)\"></ui-bind></div>\n\n<script>\n\tvar mypermissions = ['Radmin', 'Remployee', 'Uaccountant'];\n\tvar datasource = [{ id: 'admin', name: 'Admin' }, { id: 'employee', name: 'Employee' }, { id: 'accountant', name: 'Accountant' }];\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/pictures","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/pictures/","markdown_url":"https://forge.totaljs.es/en/components/pictures.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"80180492d8ef424dff6a6de40f9c844300a5513094847e70562b80dea3a38587","checked_at":"2026-10-20T12:23:06.176Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Pictures","description":"The component displays images in a simple list. A user can remove and rearrange images.","messages":[{"role":"user","content":"j-Pictures\n\nThe component displays images in a simple list. A user can remove and rearrange images."},{"role":"assistant","content":"The component displays images in a simple list. A user can remove and rearrange images.\n\n- it uses `j-Movable` component\n\n__Configuration__:\n\n- `grid {Number}` a column count in the grid (default: `4`)\n\n---\n\nA value be `Array` in the form:\n\n```\n[\n\t\"/url_to_image_1.jpeg\",\n\t\"/url_to_image_2.jpeg\",\n\t\"/url_to_image_3.jpeg\",\n\t\"/url_to_image_4.jpeg\"\n]\n```\n\nUse it with `<ui-component name=\"pictures\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"pictures\" path=\"common.pictures\"></ui-component>\n\n<script>\n\n\tvar common = {};\n\n\tcommon.pictures = [];\n\tcommon.pictures.push('https://cdn.pixabay.com/photo/2020/07/31/07/59/flowers-5452263__340.jpg');\n\tcommon.pictures.push('https://cdn.pixabay.com/photo/2020/12/13/16/21/stork-5828727__340.jpg');\n\tcommon.pictures.push('https://cdn.pixabay.com/photo/2021/01/30/14/22/women-5963963__340.jpg');\n\tcommon.pictures.push('https://cdn.pixabay.com/photo/2021/02/11/08/43/couple-6004642__340.jpg');\n\n</script>\n```"}],"knowledge":{"content_sha256":"bdd836b7efb637e0d883041197026841fb99ae2b19195c2f40a166a7e2ee2ef8","source_sha256":"80180492d8ef424dff6a6de40f9c844300a5513094847e70562b80dea3a38587","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"pictures\" path=\"common.pictures\"></ui-component>\n\n<script>\n\n\tvar common = {};\n\n\tcommon.pictures = [];\n\tcommon.pictures.push('https://cdn.pixabay.com/photo/2020/07/31/07/59/flowers-5452263__340.jpg');\n\tcommon.pictures.push('https://cdn.pixabay.com/photo/2020/12/13/16/21/stork-5828727__340.jpg');\n\tcommon.pictures.push('https://cdn.pixabay.com/photo/2021/01/30/14/22/women-5963963__340.jpg');\n\tcommon.pictures.push('https://cdn.pixabay.com/photo/2021/02/11/08/43/couple-6004642__340.jpg');\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/pictureupload","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/pictureupload/","markdown_url":"https://forge.totaljs.es/en/components/pictureupload.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"b4c0ca204194d7dd2b638d36ef8b3e3edf1d83da9060ac724d2d87ad11c12318","checked_at":"2026-10-20T12:23:07.092Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-PictureUpload","description":"j-PictureUpload: easy usage; works with Bootstrap Grid System","messages":[{"role":"user","content":"j-PictureUpload\n\nj-PictureUpload: easy usage; works with Bootstrap Grid System"},{"role":"assistant","content":"- works with Bootstrap Grid System\n- drag and drop support\n- uploads a raw file (without resizing)\n\n__Configuration__:\n\n- `width` {Number} a picture width (required)\n- `height` {Number} a picture height (required)\n- `icon` {String} (optional) the label icon without `ti`, (default: `null`)\n- `url` {String}\" (optional) upload URL (default: current URL)\n- `background` {String} (optional) a background color (default: white)\n- `src` {String} an image path (default: `null`), e.g. `/img/{0}.jpg`\n- `extension` {Boolean} response without file extension (default: `false`)\n- `singlefile` {Boolean} response will be string instead of array (default: `true`)\n- __new__ `type` {String} preview: output type (default: `png`)\n- __new__ `quality` {Number} preview: output quality\n\nUse it with `<ui-component name=\"pictureupload\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"padding:50px;max-width:300px\">\n\t<ui-component name=\"pictureupload\" path=\"picture\" config=\"width:300;height:200;url:/upload/;icon:folder\">Picture</ui-component>\n</div>\n\n<script>\n\tvar picture = '';\n</script>\n```"}],"knowledge":{"content_sha256":"7a2ce19cb695f85a25d64478807ff96d7188962716da27cee0a2103e572dcabc","source_sha256":"b4c0ca204194d7dd2b638d36ef8b3e3edf1d83da9060ac724d2d87ad11c12318","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","URL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"padding:50px;max-width:300px\">\n\t<ui-component name=\"pictureupload\" path=\"picture\" config=\"width:300;height:200;url:/upload/;icon:folder\">Picture</ui-component>\n</div>\n\n<script>\n\tvar picture = '';\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/pictureuploadexpert","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/pictureuploadexpert/","markdown_url":"https://forge.totaljs.es/en/components/pictureuploadexpert.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"55f72693dc8e3bbfc59a5bfae9c1ad460b8f7a610e900b973ea2ed2938b09c8a","checked_at":"2026-10-20T12:23:07.831Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-PictureUploadExpert","description":"j-PictureUploadExpert: advanced usage; drag and drop support","messages":[{"role":"user","content":"j-PictureUploadExpert\n\nj-PictureUploadExpert: advanced usage; drag and drop support"},{"role":"assistant","content":"- advanced usage\n- drag and drop support\n- supports `multiple`\n- supports EXIF orientation\n- multiple images are send one by one\n- supports j-Loading\n\n__Configuration__:\n\n- `url {String}` (__IMPORTANT__) upload URL (required)\n- `width {Number}` a picture width (required)\n- `height {Number}` a picture height (required)\n- `background {String}` (optional) a background color (default: white)\n- `schema {String}` prepared object for sending (default: `{file:base64,filename:filename}`, `base64` + `filename` is a value from preview)\n- `quality {Number}` JPEG quality (default: `90`)\n- `customize {Boolean}` resizes picture to the entire defined size (default: `true`)\n- `disabled {Boolean}` disables image upload (default: `false`)\n- `clickselector {String}` a class for custom click selector (default: `none`)\n- `dropselector {String}` a class for custom drop selector (default: `none`)\n- `class {String}` toggles class on `dropselector` if the drag over is emitted  (default: `over`)\n- `error {String}` a path to method `method(err)` when an error is thrown\n\nUse it with `<ui-component name=\"pictureuploadexpert\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Denis Granec. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\t.dragdrop-message { margin-top:35px; text-align:center; font-weight:bold; }\n\t.dragdrop-container { height:100px; background:#EFEFEF; border:2px dashed #CCC; border-radius: 2px; }\n\t.over { border-color: #000 !important; }\n</style>\n\n<ui-component name=\"loading\" class=\"hidden\"></ui-component>\n\n<div style=\"padding:50px;max-width:300px\">\n\t<ui-component name=\"pictureuploadexpert\" path=\"pictures\" config=\"width:200;height:200;url:/api/upload/base64/;dropselector:.dragdrop-container\" class=\"link m\">Click to upload</ui-component>\n\t<div class=\"dragdrop-container m\">\n\t\t<div class=\"dragdrop-over\">\n\t\t\t<div class=\"dragdrop-message\">Drop images here</div>\n\t\t</div>\n\t</div>\n</div>\n\n<script>\n\tvar pictures = [];\n</script>\n```"}],"knowledge":{"content_sha256":"c5c2a6a386b526f841a79483598d674e4740759b960e71ba59dced7af9d9e71e","source_sha256":"55f72693dc8e3bbfc59a5bfae9c1ad460b8f7a610e900b973ea2ed2938b09c8a","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CCC","CDN","DEF","EFEFEF","EXIF","JPEG","MIT","URL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\t.dragdrop-message { margin-top:35px; text-align:center; font-weight:bold; }\n\t.dragdrop-container { height:100px; background:#EFEFEF; border:2px dashed #CCC; border-radius: 2px; }\n\t.over { border-color: #000 !important; }\n</style>\n\n<ui-component name=\"loading\" class=\"hidden\"></ui-component>\n\n<div style=\"padding:50px;max-width:300px\">\n\t<ui-component name=\"pictureuploadexpert\" path=\"pictures\" config=\"width:200;height:200;url:/api/upload/base64/;dropselector:.dragdrop-container\" class=\"link m\">Click to upload</ui-component>\n\t<div class=\"dragdrop-container m\">\n\t\t<div class=\"dragdrop-over\">\n\t\t\t<div class=\"dragdrop-message\">Drop images here</div>\n\t\t</div>\n\t</div>\n</div>\n\n<script>\n\tvar pictures = [];\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/pin","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/pin/","markdown_url":"https://forge.totaljs.es/en/components/pin.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"9f8382f89f363d9def33533aa651dc91137970cb980d4e4fef8b81bbba396df3","checked_at":"2026-10-20T12:23:08.721Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Pin","description":"A simple PIN maker.","messages":[{"role":"user","content":"j-Pin\n\nA simple PIN maker."},{"role":"assistant","content":"A simple PIN maker.\n\n\n__Configuration__:\n\n- `count {Number}` a count of PIN inputs (default: `6`)\n- `required {Boolean}` enables \"required\" (default: `false`)\n- `disabled {Boolean}` can disabled this control\n- `hide` {Boolean} disables displaying of numbers (default: `false`)\n- `mask {Boolean}` disables the mask (default: `true`)\n- __NEW__ `exec {String}` a path to `function(pin)`\n- __NEW__ `allowpaste {Boolean}` enables pin pasting (default: `true`)\n\n__Good to know__:\n\n- input with no value creates a space\n- it expects `String`\n\n__Methods__:\n\n- `component.focus()` focuses the first input\n\nUse it with `<ui-component name=\"pin\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"padding:50px\">\n\t<div style=\"width:200px\">\n\t\t<div style=\"margin-bottom:5px\">PIN 1</div>\n\t\t<ui-component name=\"pin\" path=\"pinA\" config=\"count:4\"></ui-component>\n\t</div>\n\t<br />\n\t<div style=\"width:250px\">\n\t\t<div style=\"margin-bottom:5px\">PIN 2 (hide:true)</div>\n\t\t<ui-component name=\"pin\" path=\"pinB\" config=\"count:5;hide:true\"></ui-component>\n\t</div>\n\t<br />\n\t<div style=\"width:300px\">\n\t\t<div style=\"margin-bottom:5px\">PIN 3 (visible)</div>\n\t\t<ui-component name=\"pin\" path=\"pinC\" config=\"count:6;mask:false\"></ui-component>\n\t</div>\n</div>\n```"}],"knowledge":{"content_sha256":"cb23b44922ae8d1a8142c8426ee44741b46e7df72828fcfe49fa309f7d365d59","source_sha256":"9f8382f89f363d9def33533aa651dc91137970cb980d4e4fef8b81bbba396df3","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","PIN"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"padding:50px\">\n\t<div style=\"width:200px\">\n\t\t<div style=\"margin-bottom:5px\">PIN 1</div>\n\t\t<ui-component name=\"pin\" path=\"pinA\" config=\"count:4\"></ui-component>\n\t</div>\n\t<br />\n\t<div style=\"width:250px\">\n\t\t<div style=\"margin-bottom:5px\">PIN 2 (hide:true)</div>\n\t\t<ui-component name=\"pin\" path=\"pinB\" config=\"count:5;hide:true\"></ui-component>\n\t</div>\n\t<br />\n\t<div style=\"width:300px\">\n\t\t<div style=\"margin-bottom:5px\">PIN 3 (visible)</div>\n\t\t<ui-component name=\"pin\" path=\"pinC\" config=\"count:6;mask:false\"></ui-component>\n\t</div>\n</div>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/planner","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/planner/","markdown_url":"https://forge.totaljs.es/en/components/planner.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"91bd6cdae9c0fe06a8e435aaedf52acbb337a0ebfd349e918f679a9d86cb43ed","checked_at":"2026-10-20T12:23:09.604Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Planner","description":"Display scheduled items in a planner.","messages":[{"role":"user","content":"j-Planner\n\nDisplay scheduled items in a planner."},{"role":"assistant","content":"__Data-source__ needs to contain:\n\n```js\n{\n    items: [{ id: '12345', name: 'A name of plan', dtbeg: NOW.add('-5 days'), dtend: NOW.add('3 days'), progress: 30, color: '#5385EA', title: 'A tooltip' }, ...] // items\n    year: 2019\n}\n```\n\n__Configuration__:\n\n- `days {String}` pluralization for days (optional, default: `# days,# day,# days,# days`)\n- `exec {String}` a path to variable or method `function(item, el)`, it's evaluated if the user will click on the item\n- `scroll {String}` a path to variable or method `function(y)`, it's evaluated if the user will scroll planner\n- `parent {String}` optional, a container with fixed width/height, can be `window`. Default value: `parent` element.)\n\nUse it with `<ui-component name=\"planner\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\tbody{padding:0;margin:0;background-color:#F0F0F0;font-family:Arial;height:100%;}\n\thtml{height:100%;}\n</style>\n\n<ui-component name=\"planner\" path=\"obj\" config=\"exec:selected\">\n</ui-component>\n\n<script>\n\n\tvar obj = {};\n\tobj.year = NOW.getFullYear();\n\tobj.items = [];\n\tobj.items.push({ id: '1', name: 'jComponent', dtbeg: NOW.add('-5 days'), dtend: NOW.add('3 days'), progress: 30, color: '#769DEA' });\n\tobj.items.push({ id: '2', name: 'Total.js', dtbeg: NOW.add('-5 days'), dtend: NOW.add('3 days'), progress: 30, color: '#EACA76' });\n\n\tfunction selected(item, g) {\n\t\tconsole.log(item, g);\n\t}\n</script>\n```"}],"knowledge":{"content_sha256":"192e6c314650ebdc78e694b02744bfab9b7de151595dadd9e317353da6422675","source_sha256":"91bd6cdae9c0fe06a8e435aaedf52acbb337a0ebfd349e918f679a9d86cb43ed","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","EACA76","F0F0F0","MIT","NOW","Total.js"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\tbody{padding:0;margin:0;background-color:#F0F0F0;font-family:Arial;height:100%;}\n\thtml{height:100%;}\n</style>\n\n<ui-component name=\"planner\" path=\"obj\" config=\"exec:selected\">\n</ui-component>\n\n<script>\n\n\tvar obj = {};\n\tobj.year = NOW.getFullYear();\n\tobj.items = [];\n\tobj.items.push({ id: '1', name: 'jComponent', dtbeg: NOW.add('-5 days'), dtend: NOW.add('3 days'), progress: 30, color: '#769DEA' });\n\tobj.items.push({ id: '2', name: 'Total.js', dtbeg: NOW.add('-5 days'), dtend: NOW.add('3 days'), progress: 30, color: '#EACA76' });\n\n\tfunction selected(item, g) {\n\t\tconsole.log(item, g);\n\t}\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/preview","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/preview/","markdown_url":"https://forge.totaljs.es/en/components/preview.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"c2de87f6d782194205ac6b525c4e371126ab13d84171ae593f1fa7bac3687e93","checked_at":"2026-10-20T12:23:10.486Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Preview","description":"This component resizes the picture automatically according to the settings and uploads it to the server in base64 format.","messages":[{"role":"user","content":"j-Preview\n\nThis component resizes the picture automatically according to the settings and uploads it to the server in base64 format."},{"role":"assistant","content":"This component resizes the picture automatically according to the settings and uploads it to the server in `base64` format.\n\n- response from server needs to contain URL address to the picture\n- works with Bootstrap Grid System\n- drag and drop support\n- supports EXIF orientation\n\n__Path__:\n\nThe value/response (from the upload) will be used as a URL address for preview.\n\n__Configuration__:\n\n- `url {String}` (__IMPORTANT__) upload URL, can contain a method too (required)\n- `width {Number}` a picture width (required)\n- `height {Number}` a picture height (required)\n- `percentage {Number}` resizes a picture according to the percentage (then `width` and `height` aren't required)\n- `icon {String}` (optional) the label icon without `ti`, (default: `null`)\n- `background {String}` (optional) a background color, it can be `transparent` (default: `white`)\n- `schema {String}` prepared object for sending (default: `{file:base64,filename:filename}`, `base64` + `filename` is a value from preview)\n- `quality {Number}` JPEG quality (default: `90`)\n- `customize {Boolean}` resizes picture to the defined size (default: `true`)\n- `preview {String}` prepares a value preview URL address (default: `null`)\n\t- example: `value => '/download/' + value + '.jpg'`\n- `map {String}` prepares response value to a value (default: `null`)\n\t- example: `value => value.id`\n- `empty {String}` a link to the empty image\n- `keeporiginal {Boolean}` keeps the original image if the dimension is the same as the width/height defined in the configuration (default: `false`)\n- __NEW__ `output {String}` output type `base64` (default) or `file`\n- __NEW__ `convert {String}` an image type `jpg` (default) or `png`\n- __NEW__ `exec {String}` a path to the method `function(next(url))` (it prevents file upload dialog)\n- __NEW__ `center {Boolean}` aligns the image and label to the center\n\n__Methods__:\n\n- `component.reupload()` it reuploads a current image value as a base64.\n\nUse it with `<ui-component name=\"preview\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"padding:50px;max-width:300px\">\n\t<ui-component name=\"preview\" path=\"picture\" config=\"width:300;height:200;url:POST /upload/;icon:folder\">Preview</ui-component>\n</div>\n\n<script>\n\tvar picture = '';\n</script>\n```"}],"knowledge":{"content_sha256":"653ef5a3b446589d1cbc05b8dcffcda93bc283077d9b14f8732181f3a2479fc5","source_sha256":"c2de87f6d782194205ac6b525c4e371126ab13d84171ae593f1fa7bac3687e93","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","EXIF","JPEG","MIT","POST","URL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"padding:50px;max-width:300px\">\n\t<ui-component name=\"preview\" path=\"picture\" config=\"width:300;height:200;url:POST /upload/;icon:folder\">Preview</ui-component>\n</div>\n\n<script>\n\tvar picture = '';\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/pricerange","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/pricerange/","markdown_url":"https://forge.totaljs.es/en/components/pricerange.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"b7eedfc99c9bc038ec853d71fed4deaf728c118c7e59aecb6ca54fde41e00083","checked_at":"2026-10-20T12:23:11.370Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-PriceRange","description":"The component stores value as string 200-500, explanation: from-to.","messages":[{"role":"user","content":"j-PriceRange\n\nThe component stores value as string 200-500, explanation: from-to."},{"role":"assistant","content":"__Attributes__:\n- `data-min=\"0\"` a minimal value (default `0`)\n- `data-max=\"0\"` a maximal value (default `10000`)\n- `data-step=\"100\"` step (default `100`)\n- `data-currency=\"$ {0}\"` formats current values (default `{0} &euro;`)\n\nThe component stores value as string `200-500`, explanation: `from-to`.\n\nUse it with `<ui-component name=\"pricerange\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"width:600px\">\n\t<ui-component name=\"pricerange\" path=\"filter.price\" data-min=\"0\" data-step=\"100\" data-max=\"1000\"></ui-component>\n</div>\n\n<script>\n\tvar filter = { price: '200-800' };\n</script>\n```"}],"knowledge":{"content_sha256":"7b88dbcfb63f69968bb31e0a5e3ced2a4f209a6769de7bb4a126ec4569899844","source_sha256":"b7eedfc99c9bc038ec853d71fed4deaf728c118c7e59aecb6ca54fde41e00083","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"width:600px\">\n\t<ui-component name=\"pricerange\" path=\"filter.price\" data-min=\"0\" data-step=\"100\" data-max=\"1000\"></ui-component>\n</div>\n\n<script>\n\tvar filter = { price: '200-800' };\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/printer","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/printer/","markdown_url":"https://forge.totaljs.es/en/components/printer.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"a6c3966bac374c6d000b599a33992bbd5734c74b08db4be4d4ff6faf1d80966f","checked_at":"2026-10-20T12:23:12.259Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Printer","description":"The component can print a HTML content.","messages":[{"role":"user","content":"j-Printer\n\nThe component can print a HTML content."},{"role":"assistant","content":"The component can print a HTML content.\n\n- singleton\n__Configuration__:\n\n- `delay {Number}` a delay for evaluating of printer (default: `500`)\n- `delayprint {Number}` a delay for printing (default: `500`)\n- `delayclose {Number}` a delay for closing window (default: `1000`)\n\n__Methods__:\n\n```js\n// SETTER('printer/print', [title], html, test);\n\nSETTER('printer/print', '<h1>Total.js Platform</h1>');\n\n// Shows test window\nSETTER('printer/print', '<h1>Total.js Platform</h1>', true);\n```\n\nUse it with `<ui-component name=\"printer\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\tbody { background-color: #D0D0D0; }\n</style>\n\n<ui-component name=\"printer\"></ui-component>\n\n<script>\n\n\tON('ready', function() {\n\t\tSETTER('printer/print', '<h1>Total.js Platform</h1>');\n\t});\n\n</script>\n```"}],"knowledge":{"content_sha256":"dcca4835afba10a25a5393a5e97ea76fe39ef5bac353372ad65a17b107c87299","source_sha256":"a6c3966bac374c6d000b599a33992bbd5734c74b08db4be4d4ff6faf1d80966f","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","D0D0D0","DEF","HTML","MIT","SETTER","Total.js"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\tbody { background-color: #D0D0D0; }\n</style>\n\n<ui-component name=\"printer\"></ui-component>\n\n<script>\n\n\tON('ready', function() {\n\t\tSETTER('printer/print', '<h1>Total.js Platform</h1>');\n\t});\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/progress","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/progress/","markdown_url":"https://forge.totaljs.es/en/components/progress.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"5ac013908242287f3fbd679522272e893ded75da825f2a7180616609d914a58c","checked_at":"2026-10-20T12:23:13.160Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Progress","description":"j-Progress options: animate","messages":[{"role":"user","content":"j-Progress\n\nj-Progress options: animate"},{"role":"assistant","content":"__Configuration__:\n\n- `animate {Boolean}` enables animation (default: `true`)\n\nUse it with `<ui-component name=\"progress\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"padding:50px;max-width:300px\">\n\t<ui-component name=\"progress\" path=\"percentage1\"></ui-component>\n\t<br />\n\t<ui-component name=\"progress\" path=\"percentage2\"></ui-component>\n\t<br />\n\t<ui-component name=\"progress\" path=\"percentage3\" config=\"animate:false\"></ui-component>\n</div>\n\n<script>\n\tvar percentage = 30;\n\tsetInterval(function() {\n\t\tSET('percentage1', (Math.random() * 100) >> 0);\n\t\tSET('percentage2', (Math.random() * 100) >> 0);\n\t\tSET('percentage3', (Math.random() * 100) >> 0);\n\t}, 800);\n</script>\n```"}],"knowledge":{"content_sha256":"d9c59847d941fe91585d1ad22f665aa9ff0233e5a0f6e8360a4e71ca37fd418f","source_sha256":"5ac013908242287f3fbd679522272e893ded75da825f2a7180616609d914a58c","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","SET"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"padding:50px;max-width:300px\">\n\t<ui-component name=\"progress\" path=\"percentage1\"></ui-component>\n\t<br />\n\t<ui-component name=\"progress\" path=\"percentage2\"></ui-component>\n\t<br />\n\t<ui-component name=\"progress\" path=\"percentage3\" config=\"animate:false\"></ui-component>\n</div>\n\n<script>\n\tvar percentage = 30;\n\tsetInterval(function() {\n\t\tSET('percentage1', (Math.random() * 100) >> 0);\n\t\tSET('percentage2', (Math.random() * 100) >> 0);\n\t\tSET('percentage3', (Math.random() * 100) >> 0);\n\t}, 800);\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/prompt","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/prompt/","markdown_url":"https://forge.totaljs.es/en/components/prompt.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"4498da1cda7d3afb0949db55b40cb099e116b7e5ef4709a7030a0ed96e55bda0","checked_at":"2026-10-20T12:23:14.041Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Prompt","description":"The component (singleton) displays a prompt window with single-line or multiline input.","messages":[{"role":"user","content":"j-Prompt\n\nThe component (singleton) displays a prompt window with single-line or multiline input."},{"role":"assistant","content":"The component (singleton) displays a prompt window with single-line or multiline input.\n\n\n__Configuration__:\n\n- `zindex {Number}` optional (default: `12`)\n- `width {Number}` optional, max. width (default: `400`)\n- `cancel {String}` optional, label for the cancel button (default: `Cancel`)\n- `submit {String}` optional, label for the submit button (default: `OK`)\n\n__Example__:\n\n```js\nvar opt = {};\nopt.name = 'A title'; // optional\nopt.summary = 'A small sumarization'; // optional\nopt.value = ''; // a default value, optional\n// opt.type = 'password'; // only for single-line mode\n// opt.width = config.width; // max. width, optional\n// opt.zindex = config.zindex; // a custom zindex, optional\n// opt.centered = true; // centers the window to the middle of the screen\n// opt.newbie = true; // allows to submit a default value\n// opt.multiline = true; // NEW: enables textarea with 3 rows\n// opt.monospace = true; // NEW: enables monospace text\n\nopt.cancel = function(val) {\n\t// @val {String}\n\t// prompt is canceled\n};\n\nopt.callback = function(val) {\n\t// @val {String}\n\t// changed\n};\n\nopt.hide = function() {\n\t// prompt is hidden/closed\n};\n\nSETTER('prompt/show', opt);\n```\n\nUse it with `<ui-component name=\"prompt\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"prompt\"></ui-component>\n\n<script>\n\tsetTimeout(function() {\n\t\tvar opt = {};\n\t\topt.name = 'Folder name';\n\t\topt.summary = 'Enter a folder name for your documents:';\n\t\topt.value = '';\n\n\t\topt.callback = function(val) {\n\t\t\tconsole.log('Returned', val);\n\t\t};\n\n\t\topt.cancel = function(val) {\n\t\t\tconsole.log('Canceled', val);\n\t\t};\n\n\t\topt.hide = function() {\n\t\t\tconsole.log('Hidden');\n\t\t};\n\n\t\tSETTER('prompt/show', opt);\n\t}, 1000);\n</script>\n```"}],"knowledge":{"content_sha256":"3d45d35c15ce445e7ea07d7527b9172da86b281ce48cbba01504cb8643790c5f","source_sha256":"4498da1cda7d3afb0949db55b40cb099e116b7e5ef4709a7030a0ed96e55bda0","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","NEW","SETTER"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"prompt\"></ui-component>\n\n<script>\n\tsetTimeout(function() {\n\t\tvar opt = {};\n\t\topt.name = 'Folder name';\n\t\topt.summary = 'Enter a folder name for your documents:';\n\t\topt.value = '';\n\n\t\topt.callback = function(val) {\n\t\t\tconsole.log('Returned', val);\n\t\t};\n\n\t\topt.cancel = function(val) {\n\t\t\tconsole.log('Canceled', val);\n\t\t};\n\n\t\topt.hide = function() {\n\t\t\tconsole.log('Hidden');\n\t\t};\n\n\t\tSETTER('prompt/show', opt);\n\t}, 1000);\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/properties","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/properties/","markdown_url":"https://forge.totaljs.es/en/components/properties.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"bb15c0380092364ab5eb63e9b66b06c040e303283724bac64577bdb1cdc9664c","checked_at":"2026-10-20T12:23:14.785Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Properties","description":"The component uses j-DatePicker, j-TimePicker, j-Directory, j-Emoji, j-ColorPicker and j-FaIcons components.","messages":[{"role":"user","content":"j-Properties\n\nThe component uses j-DatePicker, j-TimePicker, j-Directory, j-Emoji, j-ColorPicker and j-FaIcons components."},{"role":"assistant","content":"The component uses `j-DatePicker`, `j-TimePicker`, `j-Directory`, `j-Emoji`, `j-ColorPicker` and `j-FaIcons` components.\n\n__Configuration__:\n\n- `dateformat` {String} a date format (default: `yyyy-MM-dd`)\n- `timeformat` {String} a time format (default: `HH:mm`)\n- `datetimeformat` {String} a date/time format (default: `yyyy-MM-dd HH:mm`)\n- `height` {Number} a height of the element (default: `undefined`)\n- `parent` {String} jQuery selector for auto setup of height (can be `window`, `parent` or `custom`)\n- `margin` {Number} vertical margin (default: `0`)\n- `change` {String} a path to method `method(item, replace_text(new_text))` (executed if some value is changed) and `replace_text` argument is optional\n- `scrollbar` {Boolean} enables scrollbar (default: `true`)\n- `visibleY` {Boolean} shows a scrollbar (default: `true`)\n\n__Data declaration__:\n\n```javascript\nvar form = [];\nform.push({ group: 'Personal', label: 'First name', name: 'user.firstname', type: 'string', value: 'Peter', transform: 'capitalize' });\nform.push({ group: 'Personal', label: 'Last name', name: 'user.lastname', type: 'string', value: 'Širka', placeholder: 'String' });\nform.push({ group: 'Personal', label: 'Age', name: 'user.age', type: 'number', value: 33, min: 30 });\nform.push({ group: 'Personal', label: 'Birth date', name: 'user.birth', type: 'date', value: NOW });\nform.push({ group: 'Additional', label: 'Newsletter', name: 'user.newsletter', type: 'bool', value: true });\nform.push({ group: 'Additional', label: 'City', name: 'user.city', type: 'list', value: 2, items: 'items', dirsearch: 'Search city', dircustom: true });\nform.push({ group: 'Additional', label: 'Color', name: 'user.color', type: 'color', value: '#e73323' });\nform.push({ group: 'Additional', label: 'Icon', name: 'user.icon', type: 'fontawesome', value: 'ti ti-home' });\nform.push({ group: 'Additional', label: 'Emoji', name: 'user.emoji', type: 'emoji', value: '', show: 'n => n.age === 33' });\n```\n\n__Data properties in data declaration__:\n- `name` {String} an item identifier (required)\n- `label` {String} a label (required)\n- `group` {String} a group name (required)\n- `type` {String} a type (required)\n- `placeholder` {String} a placeholder (supports few types only)\n- `transform` {String} for `string` type only, can contain `capitalize`, `uppercase`, `lowercase` or `slug`\n- `maxlength` {Number} for `string` type only\n- `validate` {RegExp/String} for `string` type only, can contain `email`, `phone` or `url`\n- `min` {Number} for `number` type only\n- `max` {Number} for `number` type only\n- `inc` {Number/String} increments a value according to the value, targeted for `number` or `date` (can contain `1 day` or `-1 day`) type only\n- `required` {Boolean} for type `string`, `number` or `date`\n- `dirsearch` {String/Boolean} a placeholder for `list` type only, boolean disables search\n- `dircustom` {Boolean} enables a custom value for `list` type only\n- `dirkey` {String} a key name for label/name, targeted for `list` type only (default: `name`)\n- `dirvalue` {String} a key name for value, targeted for `list` type only (default: `id`)\n- `detail` {String} URL address for obtaining of data for `list` type, example: `/users/{0}/` and `{0}` will be replaced with the value from list\n- `items` {Object Array/String} items for `list` type only, string can contain a path to `Array` or `URL address` to search\n- `show` {Arrow function}, example: `data => data.KEY === 'SOMETHING'` --> will show the item if the condition will valid\n\n__Allowed types in data__:\n\n- `string`\n- `bool`\n- `number`\n- `date`\n- `list`\n- `color`\n- `emoji`\n- `fontawesome`\n\n__Missing types__:\n\n- `datetime`\n- `time`\n- `file`\n- `months`\n\n### Persistent mode\n\n```html\n<ui-component name=\"properties\" path=\"form\" config=\"parent:window\">\n\t<script type=\"text/html\">\n\t\t[\n\t\t\t{ group: 'Personal', label: 'First name', name: 'firstname', type: 'string', transform: 'capitalize' },\n\t\t\t{ group: 'Personal', label: 'Last name', name: 'lastname', type: 'string', placeholder: 'String' },\n\t\t\t...\n\t\t\t...\n\t\t\t...\n\t\t\t{ group: 'Additional', label: 'Emoji', name: 'emoji', type: 'emoji', show: 'n => n.age === 33' }\n\t\t]\n\t</script>\n</ui-component>\n\n<script>\n\tvar form = { firstname: 'John', lastname: 'Connor' };\n</script>\n```\n\n### Good to know\n\nThe component extends the Array by adding:\n\n- `invalid` {Boolean} field determines if the `value` is invalid (default: `undefined`)\n- `changed` {Boolean} field determines if the `value` has been changed (default: `undefined`)\n- `prev` {???} field contains an initial value\n\nUse it with `<ui-component name=\"properties\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"LAZY directory\"></ui-component>\n<ui-component name=\"LAZY colorpicker\"></ui-component>\n<ui-component name=\"LAZY faicons\"></ui-component>\n<ui-component name=\"LAZY datepicker\"></ui-component>\n<ui-component name=\"LAZY timepicker\"></ui-component>\n<ui-component name=\"LAZY emoji\"></ui-component>\n\n<div style=\"width:400px;float:right\">\n\t<ui-component name=\"properties\" path=\"form\" config=\"parent:window\"></ui-component>\n</div>\n\n<script>\n\tvar items = [{ id: 2, name: 'Banská Bystrica' }, { id: 3, name: 'Bratislava' }];\n\tvar form = [];\n\tform.push({ group: 'Personal', label: 'First name', name: 'firstname', type: 'string', value: 'Peter', transform: 'capitalize' });\n\tform.push({ group: 'Personal', label: 'Last name', name: 'lastname', type: 'string', value: 'Širka', placeholder: 'String' });\n\tform.push({ group: 'Personal', label: 'Age', name: 'age', type: 'number', value: 33, min: 30 });\n\tform.push({ group: 'Personal', label: 'Birth date', name: 'birth', type: 'date', value: NOW });\n\tform.push({ group: 'Additional', label: 'Newsletter', name: 'newsletter', type: 'bool', value: true });\n\tform.push({ group: 'Additional', label: 'City', name: 'city', type: 'list', value: 2, items: 'items', dirsearch: 'Search city', dircustom: true });\n\tform.push({ group: 'Additional', label: 'Color', name: 'color', type: 'color', value: '#e73323' });\n\tform.push({ group: 'Additional', label: 'Icon', name: 'icon', type: 'fontawesome', value: 'ti ti-home' });\n\tform.push({ group: 'Additional', label: 'Emoji', name: 'emoji', type: 'emoji', value: '', show: 'n => n.age === 33' });\n</script>\n```"}],"knowledge":{"content_sha256":"800ebc73d3fac5c56576781417481bed88bd8c01c98db8afc361bcbc76525566","source_sha256":"bb15c0380092364ab5eb63e9b66b06c040e303283724bac64577bdb1cdc9664c","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","KEY","LAZY","MIT","NOW","SOMETHING","URL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"LAZY directory\"></ui-component>\n<ui-component name=\"LAZY colorpicker\"></ui-component>\n<ui-component name=\"LAZY faicons\"></ui-component>\n<ui-component name=\"LAZY datepicker\"></ui-component>\n<ui-component name=\"LAZY timepicker\"></ui-component>\n<ui-component name=\"LAZY emoji\"></ui-component>\n\n<div style=\"width:400px;float:right\">\n\t<ui-component name=\"properties\" path=\"form\" config=\"parent:window\"></ui-component>\n</div>\n\n<script>\n\tvar items = [{ id: 2, name: 'Banská Bystrica' }, { id: 3, name: 'Bratislava' }];\n\tvar form = [];\n\tform.push({ group: 'Personal', label: 'First name', name: 'firstname', type: 'string', value: 'Peter', transform: 'capitalize' });\n\tform.push({ group: 'Personal', label: 'Last name', name: 'lastname', type: 'string', value: 'Širka', placeholder: 'String' });\n\tform.push({ group: 'Personal', label: 'Age', name: 'age', type: 'number', value: 33, min: 30 });\n\tform.push({ group: 'Personal', label: 'Birth date', name: 'birth', type: 'date', value: NOW });\n\tform.push({ group: 'Additional', label: 'Newsletter', name: 'newsletter', type: 'bool', value: true });\n\tform.push({ group: 'Additional', label: 'City', name: 'city', type: 'list', value: 2, items: 'items', dirsearch: 'Search city', dircustom: true });\n\tform.push({ group: 'Additional', label: 'Color', name: 'color', type: 'color', value: '#e73323' });\n\tform.push({ group: 'Additional', label: 'Icon', name: 'icon', type: 'fontawesome', value: 'ti ti-home' });\n\tform.push({ group: 'Additional', label: 'Emoji', name: 'emoji', type: 'emoji', value: '', show: 'n => n.age === 33' });\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/properties2","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/properties2/","markdown_url":"https://forge.totaljs.es/en/components/properties2.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"371e5001d316182b090fa724779f5854679deec94c6f316c5827e6b05de46d62","checked_at":"2026-10-20T12:23:15.667Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Properties2","description":"The component uses j-DatePicker, j-TimePicker, j-Directory, j-Emoji, j-ColorPicker and j-Icons components.","messages":[{"role":"user","content":"j-Properties2\n\nThe component uses j-DatePicker, j-TimePicker, j-Directory, j-Emoji, j-ColorPicker and j-Icons components."},{"role":"assistant","content":"The component uses `j-DatePicker`, `j-TimePicker`, `j-Directory`, `j-Emoji`, `j-ColorPicker` and `j-Icons` components.\n\n\n__Configuration__:\n\n- `dateformat {String}` a date format (default: `yyyy-MM-dd`)\n- `timeformat {String}` a time format (default: `HH:mm`)\n- `datetimeformat {String}` a date/time format (default: `yyyy-MM-dd HH:mm`)\n- `change {String}` a path to method `method(item, replace_text(new_text))` (executed if some value is changed) and `replace_text` argument is optional\n- `modalalign {String}` aligment for modal windows (default: `center`)\n- `style {Number}` supports a new style `2` like `iOS` settings (default: `1`)\n- `validation {Boolean}` enables validation for required fields (default: `true`)\n- `defaultgroup {String}` a default group name (default: `Default`)\n\n__Data declaration__:\n\n```javascript\nvar form = [];\nform.push({ group: 'Personal', label: 'First name', name: 'user.firstname', type: 'string', value: 'Peter', transform: 'capitalize' });\nform.push({ group: 'Personal', label: 'Last name', name: 'user.lastname', type: 'string', value: 'Širka', placeholder: 'String' });\nform.push({ group: 'Personal', label: 'Age', name: 'user.age', type: 'number', value: 33, min: 30 });\nform.push({ group: 'Personal', label: 'Birth date', name: 'user.birth', type: 'date', value: NOW });\nform.push({ group: 'Additional', label: 'Newsletter', name: 'user.newsletter', type: 'bool', value: true });\nform.push({ group: 'Additional', label: 'City', name: 'user.city', type: 'list', value: 2, items: 'items', dirsearch: 'Search city', dircustom: true });\nform.push({ group: 'Additional', label: 'Color', name: 'user.color', type: 'color', value: '#e73323' });\nform.push({ group: 'Additional', label: 'Icon', name: 'user.icon', type: 'fontawesome', value: 'ti ti-home' });\nform.push({ group: 'Additional', label: 'Emoji', name: 'user.emoji', type: 'emoji', value: '', show: 'n => n.age === 33' });\n```\n\n__Data properties in data declaration__:\n\n- `name {String}` an item identifier (required)\n- `label {String}` a label (required)\n- `group {String}` a group name (required)\n- `note {String}` a small note under control\n- `type {String}` a type (required)\n- `placeholder {String}` a placeholder (supports few types only)\n- `transform {String}` for `string` type only, can contain `capitalize`, `uppercase`, `lowercase` or `slug`\n- `maxlength {Number}` for `string` type only\n- `validate {RegExp/String}` for `string` type only, can contain `email`, `phone` or `url`\n- `min {Number}` for `number` type only\n- `max {Number}` for `number` type only\n- `inc {Number/String}` increments a value according to the value, targeted for `number` or `date` (can contain `1 day` or `-1 day`) type only\n- `required {Boolean}` for type `string`, `number` or `date`\n- `dirsearch {String/Boolean}` a placeholder for `list` type only, boolean disables search\n- `dircustom {Boolean}` enables a custom value for `list` type only\n- `dirkey {String}` a key name for label/name, targeted for `list` type only (default: `name`)\n- `dirvalue {String}` a key name for value, targeted for `list` type only (default: `id`)\n- `detail {String}` URL address for obtaining of data for `list` type, example: `/users/{0}/` and `{0}` will be replaced with the value from list\n- `items {Object Array/String}` items for `list` type only, string can contain a path to `Array` or `URL address` to search\n- `show {Arrow function}`, example: `data => data.KEY === 'SOMETHING'` --> will show the item if the condition will valid\n- `icon {String}` Total icon, can contain a color e.g. `ti ti-home #00000`\n- `ricon {String}` Right Font-Awesome icon or text e.g. `!HTML text`\n- `riconclick {String}` a path to the method `function(item, set(new_val))`\n- `camouflage {Boolean}` only for `string` type\n- `monospace {Boolean}` it uses a monospace font\n\n__Allowed types in data__:\n\n- `string`\n- `bool`\n- `number`\n- `date`\n- `list`\n- `color`\n- `emoji`\n- `fontawesome`\n- `exec` (can exec `exec` method)\n- `text` (readonly value)\n- `menu` (it's similar to list but the component will execute `j-Menu`)\n\n__Missing types__:\n\n- `datetime`\n- `time`\n- `file`\n- `months`\n\n### Good to know\n\nThe component extends the Array by adding:\n\n- `invalid {Boolean}` field determines if the `value` is invalid (default: `undefined`)\n- `changed {Boolean}` field determines if the `value` has been changed (default: `undefined`)\n- `prev` {???} field contains an initial value\n\nUse it with `<ui-component name=\"properties2\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"LAZY directory\"></ui-component>\n<ui-component name=\"LAZY colorpicker\"></ui-component>\n<ui-component name=\"LAZY faicons\"></ui-component>\n<ui-component name=\"LAZY datepicker\"></ui-component>\n<ui-component name=\"LAZY timepicker\"></ui-component>\n<ui-component name=\"LAZY emoji\"></ui-component>\n<ui-component name=\"LAZY menu\"></ui-component>\n\n<div style=\"width:600px;padding:20px;background-color:#F0F0F0\">\n\n\t<ui-component name=\"properties2\" path=\"form\" class=\"m\"></ui-component>\n\t<ui-component name=\"properties2\" path=\"form2\" config=\"style:2\" class=\"m\"></ui-component>\n\n\t<!-- PERSISTENT MODE -->\n\t<ui-component name=\"properties2\" path=\"form3\" config=\"style:2\">\n\t\t<script type=\"text/plain\">\n\t\t\t[\n\t\t\t\t{ group: 'Personal', name: 'First name', id: 'firstname', type: 'string',transform: 'capitalize', note: 'Type your name' },\n\t\t\t\t{ group: 'Personal', name: 'Last name', id: 'lastname', type: 'string', placeholder: 'String' },\n\t\t\t\t{ group: 'Personal', name: 'Token', id: 'token', type: 'string', placeholder: 'String', camouflage: true }\n\t\t\t]\n\t\t</script>\n\t</ui-component>\n\n</div>\n\n<script>\n\tvar items = [{ id: 2, name: 'Banská Bystrica' }, { id: 3, name: 'Bratislava' }];\n\tvar form = [];\n\tform.push({ group: 'Personal', name: 'First name', id: 'firstname', type: 'string', value: 'Peter', transform: 'capitalize', note: 'Type your name' });\n\tform.push({ group: 'Personal', name: 'Last name', id: 'lastname', type: 'string', value: 'Širka', placeholder: 'String' });\n\tform.push({ group: 'Personal', name: 'Age', id: 'age', type: 'number', value: 33, min: 30 });\n\tform.push({ group: 'Personal', name: 'Birth date', id: 'birth', type: 'date', value: NOW });\n\tform.push({ group: 'Additional', name: 'Newsletter', id: 'newsletter', type: 'bool', value: true });\n\tform.push({ group: 'Additional', name: 'City', id: 'city', type: 'list', value: 2, items: 'items', dirsearch: 'Search city', dircustom: true });\n\tform.push({ group: 'Additional', name: 'Color', id: 'color', type: 'color', value: '#e73323' });\n\tform.push({ group: 'Additional', name: 'Icon', id: 'icon', type: 'fontawesome', value: 'ti ti-home' });\n\tform.push({ group: 'Additional', name: 'Emoji', id: 'emoji', type: 'emoji', value: '', show: 'n => n.age === 33' });\n\tform.push({ group: 'Additional', name: 'Exec', exec: 'myexec', type: 'exec', value: 'Some text to exec' });\n\tform.push({ group: 'Additional', name: 'Readonly', id: 'something', type: 'text', value: 'Readonly value' });\n\n\tvar form2 = [];\n\tform2.push({ group: 'Personal', name: 'First name', id: 'firstname', type: 'string', value: 'Peter', transform: 'capitalize', note: 'Type your name' });\n\tform2.push({ group: 'Personal', name: 'Last name', id: 'lastname', type: 'string', value: 'Širka', placeholder: 'String' });\n\tform2.push({ group: 'Personal', name: 'Age', id: 'age', type: 'number', value: 33, min: 30 });\n\tform2.push({ group: 'Personal', name: 'Birth date', id: 'birth', type: 'date', value: NOW });\n\tform2.push({ group: 'Additional', name: 'Newsletter', id: 'newsletter', type: 'bool', value: true });\n\tform2.push({ group: 'Additional', name: 'City', id: 'city', type: 'list', value: 2, items: 'items', dirsearch: 'Search city', dircustom: true });\n\tform2.push({ group: 'Additional', name: 'Color', id: 'color', type: 'color', value: '#e73323' });\n\tform2.push({ group: 'Additional', name: 'Icon', id: 'icon', type: 'fontawesome', value: 'ti ti-home' });\n\tform2.push({ group: 'Additional', name: 'Emoji', id: 'emoji', type: 'emoji', value: '', show: 'n => n.age === 33' });\n\tform2.push({ group: 'Additional', name: 'Exec', exec: 'myexec', type: 'exec', value: 'Some text to exec' });\n\tform2.push({ group: 'Additional', name: 'Readonly', id: 'something', type: 'text', value: 'Readonly value', icon: 'ti ti-home #000' });\n\n\tvar form3 = {};\n\tform3.firstname = 'Peter';\n\tform3.lastname = 'Širka';\n\tform3.token = GUID(30);\n\n\tfunction myexec(item, el) {\n\t\tconsole.log(item, el);\n\t}\n\n</script>\n```"}],"knowledge":{"content_sha256":"2577ce7a0f4076a948145469f278db0f44a448d641131530cae456faecfad429","source_sha256":"371e5001d316182b090fa724779f5854679deec94c6f316c5827e6b05de46d62","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","F0F0F0","GUID","HTML","KEY","LAZY","MIT","MODE","NOW","PERSISTENT","SOMETHING","URL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"LAZY directory\"></ui-component>\n<ui-component name=\"LAZY colorpicker\"></ui-component>\n<ui-component name=\"LAZY faicons\"></ui-component>\n<ui-component name=\"LAZY datepicker\"></ui-component>\n<ui-component name=\"LAZY timepicker\"></ui-component>\n<ui-component name=\"LAZY emoji\"></ui-component>\n<ui-component name=\"LAZY menu\"></ui-component>\n\n<div style=\"width:600px;padding:20px;background-color:#F0F0F0\">\n\n\t<ui-component name=\"properties2\" path=\"form\" class=\"m\"></ui-component>\n\t<ui-component name=\"properties2\" path=\"form2\" config=\"style:2\" class=\"m\"></ui-component>\n\n\t<!-- PERSISTENT MODE -->\n\t<ui-component name=\"properties2\" path=\"form3\" config=\"style:2\">\n\t\t<script type=\"text/plain\">\n\t\t\t[\n\t\t\t\t{ group: 'Personal', name: 'First name', id: 'firstname', type: 'string',transform: 'capitalize', note: 'Type your name' },\n\t\t\t\t{ group: 'Personal', name: 'Last name', id: 'lastname', type: 'string', placeholder: 'String' },\n\t\t\t\t{ group: 'Personal', name: 'Token', id: 'token', type: 'string', placeholder: 'String', camouflage: true }\n\t\t\t]\n\t\t</script>\n\t</ui-component>\n\n</div>\n\n<script>\n\tvar items = [{ id: 2, name: 'Banská Bystrica' }, { id: 3, name: 'Bratislava' }];\n\tvar form = [];\n\tform.push({ group: 'Personal', name: 'First name', id: 'firstname', type: 'string', value: 'Peter', transform: 'capitalize', note: 'Type your name' });\n\tform.push({ group: 'Personal', name: 'Last name', id: 'lastname', type: 'string', value: 'Širka', placeholder: 'String' });\n\tform.push({ group: 'Personal', name: 'Age', id: 'age', type: 'number', value: 33, min: 30 });\n\tform.push({ group: 'Personal', name: 'Birth date', id: 'birth', type: 'date', value: NOW });\n\tform.push({ group: 'Additional', name: 'Newsletter', id: 'newsletter', type: 'bool', value: true });\n\tform.push({ group: 'Additional', name: 'City', id: 'city', type: 'list', value: 2, items: 'items', dirsearch: 'Search city', dircustom: true });\n\tform.push({ group: 'Additional', name: 'Color', id: 'color', type: 'color', value: '#e73323' });\n\tform.push({ group: 'Additional', name: 'Icon', id: 'icon', type: 'fontawesome', value: 'ti ti-home' });\n\tform.push({ group: 'Additional', name: 'Emoji', id: 'emoji', type: 'emoji', value: '', show: 'n => n.age === 33' });\n\tform.push({ group: 'Additional', name: 'Exec', exec: 'myexec', type: 'exec', value: 'Some text to exec' });\n\tform.push({ group: 'Additional', name: 'Readonly', id: 'something', type: 'text', value: 'Readonly value' });\n\n\tvar form2 = [];\n\tform2.push({ group: 'Personal', name: 'First name', id: 'firstname', type: 'string', value: 'Peter', transform: 'capitalize', note: 'Type your name' });\n\tform2.push({ group: 'Personal', name: 'Last name', id: 'lastname', type: 'string', value: 'Širka', placeholder: 'String' });\n\tform2.push({ group: 'Personal', name: 'Age', id: 'age', type: 'number', value: 33, min: 30 });\n\tform2.push({ group: 'Personal', name: 'Birth date', id: 'birth', type: 'date', value: NOW });\n\tform2.push({ group: 'Additional', name: 'Newsletter', id: 'newsletter', type: 'bool', value: true });\n\tform2.push({ group: 'Additional', name: 'City', id: 'city', type: 'list', value: 2, items: 'items', dirsearch: 'Search city', dircustom: true });\n\tform2.push({ group: 'Additional', name: 'Color', id: 'color', type: 'color', value: '#e73323' });\n\tform2.push({ group: 'Additional', name: 'Icon', id: 'icon', type: 'fontawesome', value: 'ti ti-home' });\n\tform2.push({ group: 'Additional', name: 'Emoji', id: 'emoji', type: 'emoji', value: '', show: 'n => n.age === 33' });\n\tform2.push({ group: 'Additional', name: 'Exec', exec: 'myexec', type: 'exec', value: 'Some text to exec' });\n\tform2.push({ group: 'Additional', name: 'Readonly', id: 'something', type: 'text', value: 'Readonly value', icon: 'ti ti-home #000' });\n\n\tvar form3 = {};\n\tform3.firstname = 'Peter';\n\tform3.lastname = 'Širka';\n\tform3.token = GUID(30);\n\n\tfunction myexec(item, el) {\n\t\tconsole.log(item, el);\n\t}\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/qrcode","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/qrcode/","markdown_url":"https://forge.totaljs.es/en/components/qrcode.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"da65126de8f46500d2cbc2c5e2547ce771ee304aea3d1bb22dba7269d2dc5b42","checked_at":"2026-10-20T12:23:16.401Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-QRCode","description":"This component creates a QR code. It includes the [QRCode library](https://davidshimjs.github.io/qrcodejs/).","messages":[{"role":"user","content":"j-QRCode\n\nThis component creates a QR code. It includes the [QRCode library](https://davidshimjs.github.io/qrcodejs/)."},{"role":"assistant","content":"This component creates a QR code. It includes the [QRCode library](https://davidshimjs.github.io/qrcodejs/).\n\n\n__Configuration__:\n\n- `width {Number}` a default image width (default: `128`)\n- `height {Number}` a default image height (default: `128`)\n- `fg {String}` a foreground color (default: `#000000`)\n- `bg {String}` a background color (default: `#FFFFFF`)\n- `delay {Number}` a small delay for code generating (default: `200`)\n\n__How to generate QR Code?__\n\n```js\nvar opt = {};\nopt.text = 'https://www.totaljs.com';\n// opt.width {Number}\n// opt.height {Number}\n// opt.fg {String}\n// opt.bg {String}\nopt.callback = function(base64) {\n\tconsole.log(base64);\n};\nSETTER('qrcode/create', opt);\n```\n\nUse it with `<ui-component name=\"qrcode\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"qrcode\" config=\"width:200;height:200\"></ui-component>\n\n<script>\n\n\tON('ready', function() {\n\t\tvar opt = {};\n\t\topt.text = 'https://totaljs.com';\n\t\topt.callback = function(base64) {\n\t\t\tvar img = document.createElement('IMG');\n\t\t\timg.setAttribute('src', base64);\n\t\t\tdocument.body.append(img);\n\t\t};\n\t\tSETTER('qrcode/create', opt);\n\t});\n\n</script>\n```"}],"knowledge":{"content_sha256":"0b671644d91eda34269f8e120283b93e071506f44f74151b25be9a8c058aac1c","source_sha256":"da65126de8f46500d2cbc2c5e2547ce771ee304aea3d1bb22dba7269d2dc5b42","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","FFFFFF","IMG","MIT","QRC","SETTER"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"qrcode\" config=\"width:200;height:200\"></ui-component>\n\n<script>\n\n\tON('ready', function() {\n\t\tvar opt = {};\n\t\topt.text = 'https://totaljs.com';\n\t\topt.callback = function(base64) {\n\t\t\tvar img = document.createElement('IMG');\n\t\t\timg.setAttribute('src', base64);\n\t\t\tdocument.body.append(img);\n\t\t};\n\t\tSETTER('qrcode/create', opt);\n\t});\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/qrcodegenerator","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/qrcodegenerator/","markdown_url":"https://forge.totaljs.es/en/components/qrcodegenerator.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"65faecb02bca93a261f420efb2736039d52f6a3f97fdfe10257b74621f434178","checked_at":"2026-10-20T12:23:17.287Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-QRCodeGenerator","description":"j-QRCodeGenerator: easy usage; uses <http://goqr.me> server","messages":[{"role":"user","content":"j-QRCodeGenerator\n\nj-QRCodeGenerator: easy usage; uses <http://goqr.me> server"},{"role":"assistant","content":"- uses <http://goqr.me> server\n\n\n__Configuration__:\n\n- `size {Number}` in pixels, default: `200`\n\nUse it with `<ui-component name=\"qrcodegenerator\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"qrcode\" path=\"mytext\"></ui-component>\n\n<script>\n\tvar mytext = 'https://www.componentator.com';\n</script>\n```"}],"knowledge":{"content_sha256":"8225c46c98972bc2378dfb9d66ffbfc1072ed4d690c348c87741b000aebf2207","source_sha256":"65faecb02bca93a261f420efb2736039d52f6a3f97fdfe10257b74621f434178","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"qrcode\" path=\"mytext\"></ui-component>\n\n<script>\n\tvar mytext = 'https://www.componentator.com';\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/radiobutton","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/radiobutton/","markdown_url":"https://forge.totaljs.es/en/components/radiobutton.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"bde291eaf609b6ec974054019ecb21392f197d8a210857bc5d5b394908e79d02","checked_at":"2026-10-20T12:23:18.166Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-RadioButton","description":"j-RadioButton: for custom design you can use j-RadioButtonExpert; now you can use datasource","messages":[{"role":"user","content":"j-RadioButton\n\nj-RadioButton: for custom design you can use j-RadioButtonExpert; now you can use datasource"},{"role":"assistant","content":"- for custom design you can use __j-RadioButtonExpert__\n- now you can use datasource\n\n__Configuration__:\n\n- `items` {String} needs to contain `text 1|value 1;text 2|value 2` or `text 1;text 2`\n- `required` {Boolean} enables `required`\n- `disabled` {Boolean} disables this control\n- `label` {String} can change the content (default: `innerHTML`)\n- `type` {String} can contain `number`\n- `inline` {Boolean} enables/disables inline position (default: `true`)\n- `datasource` {String} path to data-source (must be array or array of objects)\n- `text` {String} determines a property name for text (in data-source), default: `name`\n- `value` {String} determines a property name for value (in data-source) default: `id`\n\nUse it with `<ui-component name=\"radiobutton\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"radiobutton\" path=\"model.myvalue1\" config=\"items:Yes|0,No|1,Maybe|2;type:number;required:true;inline:0\" default=\"0\">Are you crazy?</ui-component>\n<br />\n<ui-component name=\"radiobutton\" path=\"model.myvalue2\" config=\"datasource:frameworks;required:true\">What is the best node.js framework?</ui-component>\n<br />\n<ui-component name=\"radiobutton\" path=\"model.myvalue3\" config=\"datasource:list;required:true\">Total.js is framework for...</ui-component>\n\n<script type=\"text/javascript\">\n\tvar frameworks = ['Total.js', 'express', 'Hapi', 'Koa', 'Sails'];\n\tvar list = [{ name: 'Ruby', id: 1 }, { name: 'PHP', id: 2 }, { name: 'node.js', id: 3 }, { name: 'Python', id: 4 }];\n</script>\n```"}],"knowledge":{"content_sha256":"475c79010ff2d9387edc39db83d5a5f471e5d65ffdb8b884419ec94ff5eaf71a","source_sha256":"bde291eaf609b6ec974054019ecb21392f197d8a210857bc5d5b394908e79d02","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","PHP","Total.js"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"radiobutton\" path=\"model.myvalue1\" config=\"items:Yes|0,No|1,Maybe|2;type:number;required:true;inline:0\" default=\"0\">Are you crazy?</ui-component>\n<br />\n<ui-component name=\"radiobutton\" path=\"model.myvalue2\" config=\"datasource:frameworks;required:true\">What is the best node.js framework?</ui-component>\n<br />\n<ui-component name=\"radiobutton\" path=\"model.myvalue3\" config=\"datasource:list;required:true\">Total.js is framework for...</ui-component>\n\n<script type=\"text/javascript\">\n\tvar frameworks = ['Total.js', 'express', 'Hapi', 'Koa', 'Sails'];\n\tvar list = [{ name: 'Ruby', id: 1 }, { name: 'PHP', id: 2 }, { name: 'node.js', id: 3 }, { name: 'Python', id: 4 }];\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/radiobuttonexpert","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/radiobuttonexpert/","markdown_url":"https://forge.totaljs.es/en/components/radiobuttonexpert.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"2017de0981a69fc2b60772b64f5292d9045ea32cb6616f057fcb7c58ec04c811","checked_at":"2026-10-20T12:23:18.905Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-RadioButtonExpert","description":"Perfect component for radiobutton with custom styles. It's a little bit advanced but absolutely worth it!","messages":[{"role":"user","content":"j-RadioButtonExpert\n\nPerfect component for radiobutton with custom styles. It's a little bit advanced but absolutely worth it!"},{"role":"assistant","content":"Perfect component for radiobutton with custom styles. It's a little bit advanced but absolutely worth it!\n\n\n__Configuration__\n\n- `label {String}` can change the title label\n- `required {Boolean}` enables `required` (default: `false`)\n- `disabled {Boolean}` disables this control (default: `false`)\n- `type {String}` can contain `number` (default: `string`)\n- `class {String}` inserts a class into an element with class `.ui-radiobuttonexpert-container`\n- `datasource {String}` path to data-source (must be array or array of objects) or __NEW__: `id|name,id|name`\n- `value {String}` determines a property name for value (in data-source) (default: `id`)\n- `disabledkey {String}` determines a property name for disabled item (in data-source) (default: `disabled`)\n- __NEW__ `selector {String}` a jQuery selector for custom template (outside of the component). Works like `custom template` in [data-binding](https://wiki.totaljs.com/jcomponent/08-data-binding/)\n\n__Good to know__\n\n- You must write **all the styles**\n- Example object `{ name: 'My item', id: 1, disabled: false, classname: 'color-highlighted' }`\n- You can use `$index` and `$path` in template\n\n__Basic Styles__\n\n```css\n.my-button { display: inline-block; padding: 20px; margin-right: 10px; border: 1px solid gray; cursor: pointer; border-radius: 2px; }\n.my-button:hover { border-color: #4285F4; }\n.my-button.selected { color: white; background: #4285F4; border-color: rgba(0, 0, 0, 0.2); }\n.my-button[data-disabled=\"1\"] { background-color: #dcdcdc; color: gray; border-color: #dcdcdc; cursor: not-allowed;}\n.ui-disabled .my-button { background-color: #dcdcdc; color: gray; border-color: #dcdcdc; cursor: not-allowed; }\n.ui-disabled .my-button:hover { background-color: #dcdcdc; color: gray; border-color: #dcdcdc; }\n.ui-disabled .my-button.selected { border-color: gray; background: #efefef; color: #5a5a5a; }\n\n/* Dark mode */\n.ui-dark .my-button { border-color: 1px solid #404040; color: #adadad; }\n.ui-dark .my-button.selected { color: white; }\n.ui-dark .my-button[data-disabled=\"1\"] { background-color: #525252; color: gray; border-color: #525252; }\n.ui-dark .ui-disabled .my-button { background-color: #525252; color: gray; border-color: #525252; }\n.ui-dark .ui-disabled .my-button:hover { background-color: #525252; color: gray; border-color: #525252; }\n.ui-dark .ui-disabled .my-button.selected { border-color: rgba(0, 0, 0, 0.2); background: #969696; color: #ffffff; }\n\n/* Large mode */\n.ui-large .my-button { font-size: 16px; }\n```\n\nUse it with `<ui-component name=\"radiobuttonexpert\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Denis Granec. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\t.my-button { display: inline-block; padding: 10px 20px; margin-right: 10px; border: 1px solid gray; cursor: pointer; border-radius: 2px; }\n\t.my-button:hover { border-color: #4285F4; }\n\t.my-button.selected { color: white; background: #4285F4; border-color: rgba(0, 0, 0, 0.2); }\n\t.my-button[data-disabled=\"1\"] { background-color: #dcdcdc; color: gray; border-color: #dcdcdc; cursor: not-allowed;}\n\t.ui-disabled .my-button { background-color: #dcdcdc; color: gray; border-color: #dcdcdc; cursor: not-allowed; }\n\t.ui-disabled .my-button:hover { background-color: #dcdcdc; color: gray; border-color: #dcdcdc; }\n\t.ui-disabled .my-button.selected { border-color: gray; background: #efefef; color: #5a5a5a; }\n\n\t.ui-dark .my-button { border-color: #404040; color: #adadad; }\n\t.ui-dark .my-button.selected { color: white; }\n\t.ui-dark .my-button[data-disabled=\"1\"] { background-color: #525252; color: gray; border-color: #525252; }\n\t.ui-dark .ui-disabled .my-button { background-color: #525252; color: gray; border-color: #525252; }\n\t.ui-dark .ui-disabled .my-button:hover { background-color: #525252; color: gray; border-color: #525252; }\n\t.ui-dark .ui-disabled .my-button.selected { border-color: rgba(0, 0, 0, 0.2); background: #969696; color: #ffffff; }\n\t.ui-large .my-button { font-size: 16px; }\n\n\t.star-rating > div { display: inline-block; width: 30px; height: 30px; line-height: 29px; text-align: center; margin-right: 5px; border: 1px solid gray; cursor: pointer; border-radius: 50px; font-weight: bold; }\n\t.star-rating > div:hover { border-color: #bada55; }\n\t.star-rating > div.selected { color: white; background: #bada55; border-color: rgba(0, 0, 0, 0.2); }\n\t.star-rating > div.purple:hover { border-color: purple; }\n\t.star-rating .purple.selected { background-color: purple; }\n\t.star-rating > div[data-disabled=\"1\"] { background-color: #dcdcdc; color: gray; border-color: #dcdcdc; }\n</style>\n\n<ui-component name=\"radiobuttonexpert\" path=\"model.framework\" config=\"datasource:list;disabled:1;required:true;label:Your favorite language;type:number\" default=\"2\">\n\t<script type=\"text/html\">\n\t\t<div class=\"my-button\">\n\t\t\t<div>{{ name }}</div>\n\t\t</div>\n\t</script>\n</ui-component>\n<hr />\n<ui-component name=\"radiobuttonexpert\" path=\"model.framework\" config=\"selector:#mybutton;datasource:list;required:true;label:Your favorite language;type:number\" default=\"2\">\n\t<script type=\"text/html\" id=\"mybutton\">\n\t\t<div class=\"my-button\">\n\t\t\t<div>{{ name }}</div>\n\t\t\t<ui-bind path=\"timer\" config=\"text\" class=\"center block\" style=\"font-size:11px\"></ui-bind>\n\t\t</div>\n\t</script>\n</ui-component>\n<hr />\n<ui-component name=\"radiobuttonexpert\" path=\"model.rating\" config=\"datasource:rating;class:star-rating;label:Please, rate us\">\n\t<script type=\"text/html\">\n\t\t<div class=\"button{{ if classname }} {{ classname }}{{ fi }}\">\n\t\t\t<div>{{ name }}</div>\n\t\t</div>\n\t</script>\n</ui-component>\n\n<script>\n\tvar list = [{ name: 'Total.js', id: 1 }, { name: 'express', id: 2 }, { name: 'Hapi', id: 3, disabled: false }, { name: 'Koa', id: 4, disabled: true }, { name: 'Sails', id: 5 }, { name: 'Meteor', id: 6 }, { name: 'Adonis', id: 7 }, { name: 'Nest', id: 8 }];\n\tvar rating = [{ name: '1', id: 1, disabled: 1 }, { name: '2', id: 2 }, { name: '3', id: 3 }, { name: '4', id: 4 }, { name: '5', id: 5, classname: 'purple' }];\n\tsetInterval(function() {\n\t\tvar path = 'timer';\n\t\tSET(path, (GET(path) || 0) + 1);\n\t}, 1000);\n</script>\n```"}],"knowledge":{"content_sha256":"842e66a0567d9a39ef4038a0218bd22d33085b2ecbb5443606676c3e274f264d","source_sha256":"2017de0981a69fc2b60772b64f5292d9045ea32cb6616f057fcb7c58ec04c811","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","GET","MIT","SET","Total.js"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\t.my-button { display: inline-block; padding: 10px 20px; margin-right: 10px; border: 1px solid gray; cursor: pointer; border-radius: 2px; }\n\t.my-button:hover { border-color: #4285F4; }\n\t.my-button.selected { color: white; background: #4285F4; border-color: rgba(0, 0, 0, 0.2); }\n\t.my-button[data-disabled=\"1\"] { background-color: #dcdcdc; color: gray; border-color: #dcdcdc; cursor: not-allowed;}\n\t.ui-disabled .my-button { background-color: #dcdcdc; color: gray; border-color: #dcdcdc; cursor: not-allowed; }\n\t.ui-disabled .my-button:hover { background-color: #dcdcdc; color: gray; border-color: #dcdcdc; }\n\t.ui-disabled .my-button.selected { border-color: gray; background: #efefef; color: #5a5a5a; }\n\n\t.ui-dark .my-button { border-color: #404040; color: #adadad; }\n\t.ui-dark .my-button.selected { color: white; }\n\t.ui-dark .my-button[data-disabled=\"1\"] { background-color: #525252; color: gray; border-color: #525252; }\n\t.ui-dark .ui-disabled .my-button { background-color: #525252; color: gray; border-color: #525252; }\n\t.ui-dark .ui-disabled .my-button:hover { background-color: #525252; color: gray; border-color: #525252; }\n\t.ui-dark .ui-disabled .my-button.selected { border-color: rgba(0, 0, 0, 0.2); background: #969696; color: #ffffff; }\n\t.ui-large .my-button { font-size: 16px; }\n\n\t.star-rating > div { display: inline-block; width: 30px; height: 30px; line-height: 29px; text-align: center; margin-right: 5px; border: 1px solid gray; cursor: pointer; border-radius: 50px; font-weight: bold; }\n\t.star-rating > div:hover { border-color: #bada55; }\n\t.star-rating > div.selected { color: white; background: #bada55; border-color: rgba(0, 0, 0, 0.2); }\n\t.star-rating > div.purple:hover { border-color: purple; }\n\t.star-rating .purple.selected { background-color: purple; }\n\t.star-rating > div[data-disabled=\"1\"] { background-color: #dcdcdc; color: gray; border-color: #dcdcdc; }\n</style>\n\n<ui-component name=\"radiobuttonexpert\" path=\"model.framework\" config=\"datasource:list;disabled:1;required:true;label:Your favorite language;type:number\" default=\"2\">\n\t<script type=\"text/html\">\n\t\t<div class=\"my-button\">\n\t\t\t<div>{{ name }}</div>\n\t\t</div>\n\t</script>\n</ui-component>\n<hr />\n<ui-component name=\"radiobuttonexpert\" path=\"model.framework\" config=\"selector:#mybutton;datasource:list;required:true;label:Your favorite language;type:number\" default=\"2\">\n\t<script type=\"text/html\" id=\"mybutton\">\n\t\t<div class=\"my-button\">\n\t\t\t<div>{{ name }}</div>\n\t\t\t<ui-bind path=\"timer\" config=\"text\" class=\"center block\" style=\"font-size:11px\"></ui-bind>\n\t\t</div>\n\t</script>\n</ui-component>\n<hr />\n<ui-component name=\"radiobuttonexpert\" path=\"model.rating\" config=\"datasource:rating;class:star-rating;label:Please, rate us\">\n\t<script type=\"text/html\">\n\t\t<div class=\"button{{ if classname }} {{ classname }}{{ fi }}\">\n\t\t\t<div>{{ name }}</div>\n\t\t</div>\n\t</script>\n</ui-component>\n\n<script>\n\tvar list = [{ name: 'Total.js', id: 1 }, { name: 'express', id: 2 }, { name: 'Hapi', id: 3, disabled: false }, { name: 'Koa', id: 4, disabled: true }, { name: 'Sails', id: 5 }, { name: 'Meteor', id: 6 }, { name: 'Adonis', id: 7 }, { name: 'Nest', id: 8 }];\n\tvar rating = [{ name: '1', id: 1, disabled: 1 }, { name: '2', id: 2 }, { name: '3', id: 3 }, { name: '4', id: 4 }, { name: '5', id: 5, classname: 'purple' }];\n\tsetInterval(function() {\n\t\tvar path = 'timer';\n\t\tSET(path, (GET(path) || 0) + 1);\n\t}, 1000);\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/range","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/range/","markdown_url":"https://forge.totaljs.es/en/components/range.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"1accc59373f293d3abda894a15f7c12b68691c9e5ff9acc83bef7f47765a9f2b","checked_at":"2026-10-20T12:23:19.785Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Range","description":"j-Range: supports dark mode","messages":[{"role":"user","content":"j-Range\n\nj-Range: supports dark mode"},{"role":"assistant","content":"- supports __dark mode__\n\n__Attributes__:\n\n- `max` {Number} maximum value (default: `100`)\n- `min` {Number} minimum value (default: `0`)\n- `step` {Number} step size (default: `1`)\n- `required` {Boolean} optional, enables \"required\" (default: `false`)\n- `type` {String} optional, can be `number` and converts the value to number (default: `string`)\n\nUse it with `<ui-component name=\"range\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"margin: 80px auto 0;width:400px;\">\n\t<ui-component name=\"range\" path=\"myrange\" config=\"required:true;type:number\">Select size</ui-component>\n</div>\n\n<script>\n\tvar myrange = 30;\n</script>\n```"}],"knowledge":{"content_sha256":"41ea0b6e3dd142d2430b840e64606d13de93dc3264d680b727e7cd577e196c06","source_sha256":"1accc59373f293d3abda894a15f7c12b68691c9e5ff9acc83bef7f47765a9f2b","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"margin: 80px auto 0;width:400px;\">\n\t<ui-component name=\"range\" path=\"myrange\" config=\"required:true;type:number\">Select size</ui-component>\n</div>\n\n<script>\n\tvar myrange = 30;\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/rating","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/rating/","markdown_url":"https://forge.totaljs.es/en/components/rating.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"23b6cc45402ae37f415ad27498cc9a2796d1c90c68c15e504ccaa15c9e5e19cb","checked_at":"2026-10-20T12:23:20.673Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Rating","description":"j-Rating: easy usage","messages":[{"role":"user","content":"j-Rating\n\nj-Rating: easy usage"},{"role":"assistant","content":"Use it with `<ui-component name=\"rating\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<br />\n\t<ui-component name=\"rating\" path=\"rating\" config=\"null\" default=\"3\">Please rate our products and services:</ui-component>\n<br />\n```"}],"knowledge":{"content_sha256":"0a9aa25b9a281aa9122de9b43a9a72f87931cc2b2e84dc85b3985ccba7a19931","source_sha256":"23b6cc45402ae37f415ad27498cc9a2796d1c90c68c15e504ccaa15c9e5e19cb","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<br />\n\t<ui-component name=\"rating\" path=\"rating\" config=\"null\" default=\"3\">Please rate our products and services:</ui-component>\n<br />"}],"test":null,"test_kind":"component-browser"}
{"id":"components/raweditable","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/raweditable/","markdown_url":"https://forge.totaljs.es/en/components/raweditable.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"a43656722a39e81450871b7d7ea148013b69df2cbab747fcc54e2c8d2529c78b","checked_at":"2026-10-20T12:23:21.560Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-RawEditable","description":"j-RawEditable is a simple component with a big hidden functionality. It's targeted for additional customization.","messages":[{"role":"user","content":"j-RawEditable\n\nj-RawEditable is a simple component with a big hidden functionality. It's targeted for additional customization."},{"role":"assistant","content":"__j-RawEditable__ is a simple component with a big hidden functionality. It's targeted for additional customization.\n\n\n__Configuration__:\n\n- `type {String}` optional, can be `email`, `phone`, `zip`, `date`, `time`, `url`, `number`, `search`, `lower`, `upper` or empty (default)\n- `formatting {Boolean}` enables formatting bold, italic, etc., default: `false`\n- `required {Boolean}` optional, enables \"required\" (default: `false`)\n- `placeholder {String}` optional, adds a `placeholder` text into the input\n- `maxlength {Number}` optional, sets a maximum length of chars (default: `200`)\n- `minlength {Number}` optional, sets a minimum length of chars\n- `minvalue {Number}` optional, a minimal value for `number` type\n- `maxvalue {Number}` optional, a maximal value for `number` type\n- `validate {String}` optional, a condition for validating of value, can contain a link to `function(value)` or `!!value.match(/[a-z]+/)`\n- `format {String}` optional, output formatting e.g. for `date` type: `yyyy-MM-dd`, for `time` type: `HH:mm`, for `number` you can define max. decimals\n- `disabled {Boolean}` optional, disables this component\n- `autocomplete {String}` optional, needs to contain a link to a function, is triggered on `focus` event\n- `spaces {Boolean}` optional, enables spaces otherwise it removes them (default: `true`)\n- `autosource {String}` a path to `search` function in `autocomplete`, `function(search, render(arr))`\n- `autovalue {String}` a property path for the value in `autosource`, default: `name`\n- `autoexec {String}` a path method `function(item, next(value_to_input))`\n- `forcevalidation {Boolean}` enables for force validation for `phone` and `email` (default: `true`)\n- `monospace {Boolean}` enables `monospaced` font (default: `false`)\n- `enter {String}` a link to `function(val, com)` - evaluated when the user presses enter\n\nUse it with `<ui-component name=\"raweditable\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<b>Type something</b>: <span><ui-component name=\"raweditable\" path=\"form.name\" config=\"placeholder:Placeholder\" style=\"position:relative;display:inline-block;min-width:80px;height:24px;line-height:20px;border:2px solid blue;padding:0 5px\"></ui-component></span>\n\n<script>\n\tvar form = { name: 'Peter' };\n</script>\n```"}],"knowledge":{"content_sha256":"0f36f17ea6ef34bf9119f86090d868fbe8769108d38a43546c79b3729ac1a67f","source_sha256":"a43656722a39e81450871b7d7ea148013b69df2cbab747fcc54e2c8d2529c78b","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<b>Type something</b>: <span><ui-component name=\"raweditable\" path=\"form.name\" config=\"placeholder:Placeholder\" style=\"position:relative;display:inline-block;min-width:80px;height:24px;line-height:20px;border:2px solid blue;padding:0 5px\"></ui-component></span>\n\n<script>\n\tvar form = { name: 'Peter' };\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/rawinput","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/rawinput/","markdown_url":"https://forge.totaljs.es/en/components/rawinput.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"aefccec9e1ae3cd75170af71fb3fbcf7cc49bb8bd9b902909d78fd17ab992abc","checked_at":"2026-10-20T12:23:22.434Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-RawInput","description":"j-RawInput is a simple input component with a big hidden functionality. It's targeted for additional customization.","messages":[{"role":"user","content":"j-RawInput\n\nj-RawInput is a simple input component with a big hidden functionality. It's targeted for additional customization."},{"role":"assistant","content":"__j-RawInput__ is a simple input component with a big hidden functionality. It's targeted for additional customization.\n\n\n__Configuration__:\n\n- `type {String}` optional, can be `email`, `phone`, `password`, `zip`, `date`, `time`, `url`, `number`, `search`, `lower`, `upper` or empty (default)\n- `required {Boolean}` optional, enables \"required\" (default: `false`)\n- `autofocus {Boolean}` optional, focuses the input (default: `false`)\n- `autofill {Boolean}` optional, enables browser's autofill feature (default: `false`)\n- `placeholder {String}` optional, adds a `placeholder` text into the input\n- `maxlength {Number}` optional, sets a maximum length of chars (default: `200`)\n- `minlength {Number}` optional, sets a minimum length of chars\n- `minvalue {Number}` optional, a minimal value for `number` type\n- `maxvalue {Number}` optional, a maximal value for `number` type\n- `validate {String}` optional, a condition for validating of value, can contain a link to `function(value)` or `!!value.match(/[a-z]+/)`\n- `format {String}` optional, output formatting e.g. for `date` type: `yyyy-MM-dd`, for `time` type: `HH:mm`, for `number` you can define max. decimals\n- `disabled {Boolean}` optional, disables this component\n- `autocomplete {String}` optional, needs to contain a link to a function, is triggered on `focus` event\n- `spaces {Boolean}` optional, enables spaces otherwise it removes them (default: `true`)\n- `autosource {String}` a path to `search` function in `autocomplete`, `function(search, render(arr))`\n- `autovalue {String}` a property path for the value in `autosource`, default: `name`\n- `autoexec {String}` a path method `function(item, next(value_to_input))`\n- `forcevalidation {Boolean}` enables for force validation for `phone` and `email` (default: `true`)\n- `monospace {Boolean}` enables `monospaced` font (default: `false`)\n- `enter {String}` a link to `function(val, com)` - evaluated when the user presses enter\n- __NEW__: `realtime {Boolean}` enables real-time binding (default: `true`)\n- __NEW__: `exec {String}` a link to the `function(value, element)` which will be evaluated if the input is affected\n\nUse it with `<ui-component name=\"rawinput\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"rawinput\" path=\"form.name\"></ui-component>\n\n<script>\n\tvar form = { name: 'Peter' };\n</script>\n```"}],"knowledge":{"content_sha256":"bc158aa78dafe6dbb89cf0e743df80f054479463c2d3293e0a9b95a86809fb9d","source_sha256":"aefccec9e1ae3cd75170af71fb3fbcf7cc49bb8bd9b902909d78fd17ab992abc","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"rawinput\" path=\"form.name\"></ui-component>\n\n<script>\n\tvar form = { name: 'Peter' };\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/ready","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/ready/","markdown_url":"https://forge.totaljs.es/en/components/ready.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"03510a3949cfb2514c5b3917f5f6ae45522bcc052a15fedb5db98ea7e6be6dd6","checked_at":"2026-10-20T12:23:23.315Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Ready","description":"This component executes some operations when the component is ready.","messages":[{"role":"user","content":"j-Ready\n\nThis component executes some operations when the component is ready."},{"role":"assistant","content":"This component executes some operations when the component is ready. \n\n\n__Configuration__:\n\n- `rclass {String}` A list of CSS classes to remove divided by space\n- `rdelay {Number}` A delay for removing of classes (default: `config.delay`)\n- `aclass {String}` A list of CSS classes to add divided by space\n- `adelay {Number}` A delay for adding of classes (default: `config.delay`)\n- `delay {Number}` Initializatin delay in milliseconds (default: `800`)\n\nUse it with `<ui-component name=\"ready\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\t.ready { width: 200px; height: 200px; border: 2px solid red; padding: 20px; }\n</style>\n\n<ui-component name=\"ready\" config=\"aclass:ready\" class=\"invisible\">\n\tHello World!\n</ui-component>\n```"}],"knowledge":{"content_sha256":"ddd2d91b6c665b7bc07d9bc258a8f040df467330ceb48286d42ffa336e5be9b1","source_sha256":"03510a3949cfb2514c5b3917f5f6ae45522bcc052a15fedb5db98ea7e6be6dd6","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","CSS","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\t.ready { width: 200px; height: 200px; border: 2px solid red; padding: 20px; }\n</style>\n\n<ui-component name=\"ready\" config=\"aclass:ready\" class=\"invisible\">\n\tHello World!\n</ui-component>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/recaptcha","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/recaptcha/","markdown_url":"https://forge.totaljs.es/en/components/recaptcha.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"fb48de4a445d5a775bf821cd19bcf3da78666e7066da8957a1d8ecc1e31d26e1","checked_at":"2026-10-20T12:23:24.503Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-reCAPTCHA","description":"Display a reCAPTCHA challenge and bind its response token to the model.","messages":[{"role":"user","content":"j-reCAPTCHA\n\nDisplay a reCAPTCHA challenge and bind its response token to the model."},{"role":"assistant","content":"- [Create reCAPTCHA API KEY](https://www.google.com/recaptcha/admin)\n- [How to __verify reCAPTCHA response__ on the server-side?](https://blog.totaljs.com/blogs/tutorials/20161211-how-to-verify-recaptcha-with-help-of-total-js/)\n\n\n__Configuration__:\n- `key {String}` (required) Google API key\n- `required {Boolean}` enables `required`\n- `theme {String}` can be `light` or `dark` (default: `light`)\n\nUse it with `<ui-component name=\"recaptcha\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"recaptcha\" path=\"form.recaptcha\" config=\"key:6Ldreg4UAAAAABGC2PK9RmKupaPCiCmbifZrUXc6\"></ui-component>\n\n<script>\n\tvar form = {};\n</script>\n```"}],"knowledge":{"content_sha256":"58ba32aec7fe161b4d86b4ecb1234b86532035a71aeb427e32658a2b3e2872df","source_sha256":"fb48de4a445d5a775bf821cd19bcf3da78666e7066da8957a1d8ecc1e31d26e1","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["API","CDN","DEF","KEY","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"recaptcha\" path=\"form.recaptcha\" config=\"key:6Ldreg4UAAAAABGC2PK9RmKupaPCiCmbifZrUXc6\"></ui-component>\n\n<script>\n\tvar form = {};\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/releaser","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/releaser/","markdown_url":"https://forge.totaljs.es/en/components/releaser.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"94f5fe7639a9adc9d381c3af5f76bc4c80e9988f0f8adc43404501b2adb8b10e","checked_at":"2026-10-20T12:23:25.380Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Releaser","description":"The component can release after some time own content (DOM) + memory. We recommend to use it for j-DataGrid, j-CodeMirror, j-CloudEditor, etc..","messages":[{"role":"user","content":"j-Releaser\n\nThe component can release after some time own content (DOM) + memory. We recommend to use it for j-DataGrid, j-CodeMirror, j-CloudEditor, etc.."},{"role":"assistant","content":"The component can release after some time own content (DOM) + memory. We recommend to use it for `j-DataGrid`, `j-CodeMirror`, `j-CloudEditor`, etc..\n\n\n__Configuration__:\n\n- `if {String}` must contain a value for comparing with the model\n- `delay {Number}` delay for removing of `invisible` class (default: `500`)\n- `release {Number}` delay for releasing of the content in idle time (default: `10000`)\n- `bindvisible {Boolean}` it evalutes the content if the component is visible (default: `false`)\n- `released {String}` a link to the `function(component_element)` when the content is released\n- `render {String}` a link to the `function(component_element)` when the content is going to render\n\n__Example__:\n\n```html\n<ui-component name=\"releaser\" path=\"common.form\" config=\"if:userform\">\n\t<script type=\"text/html\">\n\t\t<ui-component name=\"datagrid\" path=\".......\">\n\t\t\t<SCR type=\"text/html\">\n\t\t\t\tYOUR COLUMNS\n\t\t\t</SCR>\n\t\t</ui-component>\n\t</script>\n</\n```\n\nUse it with `<ui-component name=\"releaser\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"exec\"></ui-component>\n\n<ui-component name=\"releaser\" path=\"common.form\" config=\"if:userform;release:2000\">\n\t<script type=\"text/html\">\n\t\t<i class=\"ti ti-thumbs-up\"></i> THE CONTENT IS RENDERED\n\t</script>\n</ui-component>\n<hr />\n<button class=\"exec\" data-exec=\"releaser_show\">Show</button>\n<button class=\"exec\" data-exec=\"releaser_release\">Release (2 sec)</button>\n\n<script>\n\tfunction releaser_show() {\n\t\tSET('common.form', 'userform');\n\t}\n\n\tfunction releaser_release() {\n\t\tNUL('common.form');\n\t}\n</script>\n```"}],"knowledge":{"content_sha256":"c3fb66cecefc89e7a96c687a1d1c3bb6298909250557e0e16dbf2886f8267508","source_sha256":"94f5fe7639a9adc9d381c3af5f76bc4c80e9988f0f8adc43404501b2adb8b10e","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","COLUMNS","CONTENT","DEF","DOM","MIT","NUL","RENDERED","SCR","SET","THE","YOUR"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"exec\"></ui-component>\n\n<ui-component name=\"releaser\" path=\"common.form\" config=\"if:userform;release:2000\">\n\t<script type=\"text/html\">\n\t\t<i class=\"ti ti-thumbs-up\"></i> THE CONTENT IS RENDERED\n\t</script>\n</ui-component>\n<hr />\n<button class=\"exec\" data-exec=\"releaser_show\">Show</button>\n<button class=\"exec\" data-exec=\"releaser_release\">Release (2 sec)</button>\n\n<script>\n\tfunction releaser_show() {\n\t\tSET('common.form', 'userform');\n\t}\n\n\tfunction releaser_release() {\n\t\tNUL('common.form');\n\t}\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/remember","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/remember/","markdown_url":"https://forge.totaljs.es/en/components/remember.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"f504e9aa4521f8c629335a131283d2b956577bf7f04a2c157567293b812fdaed","checked_at":"2026-10-20T12:23:26.264Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Remember","description":"The component calls a function within the repeat interval. Information about the previous call is stored in localStorage.","messages":[{"role":"user","content":"j-Remember\n\nThe component calls a function within the repeat interval. Information about the previous call is stored in localStorage."},{"role":"assistant","content":"The component calls a function within the repeat interval. Information about the previous call is stored in `localStorage`.\n\n__Configuration__:\n\n- `id {String}` a remember identifier\n- `repeat {String}` a repeat time e.g. `session` or `5 minutes` or `1 day`\n- `exec {String}` a link to the function that will be called\n- `delay {Number}` a caller delay in ms (default: `3000`)\n\n__Methods__:\n\n- `SETTER('remember/clear', 'id')`\n\nUse it with `<ui-component name=\"remember\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"remember\" config=\"exec:message;repeat:5 minutes\"></ui-component>\n\n<script>\n\n\tfunction message() {\n\t\talert('This message will be displayed every 5 minutes');\n\t}\n\n</script>\n```"}],"knowledge":{"content_sha256":"22964e1926f8dd8b3f2d93643dd73476084d4fa2d209c0e76d8c5c9171ffa26b","source_sha256":"f504e9aa4521f8c629335a131283d2b956577bf7f04a2c157567293b812fdaed","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","SETTER"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"remember\" config=\"exec:message;repeat:5 minutes\"></ui-component>\n\n<script>\n\n\tfunction message() {\n\t\talert('This message will be displayed every 5 minutes');\n\t}\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/resource","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/resource/","markdown_url":"https://forge.totaljs.es/en/components/resource.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"da92f190bce753631ad4590c7a24131e5e5358d7dbdd461fcc9f5403c939184a","checked_at":"2026-10-20T12:23:26.999Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Resource","description":"Resources are very great solution for e.g. translate some texts in all scripts. The component can be appended multiple-times and all components use one repository for storing keys and values.","messages":[{"role":"user","content":"j-Resource\n\nResources are very great solution for e.g. translate some texts in all scripts. The component can be appended multiple-times and all components use one repository for storing keys and values."},{"role":"assistant","content":"Resources are very great solution for e.g. translate some texts in all scripts. The component can be appended multiple-times and all components use one repository for storing keys and values.\n\n\n__METHODS__:\n\n- `RESOURCE(key, [def])` - is a global method\n- `FIND('resource').download('/my-dictionary.txt', [callback]);` - for importing new resources\n\nUse it with `<ui-component name=\"resource\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\nOpen Web Developer console.\n\n<ui-component name=\"resource\">\n\t<script type=\"text/plain\">\n\t\t\tyes     : Yes\n\t\t\tno      : No\n\t\t\tmessage : Are you sure?\n\t</script>\n</ui-component>\n\n<ui-component name=\"resource\">\n\t<script type=\"text/plain\">\n\t\t\tfirst     : Value of first\n\t\t\tsecond    : Value of second\n\t\t\ta b c     : ABC\n\t</script>\n</ui-component>\n\n<script>\n\n\tsetTimeout(function() {\n\t\tconsole.log(RESOURCE('yes'));\n\t\tconsole.log(RESOURCE('no'));\n\t\tconsole.log(RESOURCE('message'));\n\t\tconsole.log(RESOURCE('first'));\n\t\tconsole.log(RESOURCE('second'));\n\t\tconsole.log(RESOURCE('a b c'));\n\t\tconsole.log(RESOURCE('notfound', 'NOT FOUND'));\n\t}, 2000);\n\n</script>\n```"}],"knowledge":{"content_sha256":"7104cdd07cf4875e0833983d577a07667f8a8a5c3a8c9068d593c3671e4e02bc","source_sha256":"da92f190bce753631ad4590c7a24131e5e5358d7dbdd461fcc9f5403c939184a","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["ABC","CDN","DEF","FIND","FOUND","MIT","NOT","RESOURCE"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"Open Web Developer console.\n\n<ui-component name=\"resource\">\n\t<script type=\"text/plain\">\n\t\t\tyes     : Yes\n\t\t\tno      : No\n\t\t\tmessage : Are you sure?\n\t</script>\n</ui-component>\n\n<ui-component name=\"resource\">\n\t<script type=\"text/plain\">\n\t\t\tfirst     : Value of first\n\t\t\tsecond    : Value of second\n\t\t\ta b c     : ABC\n\t</script>\n</ui-component>\n\n<script>\n\n\tsetTimeout(function() {\n\t\tconsole.log(RESOURCE('yes'));\n\t\tconsole.log(RESOURCE('no'));\n\t\tconsole.log(RESOURCE('message'));\n\t\tconsole.log(RESOURCE('first'));\n\t\tconsole.log(RESOURCE('second'));\n\t\tconsole.log(RESOURCE('a b c'));\n\t\tconsole.log(RESOURCE('notfound', 'NOT FOUND'));\n\t}, 2000);\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/restart","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/restart/","markdown_url":"https://forge.totaljs.es/en/components/restart.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"95f7c9205b159ce3964fac4254e1ec94357633f5ec0d66361cbe78dada7dd354","checked_at":"2026-10-20T12:23:27.875Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Restart","description":"The component is very primitive and it will only display the content if the value is positive. It can be useful for single page applications that change a version in the background and the front end n","messages":[{"role":"user","content":"j-Restart\n\nThe component is very primitive and it will only display the content if the value is positive. It can be useful for single page applications that change a version in the background and the front end n"},{"role":"assistant","content":"The component is very primitive and it will only display the content if the value is positive. It can be useful for single page applications that change a version in the background and the front end needs to reload.\n\n\n__Configuration__:\n\n- `top {Number}` a top position (default: `75`)\n- `bottom {Number}` a top position (default: `0`)\n- `right {Number}` a top position (default: `20`)\n- `left {Number}` a top position (default: `0`)\n- `url {String}` optional, a link to the editor (default: `?reload=1`)\n\nUse it with `<ui-component name=\"restart\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"restart\" path=\"common.restart\" config=\"top:20;right:20\" class=\"hidden\"><i class=\"ti ti-bullhorn\"></i>New update. <b>Reload</b></ui-component>\n\n<script>\n\n\tvar common = {};\n\tsetTimeout(() => SET('common.restart', true), 2000);\n\n</script>\n```"}],"knowledge":{"content_sha256":"d1ba1333007a62d802122d9029141df69746716c645d49e75d7c11f4d304e3e8","source_sha256":"95f7c9205b159ce3964fac4254e1ec94357633f5ec0d66361cbe78dada7dd354","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","SET"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"restart\" path=\"common.restart\" config=\"top:20;right:20\" class=\"hidden\"><i class=\"ti ti-bullhorn\"></i>New update. <b>Reload</b></ui-component>\n\n<script>\n\n\tvar common = {};\n\tsetTimeout(() => SET('common.restart', true), 2000);\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/rows","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/rows/","markdown_url":"https://forge.totaljs.es/en/components/rows.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"98aa1cabcc577b99033da1f617afdf4f90301d9ec705c7183e65c4cc31b3df53","checked_at":"2026-10-20T12:23:28.767Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Rows","description":"The component Rows is a very simple alternative to j-Layout and j-Layout2.","messages":[{"role":"user","content":"j-Rows\n\nThe component Rows is a very simple alternative to j-Layout and j-Layout2."},{"role":"assistant","content":"The component `Rows` is a very simple alternative to `j-Layout` and `j-Layout2`.\n\n\n__Configuration__:\n\n- `parent {String}` can contain `parent`, `window` or jQuery selector for the `closest` method (default: `window`)\n- `margin {Number}` add margin to `height` (default: `0`)\n- `marginlg {Number}` adds margin to `height` for `LG` display (default: `margin`)\n- `marginmd {Number}` adds margin to `height` for `MD` display (default: `margin`)\n- `marginsm {Number}` adds margin to `height` for `SM` display (default: `margin`)\n- `marginxs {Number}` adds margin to `height` for `XS` display (default: `margin`)\n- `noborder {Boolean}` disables border (default: `false`)\n- `fontsize {Boolean}` enables percentual font-size according to the size of rows\n\n__Good to know__:\n\nEach row must be wrappend in `<section>`, try to understand from example.\n\n__Attributes__:\n\n- `data-size` contains a size of section `LG,MD,SM,XS` --> in pixels or percentage, example: `200,200,200,0` (`0` means hidden)\n- section without `data-size` attribute will be counted from the residual value\n\nUse it with `<ui-component name=\"rows\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>body { padding: 0; margin: 0; }</style>\n\n<ui-component name=\"rows\" config=\"parent:window\" class=\"hidden invisible\">\n\t<section data-size=\"33%\">Top panel</section>\n\t<section>Main</section>\n\t<section data-size=\"33%\">Bottom panel</section>\n</ui-component>\n```"}],"knowledge":{"content_sha256":"17642a4d077bf0f661d77f2506ed7a783dbeb2a4cb138c21b2e8b415b3d1f70e","source_sha256":"98aa1cabcc577b99033da1f617afdf4f90301d9ec705c7183e65c4cc31b3df53","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>body { padding: 0; margin: 0; }</style>\n\n<ui-component name=\"rows\" config=\"parent:window\" class=\"hidden invisible\">\n\t<section data-size=\"33%\">Top panel</section>\n\t<section>Main</section>\n\t<section data-size=\"33%\">Bottom panel</section>\n</ui-component>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/rules","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/rules/","markdown_url":"https://forge.totaljs.es/en/components/rules.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"58f668235c7ed9accbd55b6538e5367bb92501aae1229ec872457fc7701530d0","checked_at":"2026-10-20T12:23:29.638Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Rules","description":"The component can create a simple rules which you can transform to some engines like Total.js Flow. The component uses j-Directory component.","messages":[{"role":"user","content":"j-Rules\n\nThe component can create a simple rules which you can transform to some engines like Total.js Flow. The component uses j-Directory component."},{"role":"assistant","content":"The component can create a simple rules which you can transform to some engines like Total.js Flow. The component uses `j-Directory` component.\n\n\n__Configuration__:\n\n- `dirsearc {String}h` A placeholder for search input in `j-Directory`, default: `search`\n\n__Data declaration__:\n\n```js\nvar form = [];\nform.push({ enabled: true, label: 'CPU', name: 'cpu', type: 'number', comparer: '==', value: 0, note: 'Value in mega bytes' });\nform.push({ enabled: false, label: 'Memory', name: 'memory', type: 'number', comparer: '>', value: 0 });\nform.push({ enabled: true, label: 'Error', name: 'error', type: 'string', comparer: '==', items: [{ name: 'Error', id: 'error' }, { name: 'Obsolete', id: 'obsolete' }] });\nform.push({ enabled: false, label: 'Offline', name: 'offline', type: 'boolean', comparer: '==', value: 0 });\n```\n\n__Data properties in data declaration__:\n\n- `name {String}` an item identifier (required)\n- `label {String}` a label (required)\n- `note {String}` a small note under control\n- `type {String}` a type, lower-case (required)\n- `items {Object Array}` items for `string` type only  in the form `[{ id: String, name: String }]`\n- `enabled {Boolean}` enables item\n- `comparer {String}` can be `==`, `!=`, `>`, `>=`, `<=`, `<` (required)\n- `value {Number/Boolean/String}` a value\n\n__Allowed types in data__:\n\n- `string`\n- `boolean`\n- `number`\n\nUse it with `<ui-component name=\"rules\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"LAZY directory\"></ui-component>\n\n<div style=\"width:600px;padding:20px\">\n\t<ui-component name=\"rules\" path=\"form\"></ui-component>\n</div>\n\n<script>\n\tvar form = [];\n\tform.push({ enabled: true, label: 'CPU', name: 'cpu', type: 'number', comparer: '==', placeholder: '', value: 0, note: 'Value in mega bytes' });\n\tform.push({ enabled: false, label: 'Memory', name: 'memory', type: 'number', comparer: '>', placeholder: '', value: 0 });\n\tform.push({ enabled: true, label: 'Error', name: 'error', type: 'string', comparer: '==', items: [{ name: 'Error', id: 'error' }, { name: 'Obsolete', id: 'obsolete' }] });\n\tform.push({ enabled: false, label: 'Offline', name: 'offline', type: 'boolean', comparer: '==', value: 0 });\n</script>\n```"}],"knowledge":{"content_sha256":"aaac667b2b0ac38ecfd433ab93f019b31a45463a5eff564158f628cf5641c014","source_sha256":"58f668235c7ed9accbd55b6538e5367bb92501aae1229ec872457fc7701530d0","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","CPU","DEF","LAZY","MIT","Total.js"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"LAZY directory\"></ui-component>\n\n<div style=\"width:600px;padding:20px\">\n\t<ui-component name=\"rules\" path=\"form\"></ui-component>\n</div>\n\n<script>\n\tvar form = [];\n\tform.push({ enabled: true, label: 'CPU', name: 'cpu', type: 'number', comparer: '==', placeholder: '', value: 0, note: 'Value in mega bytes' });\n\tform.push({ enabled: false, label: 'Memory', name: 'memory', type: 'number', comparer: '>', placeholder: '', value: 0 });\n\tform.push({ enabled: true, label: 'Error', name: 'error', type: 'string', comparer: '==', items: [{ name: 'Error', id: 'error' }, { name: 'Obsolete', id: 'obsolete' }] });\n\tform.push({ enabled: false, label: 'Offline', name: 'offline', type: 'boolean', comparer: '==', value: 0 });\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/scanner","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/scanner/","markdown_url":"https://forge.totaljs.es/en/components/scanner.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"66b0f8fbf85dbaa5cf378e4dedf8e9b7f56ec8dd53505b8cd48c5a5f80db81c7","checked_at":"2026-10-20T12:23:30.359Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Scanner","description":"The component for scanning documents.","messages":[{"role":"user","content":"j-Scanner\n\nThe component for scanning documents."},{"role":"assistant","content":"The component for scanning documents.\n\n- singleton\n\n__Methods__:\n\n- `component.show(opt)`\n\t- `opt.points = [left+right {Number}, top+bottom {Number}]` mask size defined in the percentage\n\t- `opt.callback = function(base64)` The callback function, which will be executed when the user approves taken document. This callback function thakes an image in **base64** format as a parameter\n\nUse it with `<ui-component name=\"scanner\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Kirill Zlobin. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"scanner\"></ui-component>\n<button class=\"button\">Open Scanner</button>\n\n<script>\n\n\t$(document).find('.button').on('click', function() {\n\n\t\tvar opt = {};\n\t\topt.size = '';\n\t\topt.points = [40, 30];\n\n\t\topt.callback = function(base64) {\n\t\t\tconsole.log(base64);\n\t\t}\n\n\t\tSETTER('scanner/show', opt);\n\t});\n\n</script>\n```"}],"knowledge":{"content_sha256":"4a6e0f0ae3e4fc351bbd0942c3e8c9629b2d68014bb56c3a7c8251d96ecc82e9","source_sha256":"66b0f8fbf85dbaa5cf378e4dedf8e9b7f56ec8dd53505b8cd48c5a5f80db81c7","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","SETTER"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"scanner\"></ui-component>\n<button class=\"button\">Open Scanner</button>\n\n<script>\n\n\t$(document).find('.button').on('click', function() {\n\n\t\tvar opt = {};\n\t\topt.size = '';\n\t\topt.points = [40, 30];\n\n\t\topt.callback = function(base64) {\n\t\t\tconsole.log(base64);\n\t\t}\n\n\t\tSETTER('scanner/show', opt);\n\t});\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/scrollbar","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/scrollbar/","markdown_url":"https://forge.totaljs.es/en/components/scrollbar.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"27e682fc149a36386b1a23dcc4be6daa231ce08475ccf1e6d93b5970ce484b21","checked_at":"2026-10-20T12:23:31.086Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Scrollbar","description":"Custom scrollbars. IMPORTANT: It wraps current content by adding two nested div elements with relative position. It works with jComponent v17 only.","messages":[{"role":"user","content":"j-Scrollbar\n\nCustom scrollbars. IMPORTANT: It wraps current content by adding two nested div elements with relative position. It works with jComponent v17 only."},{"role":"assistant","content":"Custom scrollbars. __IMPORTANT:__ It wraps current content by adding two nested `div` elements with `relative` position. __It works with jComponent v17 only__.\n\n__Configuration__:\n\n- `parent` {String} selector for a parent element, can contain `window` (default: `undefined`)\n- `visibleX` {Boolean} always shows horizontal scrollbar (default: `false`)\n- `visibleY` {Boolean} always shows vertical scrollbar (default: `false`)\n- `offset` {Boolean} adds `offset.top` of this element (default: `false`)\n- `reset` {Boolean} resets position if the `path` is changed (default: `true`)\n- `margin` {Number} a margin for all types of displays (default: `0`)\n- `marginxs` {Number} a margin for `XS` displays (default: `0`)\n- `marginsm` {Number} a margin for `SM` displays (default: `0`)\n- `marginmd` {Number} a margin for `MD` displays (default: `0`)\n- `marginlg` {Number} a margin for `LG` displays (default: `0`)\n- `track` {String} can track only defined paths e.g. `FIELD1,FIELD2,FIELD3`, paths will linked with `component.path`\n\n__Methods__:\n\n- `component.reset()` resets the position to `0,0`\n- `component.scrollleft(val)` scrolls X\n- `component.scrolltop(val)` scrolls Y\n- `component.scrollright(val)` scrolls X from right side\n- `component.scrollbottom(val)` scrolls Y from bottom side\n- `component.scroll(x, y)` scrolls X,Y\n- `component.resize()` resizes container (it reacts on `resize` events automatically)\n\nUse it with `<ui-component name=\"scrollbar\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"scrollbar\" style=\"background-color:rgba(100,100,100,0.05);height:200px;width:300px\" class=\"hidden\" data-jc-class=\"hidden\">\n\t<div>1</div>\n\t<div>2</div>\n\t<div>3</div>\n\t<div>4</div>\n\t<div>5</div>\n\t<div>6</div>\n\t<div>7</div>\n\t<div>8</div>\n\t<div>9</div>\n\t<div>10</div>\n\t<div>1</div>\n\t<div>2</div>\n\t<div>3</div>\n\t<div>4</div>\n\t<div>5</div>\n\t<div>6</div>\n\t<div>7</div>\n\t<div>8</div>\n\t<div>9</div>\n\t<div>10</div>\n\t<div>1</div>\n\t<div>2</div>\n\t<div>3</div>\n\t<div>4</div>\n\t<div>5</div>\n\t<div>6</div>\n\t<div>7</div>\n\t<div>8</div>\n\t<div>9</div>\n\t<div>10</div>\n\t<div>1</div>\n\t<div>2</div>\n\t<div>3</div>\n\t<div>4</div>\n\t<div>5</div>\n\t<div>6</div>\n\t<div>7</div>\n\t<div>8</div>\n\t<div>9</div>\n\t<div>10</div>\n</ui-component>\n```"}],"knowledge":{"content_sha256":"91f89113cd8f6a15c7c8664e0d8ac59c996092e5e1939864458e36ca6c8216fa","source_sha256":"27e682fc149a36386b1a23dcc4be6daa231ce08475ccf1e6d93b5970ce484b21","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","FIELD1","FIELD2","FIELD3","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"scrollbar\" style=\"background-color:rgba(100,100,100,0.05);height:200px;width:300px\" class=\"hidden\" data-jc-class=\"hidden\">\n\t<div>1</div>\n\t<div>2</div>\n\t<div>3</div>\n\t<div>4</div>\n\t<div>5</div>\n\t<div>6</div>\n\t<div>7</div>\n\t<div>8</div>\n\t<div>9</div>\n\t<div>10</div>\n\t<div>1</div>\n\t<div>2</div>\n\t<div>3</div>\n\t<div>4</div>\n\t<div>5</div>\n\t<div>6</div>\n\t<div>7</div>\n\t<div>8</div>\n\t<div>9</div>\n\t<div>10</div>\n\t<div>1</div>\n\t<div>2</div>\n\t<div>3</div>\n\t<div>4</div>\n\t<div>5</div>\n\t<div>6</div>\n\t<div>7</div>\n\t<div>8</div>\n\t<div>9</div>\n\t<div>10</div>\n\t<div>1</div>\n\t<div>2</div>\n\t<div>3</div>\n\t<div>4</div>\n\t<div>5</div>\n\t<div>6</div>\n\t<div>7</div>\n\t<div>8</div>\n\t<div>9</div>\n\t<div>10</div>\n</ui-component>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/scrollpercentage","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/scrollpercentage/","markdown_url":"https://forge.totaljs.es/en/components/scrollpercentage.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"e8fb35178eb0bc3715dc4e72a29aef76889cff6a105ccb3340298007fad72b7a","checked_at":"2026-10-20T12:23:31.976Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-ScrollPercentage","description":"Component must be attached on the element which will be as a scroll container. For the entire window use body element.","messages":[{"role":"user","content":"j-ScrollPercentage\n\nComponent must be attached on the element which will be as a scroll container. For the entire window use body element."},{"role":"assistant","content":"Component must be attached on the element which will be as a scroll container. For the entire window use `body` element.\n\n\n__Configuration__:\n\n- `exec {String}` A link to the global function which is executed if the percentage is changed\n\n__IMPORTANT__: if the `exec` is not defined then the component will bind a value according to the `path`.\n\nUse it with `<ui-component name=\"scrollpercentage\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-bind path=\"common.scroll\" config=\"exec div:myProgress\" style=\"border: 1px solid #D0D0D0;padding:2px\" class=\"block\"><div style=\"height:10px;background-color:#79B34C\"></div></ui-bind>\n<br />\n<ui-component name=\"scrollpercentage\" path=\"common.scroll\" style=\"height:400px;overflow-y:scroll\">\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n</ui-component>\n\n<script>\n\tfunction myProgress(value, path, el) {\n\t\tel.css('width', value + '%');\n\t}\n</script>\n```"}],"knowledge":{"content_sha256":"233bb5983e7c8d411dacf4c3e8b4ba134c3f7aabe9dd8da78b9039b07d101596","source_sha256":"e8fb35178eb0bc3715dc4e72a29aef76889cff6a105ccb3340298007fad72b7a","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","D0D0D0","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-bind path=\"common.scroll\" config=\"exec div:myProgress\" style=\"border: 1px solid #D0D0D0;padding:2px\" class=\"block\"><div style=\"height:10px;background-color:#79B34C\"></div></ui-bind>\n<br />\n<ui-component name=\"scrollpercentage\" path=\"common.scroll\" style=\"height:400px;overflow-y:scroll\">\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n\t<div>Text</div>\n</ui-component>\n\n<script>\n\tfunction myProgress(value, path, el) {\n\t\tel.css('width', value + '%');\n\t}\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/search","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/search/","markdown_url":"https://forge.totaljs.es/en/components/search.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"16ff10b49dc8c98fed6f71a117761e06da4588a254e28bb1e42df8bc63a411fc","checked_at":"2026-10-20T12:23:32.855Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Search","description":"Simple fulltext search component. This component can show/hide elements according to the searching phrase.","messages":[{"role":"user","content":"j-Search\n\nSimple fulltext search component. This component can show/hide elements according to the searching phrase."},{"role":"assistant","content":"Simple fulltext search component. This component can show/hide elements according to the searching phrase.\n\n\n__Configuration__:\n\n- `selector {String}` jQuery selector for searching\n- `attribute {String}` element attribute which contains a search phrase (default: `data-search`)\n- `class {String}` this class will be used if the element is not found (default: `hidden`)\n- `delay {Number}` typing delay (default: `50` ms)\n- `delaydatasource {Number}` a delay for re-search when the data-source is changed (default: `100` ms)\n- __UPD__ `exec {String}` a link to `function(stats:{ total: Number, count: Number, hidden: Number, search: String, is: Boolean })` which will be evaluated if the component searches for something\n- `datasource {String}` a path to data-source for watching of changes\n- `splitwords {Boolean}` tries to find word in various position (default: `true`)\n- __NEW__ `custom {String}` a path to the `Function(element, search) must return Boolean` for custom searching\n\n__Good to know__:\n\nThis component adds the classes below automatically when:\n\n- class `ui-search-used` is added when the user searches for something\n- class `ui-search-empty` is added when the user searches for something and at the same time nothing was found\n\nUse it with `<ui-component name=\"search\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"textbox\" path=\"search\" config=\"type:search\"></ui-component>\n<br />\n<ui-component name=\"search\" path=\"search\" config=\"selector:div\">\n\t<div data-search=\"Apples\">Apples</div>\n\t<div data-search=\"Oranges\">Oranges</div>\n\t<div data-search=\"Pears\">Pears</div>\n\t<div data-search=\"Black-eye bean\">Black-eye bean</div>\n\t<div data-search=\"Broccoli\">Broccoli</div>\n</ui-component>\n\n<script>\n\tvar search = '';\n</script>\n```"}],"knowledge":{"content_sha256":"5a1c428d462a347a04d0a7b31478bd5690a86558a3b86824856d5b4f3eb05259","source_sha256":"16ff10b49dc8c98fed6f71a117761e06da4588a254e28bb1e42df8bc63a411fc","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"textbox\" path=\"search\" config=\"type:search\"></ui-component>\n<br />\n<ui-component name=\"search\" path=\"search\" config=\"selector:div\">\n\t<div data-search=\"Apples\">Apples</div>\n\t<div data-search=\"Oranges\">Oranges</div>\n\t<div data-search=\"Pears\">Pears</div>\n\t<div data-search=\"Black-eye bean\">Black-eye bean</div>\n\t<div data-search=\"Broccoli\">Broccoli</div>\n</ui-component>\n\n<script>\n\tvar search = '';\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/searchbox","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/searchbox/","markdown_url":"https://forge.totaljs.es/en/components/searchbox.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"b21cdce733c8f9b0f33ec2c9ee089a8da4e982bb8d956f529fa970ccb0aee24a","checked_at":"2026-10-20T12:23:33.728Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-SearchBox","description":"The component contains a search box (input).","messages":[{"role":"user","content":"j-SearchBox\n\nThe component contains a search box (input)."},{"role":"assistant","content":"The component contains a search box (input).\n\n\n__Configuration__:\n\n- `exec {String}` a link to the `function(model, component, changetype)`, it will be executed if the user presses enter\n- `autocomplete {String}` a link to the `function(component, input)`, it will be executed if the user focuses the search box\n- `init {String}` a link to the `function(component)`, it will be executed if the component is initialized\n- `placeholder {String}` a placeholder for the search box\n- `cleartype {Boolean}` clears type when the user will perform `ESC` (default: `false`)\n- `keypress {Boolean}` key-press binding (default: `false`)\n- `autotrim {Boolean}` trims the value automatically (default: `true`)\n- `clicktype {String}` a link to the `function(component, currenttype, currentvalue)`, it will be executed if the user clicks on the `type` (a new `type` must be set via `component.type(html, value)`)\n\n__Methods__:\n\n- `component.type(html, id, [noemit])` adds a new type before the search input\n- `component.enter()` performs searching manually\n- __NEW__: `component.show()` focuses the input\n\nUse it with `<ui-component name=\"searchbox\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"menu\"></ui-component>\n<ui-component name=\"searchbox\" config=\"exec:searchbox_enter;clicktype:searchbox_type;init:searchbox_init;placeholder:Search and press enter ...\"></ui-component>\n\n<script>\n\tfunction searchbox_enter(model, component) {\n\n\t\t// model.type\n\t\t// model.value\n\n\t\talert(model.type + ': ' + model.value);\n    }\n\n    function searchbox_init(component) {\n    \tcomponent.type('<i class=\"ti ti-user\"></i> By name', 'name', true);\n    }\n\n    function searchbox_type(component) {\n    \tvar opt = {};\n\t\topt.items = [{ name: 'By name', value: 'name', icon: 'user' }, { name: 'By subject', value: 'subject', icon: 'text-width' }, { name: 'By body', value: 'body', icon: 'file-alt' }];\n\t\topt.align = 'left';\n\t\topt.element = component.element;\n\t\topt.callback = function(selected) {\n\t\t\tcomponent.type('<i class=\"ti ti-{0}\"></i> {1}'.format(selected.icon, selected.name), selected.value, true);\n\t\t};\n\t\tSETTER('menu', 'show', opt);\n    }\n\n</script>\n```"}],"knowledge":{"content_sha256":"eb2f396bf24610791cca23beeb09618d8211c9d489face7c694c997b173b1493","source_sha256":"b21cdce733c8f9b0f33ec2c9ee089a8da4e982bb8d956f529fa970ccb0aee24a","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","ESC","MIT","SETTER"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"menu\"></ui-component>\n<ui-component name=\"searchbox\" config=\"exec:searchbox_enter;clicktype:searchbox_type;init:searchbox_init;placeholder:Search and press enter ...\"></ui-component>\n\n<script>\n\tfunction searchbox_enter(model, component) {\n\n\t\t// model.type\n\t\t// model.value\n\n\t\talert(model.type + ': ' + model.value);\n    }\n\n    function searchbox_init(component) {\n    \tcomponent.type('<i class=\"ti ti-user\"></i> By name', 'name', true);\n    }\n\n    function searchbox_type(component) {\n    \tvar opt = {};\n\t\topt.items = [{ name: 'By name', value: 'name', icon: 'user' }, { name: 'By subject', value: 'subject', icon: 'text-width' }, { name: 'By body', value: 'body', icon: 'file-alt' }];\n\t\topt.align = 'left';\n\t\topt.element = component.element;\n\t\topt.callback = function(selected) {\n\t\t\tcomponent.type('<i class=\"ti ti-{0}\"></i> {1}'.format(selected.icon, selected.name), selected.value, true);\n\t\t};\n\t\tSETTER('menu', 'show', opt);\n    }\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/searchdata","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/searchdata/","markdown_url":"https://forge.totaljs.es/en/components/searchdata.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"eefa25601ed9d489bb4c5137dc03069ea68f969186faea69115387fccc386393","checked_at":"2026-10-20T12:23:34.604Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-SearchData","description":"This component tries to filter data according to the search phrase.","messages":[{"role":"user","content":"j-SearchData\n\nThis component tries to filter data according to the search phrase."},{"role":"assistant","content":"This component tries to filter data according to the search phrase.\n\n\n__Configuration__:\n\n- `datasource {String}` __required__ - a path to the data-source with list of items `Array`\n- `output {String}` __required__ - a path for filtered items\n- `key {String}` __required__ - a key/property name for searching (default: `name`)\n- `delay {Number}` a delay (default: `50` ms)\n- `splitwords {Boolean}` tries to find word in various position (default: `true`)\n\n__Good to know__:\n\nThis component adds classes below automatically when:\n\n- class `ui-search-used` is added when the user searches for something\n- class `ui-search-empty` is added when the user searches for something and at the same time nothing was found\n\nUse it with `<ui-component name=\"searchdata\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"textbox\" path=\"search\" config=\"type:search\"></ui-component>\n<br />\n<ui-component name=\"searchdata\" path=\"search\" config=\"datasource:searchitems;output:searchoutput\">\n\t<ui-bind path=\"searchoutput\" config=\"template\" class=\"block\">\n\t\t<script type=\"text/html\">\n\t\t\t{{ foreach m in value }}\n\t\t\t\t<div>{{ m.name }}</div>\n\t\t\t{{ end }}\n\t\t</script>\n\t</ui-bind>\n</ui-component>\n\n<script>\n\n\t// Search phrase\n\tvar search = '';\n\n\t// Datasource for filtering\n\tvar searchitems = [{ name: 'Apples' }, { name: 'Oranges' }, { name: 'Pears' }, { name: 'Black-eye bean' }, { name: 'Broccoli' }];\n\n\t// Here will be stored all filtered items\n\tvar searchoutput = [];\n\n</script>\n```"}],"knowledge":{"content_sha256":"5a504ccb320d0b1ecfb497220e63518807b9f39b28283a4c3662cbb7a0e0e8e3","source_sha256":"eefa25601ed9d489bb4c5137dc03069ea68f969186faea69115387fccc386393","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"textbox\" path=\"search\" config=\"type:search\"></ui-component>\n<br />\n<ui-component name=\"searchdata\" path=\"search\" config=\"datasource:searchitems;output:searchoutput\">\n\t<ui-bind path=\"searchoutput\" config=\"template\" class=\"block\">\n\t\t<script type=\"text/html\">\n\t\t\t{{ foreach m in value }}\n\t\t\t\t<div>{{ m.name }}</div>\n\t\t\t{{ end }}\n\t\t</script>\n\t</ui-bind>\n</ui-component>\n\n<script>\n\n\t// Search phrase\n\tvar search = '';\n\n\t// Datasource for filtering\n\tvar searchitems = [{ name: 'Apples' }, { name: 'Oranges' }, { name: 'Pears' }, { name: 'Black-eye bean' }, { name: 'Broccoli' }];\n\n\t// Here will be stored all filtered items\n\tvar searchoutput = [];\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/searchinput","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/searchinput/","markdown_url":"https://forge.totaljs.es/en/components/searchinput.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"c1710d7f79140a4357ed5957c5aa6fb3494966d6ce6a15ae66a2e0338623a850","checked_at":"2026-10-20T12:23:35.343Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-SearchInput","description":"Customizable search input.","messages":[{"role":"user","content":"j-SearchInput\n\nCustomizable search input."},{"role":"assistant","content":"Customizable search input.\n\n\n__Configuration__:\n\n- `searchicon {String}` search icon (default: `ti ti-search`)\n- `cancelicon {String}` cancel icon (default: `ti ti-times`)\n- `align {String}` icon align (default: `left`)\n- `placeholder {String}` a plceholder\n- `autofocus {Boolean/Number}` enables auto-focus for non-mobile devices (number means a delay for auto-focus, default: `false`)\n- `exec {String}` a link to the `function(value, element)` which will be evaluated if the input is affected\n- __NEW__: `realtime {Boolean}` enables real-time binding (default: `true`)\n\n__Methods__:\n\n- `component.focus()` focuses input\n- `component.clear()` clears input\n\nUse it with `<ui-component name=\"searchinput\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"searchinput\" path=\"%search\" class=\"m\"></ui-component>\n<ui-bind path=\"%search\" config=\"text b\" class=\"block\">Search: <b></b></ui-bind>\n```"}],"knowledge":{"content_sha256":"bea6824b457621cadb59ad1871955b168aa7d17eaab4e4d62be34a38e9bfc0e3","source_sha256":"c1710d7f79140a4357ed5957c5aa6fb3494966d6ce6a15ae66a2e0338623a850","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"searchinput\" path=\"%search\" class=\"m\"></ui-component>\n<ui-bind path=\"%search\" config=\"text b\" class=\"block\">Search: <b></b></ui-bind>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/section","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/section/","markdown_url":"https://forge.totaljs.es/en/components/section.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"85780b823e7bc4b9a494ab7e978eb9dce06e9ba1c2201fb37f2c103ae772f346","checked_at":"2026-10-20T12:23:36.215Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Section","description":"j-Section: keeps a fixed percentage height; great usage for intranet applications","messages":[{"role":"user","content":"j-Section\n\nj-Section: keeps a fixed percentage height; great usage for intranet applications"},{"role":"assistant","content":"- keeps a fixed percentage height\n- great usage for intranet applications\n- supports `releasing`\n\n__Configuration__:\n\n- `height {Number}` a percentage of height\n- `parent {String}` optional, a container with fixed height, can be `window`. Default value: `parent` element.\n- `minheight {Number}` a minimal height in pixels (default: `0` - disabled)\n- `scroll {Boolean}` enables vertical scrolling (default: `false` - disabled)\n- `scrollbar {Boolean}` shows scrollbar (default: `false`)\n- `delay {Number}` `msec.` resizes the box again if the value is changed according to the path (default: `100`)\n- `visibleY {Boolean}` still shows `Y` scrollbar (default: `false`)\n- `visibleX {Boolean}` still shows `X` scrollbar (default: `false`)\n- `scrolltop {Boolean}` scrolls to top automatically if the `path` is changed (default: `false`)\n- `margin {Number}` optional, a top/bottom margin together (supports `auto` which is counted from `offset.top`, default: `0`)\n- `marginxs {Number}` optional, a top/bottom margin together for `xs` screen width\n- `marginsm` optional, a top/bottom margin together for `sm` screen width\n- `marginmd` optional, a top/bottom margin together for `md` screen width\n- `marginlg` optional, a top/bottom margin together for `lg` screen width\n- `invisible {Boolean}` if `true` then section sets `invisible` class when is rendering (default: `true`)\n- `autofocus {Boolean/String}` can focus an input. `String` === `jQuery selector` for the input\n- `back {String}` a title for back button (default: `Back`)\n- `delayanim {Number}` animation delay (default: `100`)\n- `scrollbarshadow {Boolean}` adds shadow for scrollbars (default: `false`)\n- `backexec {String}` a link to the `function(parent)` that captures back button (it prevents standard behaviour)\n\n__Methods__:\n\n- `component.scrolltop(val)` scrolls Y\n- `component.scrollbottom(val)` scrolls Y from bottom side\n- `component.resize()` resizes container (it reacts on `resize` events automatically)\n- `component.resizescrollbar()` resizes custom scrollbar (targeted for special cases)\n- __NEW__: `component.import(url)` imports a new section from the URL address, response must be in the form `<section data-if=\"....\"`\n- __NEW__: `component.import(html)` imports a new section from the HTML in the form `<section data-if=\"....\"`\n- __NEW__: `component.import(element)` imports a new section from the raw `SECTION` element\n- __NEW__: `component.cancel([id])` removes section dynamically\n\n__Definition__:\n\n```html\n<ui-component name=\"section\" path=\"path\">\n\n\t<!-- path must have same value as \"data-if\" attribute -->\n\t<section data-if=\"1\" data-title=\"A title\">\n\n\t</section>\n\n\t<!-- path must have same value as \"data-if\" attribute -->\n\t<!-- \"data-parent\" enables Back button in the header and after click the component sets value from \"data-parent\" attribute -->\n\t<section data-if=\"2\" data-parent=\"1\" data-title=\"A title\">\n\n\t</section>\n\n\t<!-- GOOD TO KNOW: Dynamic compilation -->\n\t<section data-if=\"3\" data-parent=\"2\" data-title=\"A title\" data-back=\"link_to_function\">\n\n\t\t<!-- SCRIPT or TEMPLATE -->\n\t\t<script type=\"text/html\">\n\t\t\tWill be compiled and rendered when the section will be displayed\n\t\t</script>\n\n\t</section>\n\n\t<!-- Template from URL address -->\n\t<section data-if=\"4\" data-parent=\"2\" data-title=\"A title\" data-url=\"LINK_TO_HTML_TEMPLATE\"></section>\n\n</ui-component>\n```\n\n__Section attributes__:\n\n- `data-if` must contain a condition for displaying\n- `data-parent` can contain a parent `data-if`\n- `data-title` contains a label/title for the section\n- `data-reload` can contain a link to the `function(el)` (optional)\n- `data-id` contains a custom identifier for replacing of all `~ID~` phrases in the content (optional)\n- `data-url` can contain a link to the template (optional)\n\n__Good to know__:\n\n- If you want to add an additional content into the header to right side, just append inside of component e.g. `<div>BUTTON</div>`.\n- You can dynamically insert/remove sections via `component.import()` or `component.cancel()` methods.\n- All section's attributes can be changed dynamically (e.g. `data-title`, `data-parent`, etc.).\n- all `~PATH~` phrases will be replaced for the `data-if` attribute value\n- all `~ID~` phrases will be replaced for the `data-if` attribute value\n- `SET('path', 'if', 'right')` will animated the content to the right side\n\nUse it with `<ui-component name=\"section\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\tbody { padding: 0; margin: 0; overflow: hidden; }\n</style>\n\n<ui-component name=\"section\" path=\"common.section\" config=\"parent:window;scrollbar:1\" default=\"''\">\n\n\t<section class=\"hidden invisible\" data-if=\"1\" data-title=\"Section 1\">\n\t\t<div class=\"padding\">\n\t\t\t<div style=\"background-color:#F0F0F0;margin:30px auto;padding:30px;max-width:600px;border-radius:10px\">\n\t\t\t\t<div>SECTION 1</div>\n\t\t\t\t<div class=\"exec link\" data-exec=\"showsection2\">Show section 2</div>\n\t\t\t</div>\n\t\t</div>\n\t</section>\n\n\t<section class=\"hidden invisible\" data-if=\"2\" data-parent=\"1\" data-title=\"Section 2\">\n\t\t<script type=\"text/html\">\n\t\t\t<div class=\"padding\">\n\t\t\t\t<div style=\"background-color:#F0F0F0;margin:30px auto;padding:30px;max-width:600px;border-radius:10px\">\n\t\t\t\t\tSECTION 2\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</script>\n\t</section>\n\n\t<section class=\"hidden invisible\" data-if=\"2\" data-parent=\"1\" data-title=\"Section 2\" data-url=\"LINK_TO_TEMPLATE\"></section>\n</ui-component>\n\n<script>\n\n\tfunction showsection2() {\n\t\tSET('common.section', '2');\n\t}\n\n\tsetTimeout(showsection2, 1000);\n\n</script>\n```"}],"knowledge":{"content_sha256":"70c4f9777c6e504d5de1eefb990bff2327c80629e0adeb29a3588e833ba2d3af","source_sha256":"85780b823e7bc4b9a494ab7e978eb9dce06e9ba1c2201fb37f2c103ae772f346","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["BUTTON","CDN","DEF","F0F0F0","GOOD","HTML","KNOW","LINK_TO_HTML_TEMPLATE","LINK_TO_TEMPLATE","MIT","PATH","SCRIPT","SECTION","SET","TEMPLATE","URL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\tbody { padding: 0; margin: 0; overflow: hidden; }\n</style>\n\n<ui-component name=\"section\" path=\"common.section\" config=\"parent:window;scrollbar:1\" default=\"''\">\n\n\t<section class=\"hidden invisible\" data-if=\"1\" data-title=\"Section 1\">\n\t\t<div class=\"padding\">\n\t\t\t<div style=\"background-color:#F0F0F0;margin:30px auto;padding:30px;max-width:600px;border-radius:10px\">\n\t\t\t\t<div>SECTION 1</div>\n\t\t\t\t<div class=\"exec link\" data-exec=\"showsection2\">Show section 2</div>\n\t\t\t</div>\n\t\t</div>\n\t</section>\n\n\t<section class=\"hidden invisible\" data-if=\"2\" data-parent=\"1\" data-title=\"Section 2\">\n\t\t<script type=\"text/html\">\n\t\t\t<div class=\"padding\">\n\t\t\t\t<div style=\"background-color:#F0F0F0;margin:30px auto;padding:30px;max-width:600px;border-radius:10px\">\n\t\t\t\t\tSECTION 2\n\t\t\t\t</div>\n\t\t\t</div>\n\t\t</script>\n\t</section>\n\n\t<section class=\"hidden invisible\" data-if=\"2\" data-parent=\"1\" data-title=\"Section 2\" data-url=\"LINK_TO_TEMPLATE\"></section>\n</ui-component>\n\n<script>\n\n\tfunction showsection2() {\n\t\tSET('common.section', '2');\n\t}\n\n\tsetTimeout(showsection2, 1000);\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/selectbox","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/selectbox/","markdown_url":"https://forge.totaljs.es/en/components/selectbox.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"16579c0c13690f168af7e73342441bb6c245be6061c7bd704595df5030b70825","checked_at":"2026-10-20T12:23:37.087Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-SelectBox","description":"j-SelectBox: NEW raw {Boolean} (optional) allow raw HTML to be rendered as text (default: false)","messages":[{"role":"user","content":"j-SelectBox\n\nj-SelectBox: NEW raw {Boolean} (optional) allow raw HTML to be rendered as text (default: false)"},{"role":"assistant","content":"__Configuration__:\n\n- `required {Boolean}` (optional) enables `required` (default: __false__)\n- `items {String}` (optional) static items `key1|value1, key2 with value, key3|value`\n- `type {String}` (optional) can be `number` (converts `String` values to `Number`)\n- `datasource {String}` path to data-source (must be array)\n- `text {String}` determines a property name for text (in data-source), default: `name`\n- `value {String}` determines a property name for value (in data-source) default: `id`\n- `empty {String}` (optional) adds an empty field\n- `disabled {Boolean}` (optional) disables this component\n- `search {String}` (optional) enables search box with this value as a placeholder\n- `height {Number}` (optional) height (default: `200`)\n- `if {String}` (optional) arrow function as a condition for filtering of datasource, example: `if:n => !n.removed`\n- __NEW__ `raw {Boolean}` (optional) allow raw HTML to be rendered as text (default: `false`)\n\nUse it with `<ui-component name=\"selectbox\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"width:400px\">\n\t<ui-component name=\"selectbox\" path=\"items\" config=\"search:Search;required:true;items:Blue|1,Red|2,Green|3,Purple|4,Orange|5,Rose|6,Gray|7,Black|0;type:number\"></ui-component>\n</div>\n\n<script>\n\tvar items = [2, 5];\n</script>\n```"}],"knowledge":{"content_sha256":"c5cc8c78ae17c3080d5a7b20e5b769434f505af634f0041fe789c3477920ee97","source_sha256":"16579c0c13690f168af7e73342441bb6c245be6061c7bd704595df5030b70825","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","HTML","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"width:400px\">\n\t<ui-component name=\"selectbox\" path=\"items\" config=\"search:Search;required:true;items:Blue|1,Red|2,Green|3,Purple|4,Orange|5,Rose|6,Gray|7,Black|0;type:number\"></ui-component>\n</div>\n\n<script>\n\tvar items = [2, 5];\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/selected","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/selected/","markdown_url":"https://forge.totaljs.es/en/components/selected.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"e5ce1e3727c7ba28a62bb1fc3221c558746436a10925c0f74761ef1544172954","checked_at":"2026-10-20T12:23:37.972Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Selected","description":"The component can set a specific class to a specific element according to the simple condition.","messages":[{"role":"user","content":"j-Selected\n\nThe component can set a specific class to a specific element according to the simple condition."},{"role":"assistant","content":"The component can set a specific class to a specific element according to the simple condition.\n\n\n__Configuration__:\n\n- `class {String}` is a class name for selected element (default: `selected`)\n- `selector {String}` is a jQuery selector (default: `a`)\n- `attr {String}` attribute for comparing (default: `if` and it means `data-if`)\n- `attror {String}` attribute for secondary comparing (default: `or` and it means `data-or`)\n- `datasource {String}` a path to data-source for list of items\n- `delay {Number}` a delay for rel-selecting of value (default: `50`)\n\n__Usage__:\n\nEach nested element according to the `selector` must have defined `data-if` attribute. It works only with `strings` values.\n\nUse it with `<ui-component name=\"selected\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\t.selected { font-weight: bold; }\n\tnav a { display: block; }\n</style>\n\n<nav>\n\t<ui-component name=\"selected\" path=\"common.page\">\n\t\t<a href=\"#users\" data-if=\"users\">Users</a>\n\t\t<a href=\"#products\" data-if=\"products\">Products</a>\n\t\t<a href=\"#settings\" data-if=\"settings\">Settings</a>\n\t</ui-component>\n</nav>\n\n<script>\n\n\tSET('common.page', 'products');\n\n\twindow.onhashchange = function() {\n\t\tSET('common.page', location.hash.substring(1));\n\t};\n\n</script>\n```"}],"knowledge":{"content_sha256":"737d8475f3a8d262c76dfd4c597e8466405344cbe857c1e4a9cb0a0b1d822dd5","source_sha256":"e5ce1e3727c7ba28a62bb1fc3221c558746436a10925c0f74761ef1544172954","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","SET"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\t.selected { font-weight: bold; }\n\tnav a { display: block; }\n</style>\n\n<nav>\n\t<ui-component name=\"selected\" path=\"common.page\">\n\t\t<a href=\"#users\" data-if=\"users\">Users</a>\n\t\t<a href=\"#products\" data-if=\"products\">Products</a>\n\t\t<a href=\"#settings\" data-if=\"settings\">Settings</a>\n\t</ui-component>\n</nav>\n\n<script>\n\n\tSET('common.page', 'products');\n\n\twindow.onhashchange = function() {\n\t\tSET('common.page', location.hash.substring(1));\n\t};\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/selection","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/selection/","markdown_url":"https://forge.totaljs.es/en/components/selection.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"86d70a720d5ce8937dbf912d9464eb6863b5a610dbe55c004ee5a7730b45c695","checked_at":"2026-10-20T12:23:38.699Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Selection","description":"The component supports something like checkbox list.","messages":[{"role":"user","content":"j-Selection\n\nThe component supports something like checkbox list."},{"role":"assistant","content":"The component supports something like checkbox list.\n\n\n__Configuration__:\n\n- `remember {Boolean}` remembers selection when the `datasource` is changed (default: `true`)\n- `datasource {String}` a path to the datasource (default: empty)\n- `key {String}` an `identifier` key name for the datasource (default: `id`)\n- `attr {String}` a `data-[attr]` for storing of `identifier` (default: `id`)\n- `selector {String}` a jQuery selector for elements (default: `.selection`)\n- `class {String}` selected class (default: `selected`)\n- `dblclickselectall {Boolean}` enables reverse selection after double-click (default: `true`)\n- `cancel {String}` a path to the variable (if the variable is changed then the selection is resetted, default: empty)\n- __NEW__: `click {String}` a selector for capturing of click (default: `.selection`)\n\n__Methods__:\n\n- `component.selectall()` selects all items\n- `component.selectnone()` selects none\n- `component.selecttoggle()` reverses selection\n\nUse it with `<ui-component name=\"selection\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\t.items > div { height: 28px; line-height: 28px; border-bottom: 1px solid #E0E0E0; }\n\t.selection { width: 18px; height: 18px; font-size: 11px; border-radius: 3px; border: 1px solid #E0E0E0; text-align: center; float: left; cursor: pointer; margin: 4px 5px 0 0; }\n\t.selection i { display: none; line-height: 16px; }\n\t.selection.selected i { display: block; }\n</style>\n\n<ui-component name=\"selection\" path=\"myselection\" class=\"items m\">\n\t<div><span class=\"selection\" data-id=\"1\"><i class=\"ti ti-check\"></i></span>Item 1</div>\n\t<div><span class=\"selection\" data-id=\"2\"><i class=\"ti ti-check\"></i></span>Item 2</div>\n\t<div><span class=\"selection\" data-id=\"3\"><i class=\"ti ti-check\"></i></span>Item 3</div>\n\t<div><span class=\"selection\" data-id=\"4\"><i class=\"ti ti-check\"></i></span>Item 4</div>\n\t<div><span class=\"selection\" data-id=\"5\"><i class=\"ti ti-check\"></i></span>Item 5</div>\n\t<div><span class=\"selection\" data-id=\"6\"><i class=\"ti ti-check\"></i></span>Item 6</div>\n\t<div><span class=\"selection\" data-id=\"7\"><i class=\"ti ti-check\"></i></span>Item 7</div>\n\t<div><span class=\"selection\" data-id=\"8\"><i class=\"ti ti-check\"></i></span>Item 8</div>\n\t<div><span class=\"selection\" data-id=\"9\"><i class=\"ti ti-check\"></i></span>Item 9</div>\n</ui-component>\n\n<div>Output: <code><ui-bind path=\"myselection\" config=\"text:JSON.stringify(value)\"></ui-bind></code></div>\n```"}],"knowledge":{"content_sha256":"c19fcf1b03bb53cf998d443bad5178001ce2af70b1e1ec6963e204ea7e77a083","source_sha256":"86d70a720d5ce8937dbf912d9464eb6863b5a610dbe55c004ee5a7730b45c695","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","E0E0E0","JSON","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\t.items > div { height: 28px; line-height: 28px; border-bottom: 1px solid #E0E0E0; }\n\t.selection { width: 18px; height: 18px; font-size: 11px; border-radius: 3px; border: 1px solid #E0E0E0; text-align: center; float: left; cursor: pointer; margin: 4px 5px 0 0; }\n\t.selection i { display: none; line-height: 16px; }\n\t.selection.selected i { display: block; }\n</style>\n\n<ui-component name=\"selection\" path=\"myselection\" class=\"items m\">\n\t<div><span class=\"selection\" data-id=\"1\"><i class=\"ti ti-check\"></i></span>Item 1</div>\n\t<div><span class=\"selection\" data-id=\"2\"><i class=\"ti ti-check\"></i></span>Item 2</div>\n\t<div><span class=\"selection\" data-id=\"3\"><i class=\"ti ti-check\"></i></span>Item 3</div>\n\t<div><span class=\"selection\" data-id=\"4\"><i class=\"ti ti-check\"></i></span>Item 4</div>\n\t<div><span class=\"selection\" data-id=\"5\"><i class=\"ti ti-check\"></i></span>Item 5</div>\n\t<div><span class=\"selection\" data-id=\"6\"><i class=\"ti ti-check\"></i></span>Item 6</div>\n\t<div><span class=\"selection\" data-id=\"7\"><i class=\"ti ti-check\"></i></span>Item 7</div>\n\t<div><span class=\"selection\" data-id=\"8\"><i class=\"ti ti-check\"></i></span>Item 8</div>\n\t<div><span class=\"selection\" data-id=\"9\"><i class=\"ti ti-check\"></i></span>Item 9</div>\n</ui-component>\n\n<div>Output: <code><ui-bind path=\"myselection\" config=\"text:JSON.stringify(value)\"></ui-bind></code></div>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/servergrid","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/servergrid/","markdown_url":"https://forge.totaljs.es/en/components/servergrid.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"bd4c53981c4284e4a805dab49d01f9cd88a9371bd0177c9ae280b48de899e68a","checked_at":"2026-10-20T12:23:39.583Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-ServerGrid","description":"BETA VERSION. This grid is designed for single page and enterprise applications. It's a simple alternative to the much more powerful j-DataGrid.","messages":[{"role":"user","content":"j-ServerGrid\n\nBETA VERSION. This grid is designed for single page and enterprise applications. It's a simple alternative to the much more powerful j-DataGrid."},{"role":"assistant","content":"__BETA VERSION__. This grid is designed for single page and enterprise applications. It's a simple alternative to the much more powerful `j-DataGrid`.\n\n__Data-source__ needs to contain:\n\n```js\n{\n\tcolumns: [\n\t\t{\n\t\t\tid: 'name',\n\t\t\tname: 'Name',\n\t\t\twidth: 150, // optional (default: config.colwidth)\n\t\t\tfiltering: false, // optional (default: false)\n\t\t\tsorting: false, // optional (default: false)\n\t\t\talign: 1, // optional: 0 or left (default), 1 or center, 2 or right\n\t\t\talignheader: 1, // optional: 0 or left (default), 1 or center, 2 or right\n\t\t\talignfilter: 1, // optional: 0 or left (default), 1 or center, 2 or right\n\t\t\ticon: 'ti ti-home', // optional: column icon\n\t\t\tcolor: '#000000' // optional: color for all rows,\n\t\t\tdirsource: [{ id: 'String|Number|Boolean', name: '...' }] // optional: a dropdown filter (it needs \"directory\" component)\n\t\t}\n\t],\n    items: [{ name: 'Row <b>1</b> will be bold', $class: 'NEW: css class for the row', $style: 'NEW: css for the row' }, { name: 'Row 2' }, ...] // items\n    page: 1,    // current pages\n    pages: 30,  // count of pages\n    limit: 5,   // items limit per page\n    count: 150  // count of items in DB\n}\n```\n\n__Configuration__:\n\n- `colwidth {Number}` a default column width in pixels (default: `150`)\n- `pluralizepages {String}` pluralization for pages (optional, default: `# pages,# page,# pages,# pages`)\n- `pluralizeitems {String}` pluralization for items (optional, default: `# items,# item,# items,# items`)\n- `margin {Number}` a top margin for height (optional, default: `0`)\n- `noborder {Boolean}` can remove a border (default: `false`)\n- `parent {String}` selector for `auto` height option (default: `window`)\n- `height {Number}` a fixed height\n- `filter {String}` a default placeholder for all filters (optional)\n- `click {String}` a link to a path or `function(row, row_element)`\n- `cell {String}` a link to a path or `function(row, cell_element, column)`\n- `exec {String}` a link to `function(type, filter, sort, page)` for server-side operations (supported types: `filter`, `page` and `sort`)\n- `opacity {Number}` a default background opacity for column colors (default: `0.3`);\n- __NEW__: `scrollbarshadow {Boolean}` enables scrollbar shadow (default: `false`)\n- __NEW__: `scrolltop {Boolean}` it automatically scrolls to the top after the data source is changed (default: `true`)\n\n__Good to know__:\n\n- `NULL('link.to.data')` evaluates `config.exec`\n\nUse it with `<ui-component name=\"servergrid\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"servergrid\" path=\"data\" config=\"checkbox:1;height:500;exec:filtering\"></ui-component>\n\n<script id=\"datasource\" type=\"text/json\">\n{\n\t\"items\": [\n\t\t{\n\t\t\t\"id\": \"IuX45p1cl61f\",\n\t\t\t\"name\": \"Hello world!\",\n\t\t\t\"createdby\": \"Peter Sirka\",\n\t\t\t\"dtcreated\": \"2024-11-25T22:43:53.455Z\"\n\t\t}\n\t],\n\t\"count\": 1,\n\t\"page\": 2,\n\t\"limit\": 50,\n\t\"pages\": 3,\n\t\"columns\": [\n\t\t{\n\t\t\t\"id\": \"name\",\n\t\t\t\"name\": \"Message\",\n\t\t\t\"width\": 300,\n\t\t\t\"color\": \"#F4E8E8\",\n\t\t\t\"icon\": \"ti ti-envelope\",\n\t\t\t\"filtering\": true,\n\t\t\t\"sorting\": true\n\t\t},\n\t\t{\n\t\t\t\"id\": \"createdby\",\n\t\t\t\"name\": \"Created by\",\n\t\t\t\"filtering\": true,\n\t\t\t\"sorting\": true\n\t\t},\n\t\t{\n\t\t\t\"id\": \"dtcreated\",\n\t\t\t\"name\": \"Created\",\n\t\t\t\"icon\": \"ti ti-calendar\",\n\t\t\t\"filtering\": true,\n\t\t\t\"sorting\": true\n\t\t}\n\t]\n}\n</script>\n\n\n<script>\n\n\tvar data = PARSE('#datasource');\n\n\tfunction filtering(type, filter, sort, page) {\n\t\tconsole.log('Type:', type, 'Filter:', filter, 'Sort:', sort, 'Page:', page);\n\t\tUPD('data');\n\t}\n\n</script>\n```"}],"knowledge":{"content_sha256":"eb7c2a1d99b6c5051929883316cc3763370bb2e1596a6fe9e2c798665b00ce4c","source_sha256":"bd4c53981c4284e4a805dab49d01f9cd88a9371bd0177c9ae280b48de899e68a","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","F4E8E8","MIT","NEW","NULL","PARSE","UPD","VERSION__"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"servergrid\" path=\"data\" config=\"checkbox:1;height:500;exec:filtering\"></ui-component>\n\n<script id=\"datasource\" type=\"text/json\">\n{\n\t\"items\": [\n\t\t{\n\t\t\t\"id\": \"IuX45p1cl61f\",\n\t\t\t\"name\": \"Hello world!\",\n\t\t\t\"createdby\": \"Peter Sirka\",\n\t\t\t\"dtcreated\": \"2024-11-25T22:43:53.455Z\"\n\t\t}\n\t],\n\t\"count\": 1,\n\t\"page\": 2,\n\t\"limit\": 50,\n\t\"pages\": 3,\n\t\"columns\": [\n\t\t{\n\t\t\t\"id\": \"name\",\n\t\t\t\"name\": \"Message\",\n\t\t\t\"width\": 300,\n\t\t\t\"color\": \"#F4E8E8\",\n\t\t\t\"icon\": \"ti ti-envelope\",\n\t\t\t\"filtering\": true,\n\t\t\t\"sorting\": true\n\t\t},\n\t\t{\n\t\t\t\"id\": \"createdby\",\n\t\t\t\"name\": \"Created by\",\n\t\t\t\"filtering\": true,\n\t\t\t\"sorting\": true\n\t\t},\n\t\t{\n\t\t\t\"id\": \"dtcreated\",\n\t\t\t\"name\": \"Created\",\n\t\t\t\"icon\": \"ti ti-calendar\",\n\t\t\t\"filtering\": true,\n\t\t\t\"sorting\": true\n\t\t}\n\t]\n}\n</script>\n\n\n<script>\n\n\tvar data = PARSE('#datasource');\n\n\tfunction filtering(type, filter, sort, page) {\n\t\tconsole.log('Type:', type, 'Filter:', filter, 'Sort:', sort, 'Page:', page);\n\t\tUPD('data');\n\t}\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/serverlist","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/serverlist/","markdown_url":"https://forge.totaljs.es/en/components/serverlist.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"ffeeebefee99ded9d6a4a51a189d7365531723544adddbb0e37d960a9ef24621","checked_at":"2026-10-20T12:23:40.474Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-ServerList","description":"BETA VERSION. This componnet is designed for single page and enterprise applications.It has nearly the same functionality as the j-ServerGrid, but it gives developers more freedom to create custom ite","messages":[{"role":"user","content":"j-ServerList\n\nBETA VERSION. This componnet is designed for single page and enterprise applications.It has nearly the same functionality as the j-ServerGrid, but it gives developers more freedom to create custom ite"},{"role":"assistant","content":"__BETA VERSION__. This componnet is designed for single page and enterprise applications.It has nearly the same functionality as the `j-ServerGrid`, but it gives developers more freedom to create custom item rendering.\n\n__Data-source__ needs to contain:\n\n```js\n{\n\tcolumns: [\n\t\t{\n\t\t\tid: 'name',\n\t\t\tname: 'Name',\n\t\t\twidth: 150, // optional (default: config.colwidth)\n\t\t\tfiltering: false, // optional (default: false)\n\t\t\tsorting: false, // optional (default: false)\n\t\t\talign: 1, // optional: 0 or left (default), 1 or center, 2 or right\n\t\t\talignheader: 1, // optional: 0 or left (default), 1 or center, 2 or right\n\t\t\talignfilter: 1, // optional: 0 or left (default), 1 or center, 2 or right\n\t\t\ticon: 'ti ti-home', // optional: column icon\n\t\t\tdirsource: [{ id: 'String|Number|Boolean', name: '...' }] // optional: a dropdown filter (it needs \"directory\" component)\n\t\t}\n\t],\n    items: [{ name: 'Row <b>1</b> will be bold', html: 'RENDER' }, { name: 'Row 2', html: 'RENDER' }, ...] // items\n    page: 1,    // current pages\n    pages: 30,  // count of pages\n    limit: 5,   // items limit per page\n    count: 150  // count of items in DB\n}\n```\n\n__Configuration__:\n\n- `colwidth {Number}` a default column width in pixels (default: `150`)\n- `pluralizepages {String}` pluralization for pages (optional, default: `# pages,# page,# pages,# pages`)\n- `pluralizeitems {String}` pluralization for items (optional, default: `# items,# item,# items,# items`)\n- `margin {Number}` a top margin for height (optional, default: `0`)\n- `noborder {Boolean}` can remove a border (default: `false`)\n- `parent {String}` selector for `auto` height option (default: `window`)\n- `height {Number}` a fixed height\n- `filter {String}` a default placeholder for all filters (optional)\n- `click {String}` a link to a path or `function(row, row_element)`\n- `exec {String}` a link to `function(type, filter, sort, page)` for server-side operations (supported types: `filter`, `page` and `sort`)\n- `display {Boolean}` enables auto adding jComponent responsive class `d-xs`, `d-lg`, etc. according to the screen width (default: `true`)\n- `empty {String}` an empty message (default: `No records found.`)\n- __NEW__: `scrollbarshadow {Boolean}` enables scrollbar shadow (default: `false`)\n- __NEW__: `scrolltop {Boolean}` it automatically scrolls to the top after the data source is changed (default: `true`)\n\n__Good to know__:\n\n- `NULL('link.to.data')` evaluates `config.exec`\n\n## Custom templating\n\nThe ServerList component attempts to locate a Tangular template. If one exists, the component uses it. Otherwise, it tries to render a content from the `html` property of each item/row.\n\n```html\n<ui-component name=\"serverlist\" ...>\n\t<script type=\"text/html\">\n\t\t<!-- \"value\" represents each row -->\n\t\t<!-- \"$\" represents the entire datasource -->\n\t\t<div>{{ value.name }}</div>\n\t</script>\n</ui-component>\n```\n\nUse it with `<ui-component name=\"serverlist\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\t/* CUSTOM FORMATTING */\n\t.ui-serverlist-items { display: grid; column-gap: var(--gap); grid-template-columns: repeat(4, minmax(0,1fr)); padding: 20px; }\n\t.ui-serverlist-item { border: 2px solid #E0E0E0; padding: 10px; border-radius: 10px; }\n\t.ui-serverlist-check { position: absolute; margin: -20px 0 0 -20px; }\n</style>\n\n<ui-component name=\"serverlist\" path=\"data\" config=\"checkbox:1;height:500;exec:filtering\">\n\t<script type=\"text/html\">\n\t\t<div>{{ value.name }}, total pages: {{ $.pages }}</div>\n\t</script>\n</ui-component>\n\n<script id=\"datasource\" type=\"text/json\">\n{\n\t\"items\": [\n\t\t{\n\t\t\t\"id\": \"1\",\n\t\t\t\"name\": \"Hello world! 1\",\n\t\t\t\"createdby\": \"Peter Sirka\",\n\t\t\t\"dtcreated\": \"2024-11-25T22:43:53.455Z\"\n\t\t},\n\t\t{\n\t\t\t\"id\": \"2\",\n\t\t\t\"name\": \"Hello world! 2\",\n\t\t\t\"createdby\": \"Peter Sirka\",\n\t\t\t\"dtcreated\": \"2024-11-25T22:43:53.455Z\"\n\t\t},\n\t\t{\n\t\t\t\"id\": \"3\",\n\t\t\t\"name\": \"Hello world! 3\",\n\t\t\t\"createdby\": \"Peter Sirka\",\n\t\t\t\"dtcreated\": \"2024-11-25T22:43:53.455Z\"\n\t\t}\n\t],\n\t\"count\": 1,\n\t\"page\": 2,\n\t\"limit\": 50,\n\t\"pages\": 3,\n\t\"columns\": [\n\t\t{\n\t\t\t\"id\": \"name\",\n\t\t\t\"name\": \"Message\",\n\t\t\t\"icon\": \"ti ti-envelope\",\n\t\t\t\"filtering\": true,\n\t\t\t\"sorting\": true\n\t\t},\n\t\t{\n\t\t\t\"id\": \"createdby\",\n\t\t\t\"name\": \"Created by\",\n\t\t\t\"filtering\": true,\n\t\t\t\"sorting\": true\n\t\t},\n\t\t{\n\t\t\t\"id\": \"dtcreated\",\n\t\t\t\"name\": \"Created\",\n\t\t\t\"icon\": \"ti ti-calendar\",\n\t\t\t\"filtering\": true,\n\t\t\t\"sorting\": true\n\t\t}\n\t]\n}\n</script>\n\n\n<script>\n\n\tvar data = PARSE('#datasource');\n\n\tfunction filtering(type, filter, sort, page) {\n\t\tconsole.log('Type:', type, 'Filter:', filter, 'Sort:', sort, 'Page:', page);\n\t\tUPD('data');\n\t}\n\n</script>\n```"}],"knowledge":{"content_sha256":"f1255562ae293134d1a9137753a087c2e1ca9aafeaa96e6e157025eb97b7b7a3","source_sha256":"ffeeebefee99ded9d6a4a51a189d7365531723544adddbb0e37d960a9ef24621","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","CUSTOM","DEF","E0E0E0","FORMATTING","MIT","NULL","PARSE","RENDER","UPD","VERSION__"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\t/* CUSTOM FORMATTING */\n\t.ui-serverlist-items { display: grid; column-gap: var(--gap); grid-template-columns: repeat(4, minmax(0,1fr)); padding: 20px; }\n\t.ui-serverlist-item { border: 2px solid #E0E0E0; padding: 10px; border-radius: 10px; }\n\t.ui-serverlist-check { position: absolute; margin: -20px 0 0 -20px; }\n</style>\n\n<ui-component name=\"serverlist\" path=\"data\" config=\"checkbox:1;height:500;exec:filtering\">\n\t<script type=\"text/html\">\n\t\t<div>{{ value.name }}, total pages: {{ $.pages }}</div>\n\t</script>\n</ui-component>\n\n<script id=\"datasource\" type=\"text/json\">\n{\n\t\"items\": [\n\t\t{\n\t\t\t\"id\": \"1\",\n\t\t\t\"name\": \"Hello world! 1\",\n\t\t\t\"createdby\": \"Peter Sirka\",\n\t\t\t\"dtcreated\": \"2024-11-25T22:43:53.455Z\"\n\t\t},\n\t\t{\n\t\t\t\"id\": \"2\",\n\t\t\t\"name\": \"Hello world! 2\",\n\t\t\t\"createdby\": \"Peter Sirka\",\n\t\t\t\"dtcreated\": \"2024-11-25T22:43:53.455Z\"\n\t\t},\n\t\t{\n\t\t\t\"id\": \"3\",\n\t\t\t\"name\": \"Hello world! 3\",\n\t\t\t\"createdby\": \"Peter Sirka\",\n\t\t\t\"dtcreated\": \"2024-11-25T22:43:53.455Z\"\n\t\t}\n\t],\n\t\"count\": 1,\n\t\"page\": 2,\n\t\"limit\": 50,\n\t\"pages\": 3,\n\t\"columns\": [\n\t\t{\n\t\t\t\"id\": \"name\",\n\t\t\t\"name\": \"Message\",\n\t\t\t\"icon\": \"ti ti-envelope\",\n\t\t\t\"filtering\": true,\n\t\t\t\"sorting\": true\n\t\t},\n\t\t{\n\t\t\t\"id\": \"createdby\",\n\t\t\t\"name\": \"Created by\",\n\t\t\t\"filtering\": true,\n\t\t\t\"sorting\": true\n\t\t},\n\t\t{\n\t\t\t\"id\": \"dtcreated\",\n\t\t\t\"name\": \"Created\",\n\t\t\t\"icon\": \"ti ti-calendar\",\n\t\t\t\"filtering\": true,\n\t\t\t\"sorting\": true\n\t\t}\n\t]\n}\n</script>\n\n\n<script>\n\n\tvar data = PARSE('#datasource');\n\n\tfunction filtering(type, filter, sort, page) {\n\t\tconsole.log('Type:', type, 'Filter:', filter, 'Sort:', sort, 'Page:', page);\n\t\tUPD('data');\n\t}\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/serverlisting","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/serverlisting/","markdown_url":"https://forge.totaljs.es/en/components/serverlisting.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"b44142df5a60720c4cc8db901351ca9f0f54cd3474191bd32436883b4f669e4b","checked_at":"2026-10-20T12:23:41.361Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-ServerListing","description":"j-ServerListing: easy usage; second model $ contains info about data-source { index: Number, page: Number, pages: Number, count: Number, value: YOURDATA }","messages":[{"role":"user","content":"j-ServerListing\n\nj-ServerListing: easy usage; second model $ contains info about data-source { index: Number, page: Number, pages: Number, count: Number, value: YOURDATA }"},{"role":"assistant","content":"__Configuration__:\n\n- `paginate {String}` link to a function `function(page)` for pagination\n- `pages {Number}` max. pages in pagination (default: `3` --> half + current page + half)\n- `scrolltop {Boolean}` scrolls top automatically when the user changes a page\n- `parent {String}` jQuery selector for obtaining of height, enables fixed area with custom scrollbars\n- `margin {Number}` a margin for `parent` (default: `0`)\n- `marginxs {Number}` optional, a top/bottom margin together for `xs` screen width\n- `marginsm` a top/bottom margin together for `sm` screen width\n- `marginmd` a top/bottom margin together for `md` screen width\n- `marginlg` a top/bottom margin together for `lg` screen width\n- `pluralizeitems` a pluralization for count of all items, form: `# zero,# one,# two-four,# five and more` (if empty, counter will be hidden)\n- `pluralizepages` a pluralization for count of all items, form: `# zero,# one,# two-four,# five and more` (if empty, counter will be hidden)\n- `modulo {Number}` can append empty items with `{ EMPTY: 1 }` object (default: `0`)\n- `nopages {Boolean}` can hide pagination if the pages are less than `2` (default: `false`)\n\n__Model__:\n\nThe model must be in the format below:\n\n```javascript\n{\n\tpage: Number,\n\tcount: Number,\n\tpages: Number,\n\titems: [{...}, {...}]\n}\n```\n\n__Tangular layout__:\n\n- `body` {String} contains all rendered items, output is __HTML__\n- `page` {Number} current page number\n- `pages` {Number} count of pages\n- `count` {Number} count of items\n\n__Tangular item__:\n\n- `model` contains an object from `Array`\n- second model `$` contains info about data-source `{ index: Number, page: Number, pages: Number, count: Number, value: YOUR_DATA }`\n\nUse it with `<ui-component name=\"serverlisting\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div class=\"row\">\n\t<div class=\"col-sm-6\">\n\t\t<h2>With layout</h2>\n\t\t<ui-component name=\"serverlisting\" path=\"model\" config=\"paginate:gotopage\">\n\t\t\t<script type=\"text/html\">\n\t\t\t\t<div class=\"col-xs-4\">\n\t\t\t\t\t<div>Item: {{ name }}</div>\n\t\t\t\t</div>\n\t\t\t</script>\n\t\t\t<script type=\"text/html\">\n\t\t\t\t<div><b>Layout:</b></div>\n\t\t\t\t<br />\n\t\t\t\t<div class=\"row\">{{ body | raw }}</div>\n\t\t\t</script>\n\t\t</ui-component>\n\t\t<br />\n\t</div>\n\n\t<div class=\"col-sm-6\">\n\t\t<h2>Without layout</h2>\n\t\t<ui-component name=\"serverlisting\" path=\"model\" config=\"paginate:gotopage\">\n\t\t\t<script type=\"text/html\">\n\t\t\t\t<div>\n\t\t\t\t\t<div>Item: {{ name }}</div>\n\t\t\t\t</div>\n\t\t\t</script>\n\t\t</ui-component>\n\t\t<br />\n\t</div>\n</div>\n\n<script>\n\n\tvar model = {};\n\tmodel.page = 1;\n\tmodel.pages = 3;\n\tmodel.count = 10;\n\tmodel.items = [];\n\n\tfor (var i = 0; i < 10; i++)\n\t\tmodel.items.push({ name: GUID(10) });\n\n\tfunction gotopage(page) {\n\t\talert('GO TO PAGE:' + page);\n\t}\n\n</script>\n```"}],"knowledge":{"content_sha256":"4208c1a50f7cc75a1ba796827c4e8997c324cc25ee989fdabe75822aa0a775d1","source_sha256":"b44142df5a60720c4cc8db901351ca9f0f54cd3474191bd32436883b4f669e4b","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","EMPTY","GUID","MIT","PAGE","YOUR_DATA"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div class=\"row\">\n\t<div class=\"col-sm-6\">\n\t\t<h2>With layout</h2>\n\t\t<ui-component name=\"serverlisting\" path=\"model\" config=\"paginate:gotopage\">\n\t\t\t<script type=\"text/html\">\n\t\t\t\t<div class=\"col-xs-4\">\n\t\t\t\t\t<div>Item: {{ name }}</div>\n\t\t\t\t</div>\n\t\t\t</script>\n\t\t\t<script type=\"text/html\">\n\t\t\t\t<div><b>Layout:</b></div>\n\t\t\t\t<br />\n\t\t\t\t<div class=\"row\">{{ body | raw }}</div>\n\t\t\t</script>\n\t\t</ui-component>\n\t\t<br />\n\t</div>\n\n\t<div class=\"col-sm-6\">\n\t\t<h2>Without layout</h2>\n\t\t<ui-component name=\"serverlisting\" path=\"model\" config=\"paginate:gotopage\">\n\t\t\t<script type=\"text/html\">\n\t\t\t\t<div>\n\t\t\t\t\t<div>Item: {{ name }}</div>\n\t\t\t\t</div>\n\t\t\t</script>\n\t\t</ui-component>\n\t\t<br />\n\t</div>\n</div>\n\n<script>\n\n\tvar model = {};\n\tmodel.page = 1;\n\tmodel.pages = 3;\n\tmodel.count = 10;\n\tmodel.items = [];\n\n\tfor (var i = 0; i < 10; i++)\n\t\tmodel.items.push({ name: GUID(10) });\n\n\tfunction gotopage(page) {\n\t\talert('GO TO PAGE:' + page);\n\t}\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/shoppingcart","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/shoppingcart/","markdown_url":"https://forge.totaljs.es/en/components/shoppingcart.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"51cb8b49dcf185769aad906d7e58c3d85b096c52891379e848d44ad49372a32f","checked_at":"2026-10-20T12:23:42.237Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-ShoppingCart","description":"j-ShoppingCart: singleton; supports discount","messages":[{"role":"user","content":"j-ShoppingCart\n\nj-ShoppingCart: singleton; supports discount"},{"role":"assistant","content":"- singleton\n- supports discount\n- data are stored in localStorage\n\n__Configuration__:\n\n- `discount {Number}` a discount in percentage (optional, default: `0`)\n- `expiration {String}` data expiration (default: `6 days`)\n\n__Output__:\n\n```js\n{\n\titems: [\n\t\t{\n\t\t\tname: String,\n\t\t\tprice: Number,\n\t\t\ttotal: Number,\n\t\t\tcount: Number,\n\t\t\tdate: Date\n\t\t}\n\t],\n\tprice: Number,\n\ttotal: Number,\n\tcount: Number\n}\n```\n\n### Methods\n\n- `instance.add(id, price, count, [name])` adds new product or increase count if the product exists\n- `instance.upd(id, [count], [price], [name])` updates product's data\n- `instance.buy(id, price, count, [name])` creates/updates product\n- `instance.rem(id)` removes product\n- `instance.has(id)` checks product\n- `instance.read(id)` reads product\n- `instance.clear()` clears all products\n- `instance.clean()` removes all products with zero count\n- `instance.sync(function(array_id_products, meta) {})` create data for synchronization with the server\n\n### Global Events\n\n- `ON('shoppingcart.add', function(item) {})`\n- `ON('shoppingcart.upd', function(item) {})`\n- `ON('shoppingcart.rem', function(id) {})`\n- `ON('shoppingcart.sum', function(data) {})`\n- `ON('shoppingcart.clear', function() {})`\n\nUse it with `<ui-component name=\"shoppingcart\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"shoppingcart\" path=\"common.shoppingcart\"></ui-component>\n\n<script>\n\tvar common = {};\n</script>\n```"}],"knowledge":{"content_sha256":"75efbac550362325a9ccdacd9e8dea579b4db66523d1a61f271f07fdfabbabc0","source_sha256":"51cb8b49dcf185769aad906d7e58c3d85b096c52891379e848d44ad49372a32f","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"shoppingcart\" path=\"common.shoppingcart\"></ui-component>\n\n<script>\n\tvar common = {};\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/shortcuts","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/shortcuts/","markdown_url":"https://forge.totaljs.es/en/components/shortcuts.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"ebfdf3359f72d13e8e17dd3380c999a32a3d46bf5c714b3e33987e3a163e7938","checked_at":"2026-10-20T12:23:43.118Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Shortcuts","description":"This component can help with creating keyboard shortcuts. The component is singleton.","messages":[{"role":"user","content":"j-Shortcuts\n\nThis component can help with creating keyboard shortcuts. The component is singleton."},{"role":"assistant","content":"This component can help with creating keyboard shortcuts. The component __is singleton__.\n\n__Methods__:\n- `component.register(shortcut, callback(e), [prevent], [preparation(e)])` - registers a new shortcut\n- `component.exec('shortcut')` - evaluates shortcut\n\n__Shortcuts__:\n- `enter`\n- `esc`\n- `tab`\n- `cmd`, `meta`, `win`\n- `shift`, `capslock`\n- `ctrl`\n- `alt`\n- `space`\n- `backspace`, `del`, `delete`\n- `left`, `up`, `right`, `down`\n- `F1`, `F2`, `F3`, `F4`, `F5`, `F6`, `F7`, `F8`, `F9`, `F10`, `F11`, `F12`\n- or custom key-codes `{Number}` or key-characters `{String(1)}`\n- `ins`\n- `remove` means`backspace` or `delete`\n- `save`\n- `selectall`\n- `clone`\n- `undo`\n- `redo`\n-  supports `.shortcut`, read more below\n\n__Example__:\n\n```javascript\n// SETTER(true, ...) --> works in jComponent +v9.0.0\n\nSETTER(true, 'shortcuts/register', 'cmd + enter', function(e) {\n    console.log('PRESSED: CMD + ENTER');\n});\n\nSETTER(true, 'shortcuts/register', 'alt + p', function(e) {\n    console.log('PRESSED: ALT + P');\n});\n\nSETTER(true, 'shortcuts/register', 'esc', function(e) {\n    console.log('PRESSED: esc');\n});\n\n// +v3 supports multiple shortcuts at the one time:\nSETTER(true, 'shortcuts/register', 'cmd+enter, ctrl+enter', function(e) {\n    console.log('PRESSED: CMD + ENTER');\n});\n\n// NEW: you can make preparation for the shortcut\nSETTER(true, 'shortcuts/register', 'cmd+enter, ctrl+enter', function(e) {\n    console.log('PRESSED: CMD + ENTER');\n}, function(e) {\n\t// You can enable/disable shortcut manually in the \"preparation function\"\n\t// but you need to \"return {Boolean}\", true will approve shortcut (it calls of the shortcut \"callback\")\n\t// otherwise \"returning false\" the shorcut will be skipped\n\treturn e.target.tagName !== 'INPUT';\n});\n```\n\n__New inline declaration__:\n\n```html\n<button class=\"exec shortcut\" data-shortcut=\"F1\" data-exec=\"METHO_TO_CALL\">Help</button>\n```\n\n- `.shortcut` class will emit `click` event\n- property `e.shortcut` will contain Event instance from the `keydown` event\n\n__New session shortcuts__:\n\n```javascript\n// Open session for new shortcuts\n// The methods disables all registered shortcuts while session is not end\nSETTER('shortcuts/session', function(register) {\n\tregister('esc', function() {\n\n\t\t// Cancels session\n\t\tSETTER('shortcuts/end');\n\n\t});\n});\n```\n\nUse it with `<ui-component name=\"shortcuts\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"shortcuts\"></ui-component>\n\n<div>PRESS ENTER or INSERT</div>\n\n<script>\n    var keyvalue = {};\n    SETTER(true, 'shortcuts/register', 'ENTER', () => alert('ENTER'));\n    SETTER(true, 'shortcuts/register', 'ins', () => alert('INSERT'));\n</script>\n```"}],"knowledge":{"content_sha256":"bfb6fe853acadcbd942b2ca141830169b259674033c9cb728213a1af67784cbd","source_sha256":"ebfdf3359f72d13e8e17dd3380c999a32a3d46bf5c714b3e33987e3a163e7938","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["ALT","CDN","CMD","DEF","ENTER","F10","F11","F12","INPUT","INSERT","METHO_TO_CALL","MIT","NEW","PRESS","PRESSED","SETTER"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"shortcuts\"></ui-component>\n\n<div>PRESS ENTER or INSERT</div>\n\n<script>\n    var keyvalue = {};\n    SETTER(true, 'shortcuts/register', 'ENTER', () => alert('ENTER'));\n    SETTER(true, 'shortcuts/register', 'ins', () => alert('INSERT'));\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/singleupload","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/singleupload/","markdown_url":"https://forge.totaljs.es/en/components/singleupload.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"7ade9255b2df3e534db1fc8e15848e385bf1bdcd43f8457dc0f03bd3bf2b053f","checked_at":"2026-10-20T12:23:43.854Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-SingleUpload","description":"This component works with loading (when the file is uploading) and message (when server throws an error) component.","messages":[{"role":"user","content":"j-SingleUpload\n\nThis component works with loading (when the file is uploading) and message (when server throws an error) component."},{"role":"assistant","content":"__Configuration__:\n\n- `url` {String} a target URL for the uploading (default: `/api/upload/`)\n- `accept` {String} (optional) can contain a content-type frow browsing files e.g. `image/*`\n- `label` {String} (optional) changes a current `innerHTML` (default: `innerHTML`)\n- `title` {String} Tangular template for rendering a title of attachment (default: `{{ name }}`)\n- `disabled` {Boolean} disables the component\n- `remap` {String} (optional) a remap function (default: `null`), example: `value.length ? value[0] : null`\n\n__Friendly components__:\nThis component works with `loading` (when the file is uploading) and `message` (when server throws an error) component.\n\nUse it with `<ui-component name=\"singleupload\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"singleupload\" path=\"mypicture\"><i class=\"ti ti-folder\"></i>Add attachment</ui-component>\n\n<script>\n\tvar mypicture = { name: 'my photo.jpg', url: '/download/3493874938438.jpg' };\n</script>\n```"}],"knowledge":{"content_sha256":"9470c4ec93ad07158dde85007bc9b89e45801fa63a68da180b74c2b34c551d30","source_sha256":"7ade9255b2df3e534db1fc8e15848e385bf1bdcd43f8457dc0f03bd3bf2b053f","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","URL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"singleupload\" path=\"mypicture\"><i class=\"ti ti-folder\"></i>Add attachment</ui-component>\n\n<script>\n\tvar mypicture = { name: 'my photo.jpg', url: '/download/3493874938438.jpg' };\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/slider","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/slider/","markdown_url":"https://forge.totaljs.es/en/components/slider.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"e467bb9c7c1e68b2037f366094758f291d557b1263595650ff0fa7a924a640a5","checked_at":"2026-10-20T12:23:44.586Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Slider","description":"This slider works only with 100% X axis. Output: percentage (from 0 to 100).","messages":[{"role":"user","content":"j-Slider\n\nThis slider works only with 100% X axis. Output: percentage (from 0 to 100)."},{"role":"assistant","content":"This slider works only with 100% X axis. Output: `percentage` (from 0 to 100).\n\n\n__Configuration__:\n\n- `disabled {Boolean}` can disable the component\n- `steps {Number}` a count of steps per 100%\n\nUse it with `<ui-component name=\"slider\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"max-width:300px\">\n\t<ui-component name=\"slider\" path=\"myrange\"></ui-component>\n\t<ui-bind path=\"myrange\" config=\"text:value + '%'\" class=\"help block\"></ui-bind>\n\t<br />\n\t<ui-component name=\"slider\" path=\"myrange2\"></ui-component>\n\t<ui-bind path=\"myrange2\" config=\"text:value + '%'\" class=\"help block\"></ui-bind>\n\t<br />\n\t<ui-component name=\"slider\" path=\"myrange\" config=\"disabled:1\"></ui-component>\n\t<ui-bind path=\"myrange\" config=\"text:value + '%'\" class=\"help block\"></ui-bind>\n</div>\n\n<script>\n\tvar myrange = 30;\n\tvar myrange2 = 80;\n</script>\n```"}],"knowledge":{"content_sha256":"fcb4bf8a9f2c65431f40cb65a3a0f5c8a58addd7d3a32c2be5ffad261f3b7168","source_sha256":"e467bb9c7c1e68b2037f366094758f291d557b1263595650ff0fa7a924a640a5","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"max-width:300px\">\n\t<ui-component name=\"slider\" path=\"myrange\"></ui-component>\n\t<ui-bind path=\"myrange\" config=\"text:value + '%'\" class=\"help block\"></ui-bind>\n\t<br />\n\t<ui-component name=\"slider\" path=\"myrange2\"></ui-component>\n\t<ui-bind path=\"myrange2\" config=\"text:value + '%'\" class=\"help block\"></ui-bind>\n\t<br />\n\t<ui-component name=\"slider\" path=\"myrange\" config=\"disabled:1\"></ui-component>\n\t<ui-bind path=\"myrange\" config=\"text:value + '%'\" class=\"help block\"></ui-bind>\n</div>\n\n<script>\n\tvar myrange = 30;\n\tvar myrange2 = 80;\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/snackbar","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/snackbar/","markdown_url":"https://forge.totaljs.es/en/components/snackbar.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"e9ff42c63bbec61f26758868371fd7acd24d17d8f1b2fd6047c5c6cddceed301","checked_at":"2026-10-20T12:23:45.478Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-SnackBar","description":"j-SnackBar: singleton; info message SETTER('snackbar/show', 'MESSAGE', '[BUTTONLABEL]', [callbackdismiss])","messages":[{"role":"user","content":"j-SnackBar\n\nj-SnackBar: singleton; info message SETTER('snackbar/show', 'MESSAGE', '[BUTTONLABEL]', [callbackdismiss])"},{"role":"assistant","content":"- singleton\n- info message `SETTER('snackbar/show', 'MESSAGE', '[BUTTON_LABEL]', [callback_dismiss])`\n- success message `SETTER('snackbar/success', 'MESSAGE', '[BUTTON_LABEL]', [callback_dismiss])`\n- warning message `SETTER('snackbar/warning', 'MESSAGE', '[BUTTON_LABEL]', [callback_dismiss])`\n- __NEW__ waiting message `SETTER('snackbar/waiting', 'MESSAGE', '[BUTTON_LABEL]', [callback_dismiss])`\n- `message` can contain `HTML` code\n\n\n__Configuration__:\n\n- `button {String}` A label for dismiss button (default: `OK`)\n- `timeout {Number}` A timeout in milliseconds (default: `4000`)\n\n__Works with ASETTER__:\n\n- errors are handled automatically\n\n```javascript\n// Without callback\nAJAX('POST /api/profile/', userprofile, ASETTER('snackbar/response', 'Profile has been saved successfully'));\n\n// Or with a callback\nAJAX('POST /api/profile/', userprofile, ASETTER('snackbar/response', 'Profile has been saved successfully', function(response) {\n\tconsole.log(response);\n}));\n\n// Or with a callback and without a message\nAJAX('POST /api/profile/', userprofile, ASETTER('snackbar/response', function(response) {\n\tconsole.log(response);\n}));\n\n// With auto-binding\nAJAX('POST /api/profile/', userprofile, ASETTER('snackbar/response', '?.response'));\nAJAX('POST /api/profile/', userprofile, ASETTER('snackbar/response', 'Your message', '?.response'));\n```\n\nUse it with `<ui-component name=\"snackbar\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"snackbar\"></ui-component>\n\n<div style=\"position: absolute; bottom: 0; left: 10px;\">\n\t<a href=\"https://totaljs.com\" title=\"totaljs.com\">Totaljs.com</a>\n</div>\n<div style=\"position: absolute; bottom: 0; right: 50px;\">\n\t<a href=\"https://componentator.com\" title=\"componentator.com\">Componentator.com</a>\n</div>\n\n<script>\n\tSETTER(true, 'snackbar/success', 'This is a message!', function() {\n\t\t// Optional\n\t\tconsole.log('Dismiss');\n\t});\n</script>\n```"}],"knowledge":{"content_sha256":"d4c159a21787d1200d216eb7ebba06929b55fd65f6a42a45795ee8983105afda","source_sha256":"e9ff42c63bbec61f26758868371fd7acd24d17d8f1b2fd6047c5c6cddceed301","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["AJAX","ASETTER","ASETTER__","BUTTON_LABEL","CDN","DEF","HTML","MESSAGE","MIT","POST","SETTER"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"snackbar\"></ui-component>\n\n<div style=\"position: absolute; bottom: 0; left: 10px;\">\n\t<a href=\"https://totaljs.com\" title=\"totaljs.com\">Totaljs.com</a>\n</div>\n<div style=\"position: absolute; bottom: 0; right: 50px;\">\n\t<a href=\"https://componentator.com\" title=\"componentator.com\">Componentator.com</a>\n</div>\n\n<script>\n\tSETTER(true, 'snackbar/success', 'This is a message!', function() {\n\t\t// Optional\n\t\tconsole.log('Dismiss');\n\t});\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/sounds","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/sounds/","markdown_url":"https://forge.totaljs.es/en/components/sounds.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"7d39f9bb7fdf1520f0bca691a802e6cc00f51db0b265aebd77f451cfed6aa3ab","checked_at":"2026-10-20T12:23:46.369Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Sounds","description":"This component contains some predefined sounds like beep, fail, success, etc... Path contains a volume control in percentage, 0 disables playing.","messages":[{"role":"user","content":"j-Sounds\n\nThis component contains some predefined sounds like beep, fail, success, etc... Path contains a volume control in percentage, 0 disables playing."},{"role":"assistant","content":"This component contains some predefined sounds like `beep`, `fail`, `success`, etc... Path contains a volume control in percentage, `0` disables playing.\n\n\n__Configuration__:\n\n- `url {String}` URL address to sound library (default: `https://cdn.componentator.com/sounds/`)\n- `volume {Number}` (default: `50`)\n\n__Good to know__:\n\n`path` can contain `{Number}`, which will control volume or `{Boolean}` which will enable or disable sounds.\n\n__Methods__:\n\n- `component.play(sound)` supported sounds:\n\t- `alert`\n\t- `message`\n\t- `badges`\n\t- `notifications`\n\t- `beep`\n\t- `confirm`\n\t- `done`\n\t- `drum`\n\t- `fail`\n\t- `success`\n- `component.success()`\n- `component.fail()`\n- `component.alert()`, `component.error()`\n- `component.warning()`\n- `component.notify()`\n- `component.badge()`\n- `component.confirm()`\n- `component.beep()`\n- `component.drum()`\n\nUse it with `<ui-component name=\"sounds\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"sounds\" path=\"volume\"></ui-component>\n\n<script>\n\tvar volume = 80;\n\n\tsetTimeout(function() {\n\t\tSETTER('sounds/play', 'beep');\n\t}, 1000);\n\n\tsetTimeout(function() {\n\t\tSETTER('sounds/play', 'drum');\n\t}, 3000);\n\n\tsetTimeout(function() {\n\t\tSETTER('sounds/play', 'fail');\n\t}, 7000);\n\n</script>\n```"}],"knowledge":{"content_sha256":"a25786ca81f7a7f72a534aaa09ad7d9efc63d100a561c235d6334e150ec54ee9","source_sha256":"7d39f9bb7fdf1520f0bca691a802e6cc00f51db0b265aebd77f451cfed6aa3ab","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","SETTER","URL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"sounds\" path=\"volume\"></ui-component>\n\n<script>\n\tvar volume = 80;\n\n\tsetTimeout(function() {\n\t\tSETTER('sounds/play', 'beep');\n\t}, 1000);\n\n\tsetTimeout(function() {\n\t\tSETTER('sounds/play', 'drum');\n\t}, 3000);\n\n\tsetTimeout(function() {\n\t\tSETTER('sounds/play', 'fail');\n\t}, 7000);\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/splashscreen","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/splashscreen/","markdown_url":"https://forge.totaljs.es/en/components/splashscreen.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"2c48c9beee0d6d9411263575acc4d3d5f000672526e60c3595b6f540de06b2bf","checked_at":"2026-10-20T12:23:47.243Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-SplashScreen","description":"A splash screen for single page applications. The body has to be wrapped into the <script type='text/html'>HTML</script> tag. The component will be destroyed automatically.","messages":[{"role":"user","content":"j-SplashScreen\n\nA splash screen for single page applications. The body has to be wrapped into the <script type='text/html'>HTML</script> tag. The component will be destroyed automatically."},{"role":"assistant","content":"A splash screen for single page applications. The body has to be wrapped into the `<script type=\"text/html\">HTML</script>` tag. The component will be destroyed automatically.\n\n__Configuration__:\n\n- `timeout` a timeout in milliseconds for destroying the splash screen (default: `3000`)\n- `autoremove:true {Boolean}` automatically removes component.\n- `remember {String}` e.g. `1 day` or `1 week` - it doesn't show a splash screen (default: `empty` = disabled)\n\n__Methods__:\n\n- `component.show([body])` will show splashscreen with a new or old body (`autoremove` must be `false`)\n\nUse it with `<ui-component name=\"splashscreen\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\thtml,body { height: 100%; overflow: hidden; padding: 0; margin: 0; }\n\tbody { background-color: #F0F0F0; }\n</style>\n\n<ui-component name=\"splashscreen\" class=\"ui-splashscreen\">\n\t<script type=\"text/html\">\n\t\t<div><img src=\"https://www.totaljs.com/download/B20200120T000000011.png\" alt=\"Total.js Framework\" border=\"0\" /></div>\n\t\t<br />\n\t\t<p>Total.js framework is a server-side framework for Node.js<br /><b>Better than sex</b></p>\n\t</script>\n</ui-component>\n\n<div>Lorem ipsum</div>\n<div>Lorem ipsum</div>\n<div>Lorem ipsum</div>\n<div>Lorem ipsum</div>\n<div>Lorem ipsum</div>\n<div>Lorem ipsum</div>\n<div>Lorem ipsum</div>\n```"}],"knowledge":{"content_sha256":"81a1a3a71c64cf23911e2aa40c71c94e2794673ebef08a3d7818454f99884ba4","source_sha256":"2c48c9beee0d6d9411263575acc4d3d5f000672526e60c3595b6f540de06b2bf","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["B20200120T000000011","CDN","DEF","F0F0F0","HTML","MIT","Total.js"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\thtml,body { height: 100%; overflow: hidden; padding: 0; margin: 0; }\n\tbody { background-color: #F0F0F0; }\n</style>\n\n<ui-component name=\"splashscreen\" class=\"ui-splashscreen\">\n\t<script type=\"text/html\">\n\t\t<div><img src=\"https://www.totaljs.com/download/B20200120T000000011.png\" alt=\"Total.js Framework\" border=\"0\" /></div>\n\t\t<br />\n\t\t<p>Total.js framework is a server-side framework for Node.js<br /><b>Better than sex</b></p>\n\t</script>\n</ui-component>\n\n<div>Lorem ipsum</div>\n<div>Lorem ipsum</div>\n<div>Lorem ipsum</div>\n<div>Lorem ipsum</div>\n<div>Lorem ipsum</div>\n<div>Lorem ipsum</div>\n<div>Lorem ipsum</div>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/spotlight","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/spotlight/","markdown_url":"https://forge.totaljs.es/en/components/spotlight.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"191dc631fd5bb1590390c6af9be0215882b466c112de8b587d04afd9ac9faf76","checked_at":"2026-10-20T12:23:48.116Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Spotlight","description":"j-Spotlight: singleton; works with Bootstrap Grid System","messages":[{"role":"user","content":"j-Spotlight\n\nj-Spotlight: singleton; works with Bootstrap Grid System"},{"role":"assistant","content":"- singleton\n- works with Bootstrap Grid System\n\n__Configuration__:\n\n- `height` {Number} a default height of each item (default: `40`, must be defined in CSS too)\n- `placeholder` {String} a default placeholder (default: `Search`)\n\n__Methods__:\n\nMethod: `component.show(opt)`\n\n- `opt.id {String}` optional, an identifier for recent items (default: `empty`)\n- `opt.recent {String}` enables recent items, must contain an expiration format (default: `3 days`, `empty` means disabled)\n- `opt.callback(selected_item) {Function}` is triggered when the user clicks on the item\n- `opt.cancel {Function}` is triggered then the user cancels component\n- `opt.search {String}` URL address for searching of values in the form `GET /api/search/?={0}`\n- or `opt.search {Function}` a link to `function(search, next(ARR_ITEMS))` for searching\n- `opt.cache {String}` enables cache, can contain an expiration format e.g. `1 day` or `session`\n- `opt.remap {Function}` it can remap results `items => items.map(...)`\n- `opt.clear {Boolean}` it clears previous results (default: `false`)\n- `opt.init {Boolean}` makes an empty search when the component is called (default: `false`)\n- `opt.delay {Number}` a delay in `ms` for performing searching (default: `100`)\n- `opt.height {Number}` a height of item (default: `config.height`)\n- `opt.class {String}` adds a custom class to the container\n- __NEW__ `opt.placeholder` {String} search placeholder (default: `config.placeholder`)\n\n__Good to know__:\n\n- `.items = [{ html }]` -> `html` field ( in items) will be rendered as a plain HTML\n\nUse it with `<ui-component name=\"spotlight\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\tbody { background-color: #F0F0F0; }\n</style>\n\n<ui-component name=\"spotlight\" config=\"placeholder:Search ...\"></ui-component>\n\n<div class=\"container\">\n\t<a href=\"javascript:spotlight()\">Show Spotlight</a>\n</div>\n\n<script>\n\tfunction spotlight() {\n\n\t\tvar opt = {};\n\n\t\topt.search = function(value, next) {\n\t\t\t// console.log(value);\n\t\t\tnext([{ name: 'Dashboard' }, { name: 'Invoices' }, { name: 'Orders' }, { name: 'Products' }, { name: 'Pages' }, { name: 'Notes' }, { name: 'Users' }, { name: 'Settings' }]);\n\t\t};\n\n\t\topt.callback = function(item) {\n\t\t\tconsole.log('Selected item:', item);\n\t\t};\n\n\t\tSETTER('spotlight/show', opt);\n\t}\n\n\tsetTimeout(spotlight, 500);\n</script>\n```"}],"knowledge":{"content_sha256":"564749c00088cc64f5896bc384a7d6b3f559892892463bd1c2e9482611570436","source_sha256":"191dc631fd5bb1590390c6af9be0215882b466c112de8b587d04afd9ac9faf76","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["ARR_ITEMS","CDN","CSS","DEF","F0F0F0","GET","HTML","MIT","SETTER","URL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\tbody { background-color: #F0F0F0; }\n</style>\n\n<ui-component name=\"spotlight\" config=\"placeholder:Search ...\"></ui-component>\n\n<div class=\"container\">\n\t<a href=\"javascript:spotlight()\">Show Spotlight</a>\n</div>\n\n<script>\n\tfunction spotlight() {\n\n\t\tvar opt = {};\n\n\t\topt.search = function(value, next) {\n\t\t\t// console.log(value);\n\t\t\tnext([{ name: 'Dashboard' }, { name: 'Invoices' }, { name: 'Orders' }, { name: 'Products' }, { name: 'Pages' }, { name: 'Notes' }, { name: 'Users' }, { name: 'Settings' }]);\n\t\t};\n\n\t\topt.callback = function(item) {\n\t\t\tconsole.log('Selected item:', item);\n\t\t};\n\n\t\tSETTER('spotlight/show', opt);\n\t}\n\n\tsetTimeout(spotlight, 500);\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/stash","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/stash/","markdown_url":"https://forge.totaljs.es/en/components/stash.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"68451d845048c90f509024cd44185b7cc93c9c4b5f870bb1adca34072b591561","checked_at":"2026-10-20T12:23:48.985Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Stash","description":"The component can postpone elements without destructing and detaching from the DOM. This component can help you with dynamic content from the server side. We use the component in the Total.js FlowStre","messages":[{"role":"user","content":"j-Stash\n\nThe component can postpone elements without destructing and detaching from the DOM. This component can help you with dynamic content from the server side. We use the component in the Total.js FlowStre"},{"role":"assistant","content":"The component can postpone elements without destructing and detaching from the DOM. This component can help you with dynamic content from the server side. We use the component in the Total.js FlowStream projects for the component's settings.\n\n- elements can contain everything like components, scopes, binders, etc.\n\n__Configuration__:\n\n- `load {String}` a path to the method `function(id, next(html/element))` that is evaluated if the content wasn't loaded before\n- __NEW__: `class {String}` a class name for the stash container (default: `hidden`)\n\n__Methods__:\n\n- `component.add(id, html/element)` adds new HTML/element\n- `component.clear([id])` clears specific ID (`id` is optional) or clears all cached content\n\nUse it with `<ui-component name=\"stash\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"stash\" path=\"common.id\" config=\"load:loadcontent\"></ui-component>\n\n<script>\n\n\tvar common = {};\n\n\tfunction loadcontent(id, next) {\n\t\t// next(html/element)\n\t\tconsole.log('LOAD:', id);\n\t\tnext('<div>{0}</div>'.format(id));\n\t}\n\n\tsetTimeout(function() {\n\t\tSET('common.id', 'component_settings_1');\n\t}, 1000);\n\n\tsetTimeout(function() {\n\t\tSET('common.id', 'component_settings_2');\n\t}, 3000);\n\n\tsetTimeout(function() {\n\t\tSET('common.id', 'component_settings_3');\n\t}, 6000);\n\n\tsetTimeout(function() {\n\t\tSET('common.id', 'component_settings_1');\n\t}, 9000);\n\n</script>\n```"}],"knowledge":{"content_sha256":"b250a27f45b5d99ee5a818aecf8d0675939ce15f54c62f888b7cc45c51a8667b","source_sha256":"68451d845048c90f509024cd44185b7cc93c9c4b5f870bb1adca34072b591561","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","DOM","HTML","LOAD","MIT","SET","Total.js"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"stash\" path=\"common.id\" config=\"load:loadcontent\"></ui-component>\n\n<script>\n\n\tvar common = {};\n\n\tfunction loadcontent(id, next) {\n\t\t// next(html/element)\n\t\tconsole.log('LOAD:', id);\n\t\tnext('<div>{0}</div>'.format(id));\n\t}\n\n\tsetTimeout(function() {\n\t\tSET('common.id', 'component_settings_1');\n\t}, 1000);\n\n\tsetTimeout(function() {\n\t\tSET('common.id', 'component_settings_2');\n\t}, 3000);\n\n\tsetTimeout(function() {\n\t\tSET('common.id', 'component_settings_3');\n\t}, 6000);\n\n\tsetTimeout(function() {\n\t\tSET('common.id', 'component_settings_1');\n\t}, 9000);\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/stats12","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/stats12/","markdown_url":"https://forge.totaljs.es/en/components/stats12.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"e4024d4c4c536ad64f3e04ce3e3bdc9e5ce11b4d29d54ca3bed768ef33dd1fe8","checked_at":"2026-10-20T12:23:49.721Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Stats12","description":"j-Stats12 options: height, max, border, highlight","messages":[{"role":"user","content":"j-Stats12\n\nj-Stats12 options: height, max, border, highlight"},{"role":"assistant","content":"__Configuration__:\n\n- `height {Number}` a height of chart (default: `120`)\n- `max {Number}` a maximal hourly value (default: `undefined`)\n- `border {Boolean}` enables border (default: `true`)\n- `highlight {Boolean}` highlights the current hour (default: `false`)\n\nUse it with `<ui-component name=\"stats12\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"width:400px;margin:0 auto\">\n\t<ui-component name=\"stats12\" path=\"mystats\" config=\"height:80\"></ui-component>\n</div>\n\n<script>\n\tvar mystats = [30, 500, 33, 49, 340, 94, 10, 30, 500, 33, 49, 3];\n</script>\n```"}],"knowledge":{"content_sha256":"fe37cbda0a67be0181ee96d044d586f6b42c84031d5f6ee0ad8e8802d0366a72","source_sha256":"e4024d4c4c536ad64f3e04ce3e3bdc9e5ce11b4d29d54ca3bed768ef33dd1fe8","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"width:400px;margin:0 auto\">\n\t<ui-component name=\"stats12\" path=\"mystats\" config=\"height:80\"></ui-component>\n</div>\n\n<script>\n\tvar mystats = [30, 500, 33, 49, 340, 94, 10, 30, 500, 33, 49, 3];\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/stats24","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/stats24/","markdown_url":"https://forge.totaljs.es/en/components/stats24.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"3b47c03151bc1f5ee2eff6540b1921840083340af1aa444907a98edb16203214","checked_at":"2026-10-20T12:23:50.597Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Stats24","description":"j-Stats24 options: height, max, tooltiplarge, tooltip, tooltiptext, border, highlight","messages":[{"role":"user","content":"j-Stats24\n\nj-Stats24 options: height, max, tooltiplarge, tooltip, tooltiptext, border, highlight"},{"role":"assistant","content":"__Configuration__:\n\n- `height {Number}` a height of chart (default: `120`)\n- `max {Number}` a maximal hourly value (default: `undefined`)\n- `tooltiplarge {Boolean}` enables tooltips in larger mode (default: `true`)\n- `tooltip {Boolean}` enables tooltips (default: `true`)\n- `tooltiptext {String}` Tooltip text (default: `{0}`)\n- `border {Boolean}` enables border (default: `true`)\n- `highlight {Boolean}` highlights current hour (default: `false`)\n\nUse it with `<ui-component name=\"stats24\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"LAZY tooltip\"></ui-component>\n\n<div style=\"width:300px;margin:0 auto\">\n\t<ui-component name=\"stats24\" path=\"mystats\" config=\"height:80;tooltiptext:{0} visitors\"></ui-component>\n</div>\n\n<script>\n\tvar mystats = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 3, 44, 0, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0];\n</script>\n```"}],"knowledge":{"content_sha256":"f4e9f8e2cb15f15e0bbb16152d30c51522a89f510b5f065fc0518c22ba7389ee","source_sha256":"3b47c03151bc1f5ee2eff6540b1921840083340af1aa444907a98edb16203214","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","LAZY","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"LAZY tooltip\"></ui-component>\n\n<div style=\"width:300px;margin:0 auto\">\n\t<ui-component name=\"stats24\" path=\"mystats\" config=\"height:80;tooltiptext:{0} visitors\"></ui-component>\n</div>\n\n<script>\n\tvar mystats = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 30, 3, 44, 0, 23, 0, 0, 0, 0, 0, 0, 0, 0, 0];\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/stats7","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/stats7/","markdown_url":"https://forge.totaljs.es/en/components/stats7.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"9984e84a34abf050eaf21b2ffc707c638db5a3ce9578a1cca17ba55b23d7e942","checked_at":"2026-10-20T12:23:51.469Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Stats7","description":"j-Stats7 options: height, max, border, highlight, firstday","messages":[{"role":"user","content":"j-Stats7\n\nj-Stats7 options: height, max, border, highlight, firstday"},{"role":"assistant","content":"__Configuration__:\n\n- `height {Number}` a height of chart (default: `120`)\n- `max {Number}` a maximal hourly value (default: `undefined`)\n- `border {Boolean}` enables border (default: `true`)\n- `highlight {Boolean}` highlights the current hour (default: `false`)\n- `firstday {Number}` first day (default: `0`)\n\nUse it with `<ui-component name=\"stats7\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"width:300px;margin:0 auto\">\n\t<ui-component name=\"stats7\" path=\"mystats\" config=\"height:80\"></ui-component>\n</div>\n\n<script>\n\tvar mystats = [30, 500, 33, 49, 340, 94, 10];\n</script>\n```"}],"knowledge":{"content_sha256":"3de9c228bd1e89d867296a5df59e83e9cc01c83841f534130bb7efc07309ada7","source_sha256":"9984e84a34abf050eaf21b2ffc707c638db5a3ce9578a1cca17ba55b23d7e942","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"width:300px;margin:0 auto\">\n\t<ui-component name=\"stats7\" path=\"mystats\" config=\"height:80\"></ui-component>\n</div>\n\n<script>\n\tvar mystats = [30, 500, 33, 49, 340, 94, 10];\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/statsbarsimple","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/statsbarsimple/","markdown_url":"https://forge.totaljs.es/en/components/statsbarsimple.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"509a758f841f00cc5efd8fa22f243e164d5628b5e844d05d967b5db9876849f9","checked_at":"2026-10-20T12:23:52.346Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-StatsBarSimple","description":"The component needs to have a defined template for rendering content and a tooltip. For an understanding look to the example.","messages":[{"role":"user","content":"j-StatsBarSimple\n\nThe component needs to have a defined template for rendering content and a tooltip. For an understanding look to the example."},{"role":"assistant","content":"__Configuration__:\n\n- `tooltip {Boolean}` enables tooltips (default: `true`)\n- `animate {Boolean}` enables animation (default: `true`)\n- `value {String}` a key for the value field (default: `value`)\n- `colors {String}` predefined colors\n\nThe component needs to have a defined template for rendering content and a tooltip. For an understanding look to the example.\n\nUse it with `<ui-component name=\"statsbarsimple\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"LAZY tooltip\"></ui-component>\n\n<ui-component name=\"statsbarsimple\" path=\"mystats\">\n\t<script type=\"text/html\">\n\t\t{{ name }} {{ value | format(0) }} visitors / {{ percentage }}%\n\t</script>\n\t<script type=\"text/html\">\n\t\tTooltip: {{ name }} {{ percentage }}%\n\t</script>\n</ui-component>\n\n<script>\n\tvar mystats = [{ name: 'Social', value: 40, color: '#649BD0' }, { name: 'Advert', value: 98, color: '#EF914E' }, { name: 'Search', value: 59, color: '#87C17E' }, { name: 'Direct', value: 76, color: '#9D9BC5' }];\n</script>\n```"}],"knowledge":{"content_sha256":"91635180a7fa998f3ce932108f17a78b11e2c273afdf4712681ecb9179b73788","source_sha256":"509a758f841f00cc5efd8fa22f243e164d5628b5e844d05d967b5db9876849f9","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","EF914E","LAZY","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"LAZY tooltip\"></ui-component>\n\n<ui-component name=\"statsbarsimple\" path=\"mystats\">\n\t<script type=\"text/html\">\n\t\t{{ name }} {{ value | format(0) }} visitors / {{ percentage }}%\n\t</script>\n\t<script type=\"text/html\">\n\t\tTooltip: {{ name }} {{ percentage }}%\n\t</script>\n</ui-component>\n\n<script>\n\tvar mystats = [{ name: 'Social', value: 40, color: '#649BD0' }, { name: 'Advert', value: 98, color: '#EF914E' }, { name: 'Search', value: 59, color: '#87C17E' }, { name: 'Direct', value: 76, color: '#9D9BC5' }];\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/statusinput","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/statusinput/","markdown_url":"https://forge.totaljs.es/en/components/statusinput.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"4094de99052575da976f0e119daf59cd600df75ec15324548e26c4a305cce39c","checked_at":"2026-10-20T12:23:53.229Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-StatusInput","description":"j-StatusInput: easy usage for different cases; supports Dark mode","messages":[{"role":"user","content":"j-StatusInput\n\nj-StatusInput: easy usage for different cases; supports Dark mode"},{"role":"assistant","content":"- supports __Dark mode__\n- singleton\n\n\n## Usage\n\n```javascript\nvar opt = {};\n\n// opt.placeholder {String}  : A placeholder and can be HTML (optional)\n// opt.icon {String}         : An icon (optional)\n// opt.hide {Function}       : Is executed when the StatusInput is hiding (optional)\n// opt.validate {RegExp}     : A validator, must be RegExp or nullable (optional)\n// opt.valuel {String}       : A default value for the input (optional)\n// opt.callback {Function}   : A callback\n\nopt.callback = function(newvalue) {\n\tconsole.log(newvalue);\n};\n\nSETTER('statusinput/show', opt);\n```\n\nUse it with `<ui-component name=\"statusinput\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"statusinput\"></ui-component>\n\n<script>\n\tsetTimeout(function() {\n\t\tvar opt = {};\n\t\topt.callback = function(val) {\n\t\t\tconsole.log('callback', val);\n\t\t};\n\t\topt.placeholder = 'Type a new name';\n\t\tSETTER('statusinput/show', opt);\n\t}, 500);\n</script>\n```"}],"knowledge":{"content_sha256":"894330f8850ee3b374098b8060439ed7922406d08e6360e301bb412e84409b2a","source_sha256":"4094de99052575da976f0e119daf59cd600df75ec15324548e26c4a305cce39c","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","HTML","MIT","SETTER"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"statusinput\"></ui-component>\n\n<script>\n\tsetTimeout(function() {\n\t\tvar opt = {};\n\t\topt.callback = function(val) {\n\t\t\tconsole.log('callback', val);\n\t\t};\n\t\topt.placeholder = 'Type a new name';\n\t\tSETTER('statusinput/show', opt);\n\t}, 500);\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/sticker","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/sticker/","markdown_url":"https://forge.totaljs.es/en/components/sticker.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"c29c7ea7d65f6a663cbb5c3ac480a045fb56e0de3feadcb74d08cbf16a32c594","checked_at":"2026-10-20T12:23:53.963Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Sticker","description":"j-Sticker: NEW: minheight {Number} a minimal height of scroll area for applying of classes","messages":[{"role":"user","content":"j-Sticker\n\nj-Sticker: NEW: minheight {Number} a minimal height of scroll area for applying of classes"},{"role":"assistant","content":"__Configuration__:\n\n- `on {String}` this class is enabled when the `scroll top` position is greater than this element `Y` position\n- `off {String}` this class is enabled when the `scroll top` position is smaller than this element `Y` position\n- `margin {Number}` a margin which will be deducted from the `top` position of the current element\n- `xs {Boolean}` enables sticker for extra small display (default: `false`)\n- `sm {Boolean}` enables sticker for small display (default: `true`)\n- `md {Boolean}` enables sticker for medium display (default: `true`)\n- `lg {Boolean}` enables sticker for large display (default: `true`)\n- `type {String}` a method name for obtaining of position `offset` (default) or `position`\n- `marginparent {Number}` a margin for `parent` scrolling (default: `0`)\n- `parent {String}` optional, a parent selector (look to example for understanding)\n- __NEW__: `minheight {Number}` a minimal height of scroll area for applying of classes\n\nUse it with `<ui-component name=\"sticker\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\t.fixed { position: fixed; background-color: red; right: 0; top: 0; left: 0; padding: 20px 0; text-align: center; color: white; }\n\t.relative { background-color: blue; padding: 10px; border-radius: 4px; position: relative; display: inline-block; color: white; }\n</style>\n\n<div style=\"padding:50px; height:1500px\">\n\t<div style=\"background-color:#F0F0F0;height:500px;margin:200px 0 0\">\n\t\t<ui-component name=\"sticker\" config=\"parent:parent\" style=\"position:absolute\">\n\t\t\tThis text will depent on parent position.\n\t\t</ui-component>\n\t</div>\n</div>\n<hr />\n<div style=\"padding:50px; height:1500px\">\n\t<ui-component name=\"sticker\" config=\"on:relative;off:fixed\">\n\t\tThis text will be on fixed position.\n\t</ui-component>\n</div>\n```"}],"knowledge":{"content_sha256":"19cb287f5a79726f4e731a54b0567ed8e59f22a23fb6118adfb82acb67be4049","source_sha256":"c29c7ea7d65f6a663cbb5c3ac480a045fb56e0de3feadcb74d08cbf16a32c594","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","F0F0F0","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\t.fixed { position: fixed; background-color: red; right: 0; top: 0; left: 0; padding: 20px 0; text-align: center; color: white; }\n\t.relative { background-color: blue; padding: 10px; border-radius: 4px; position: relative; display: inline-block; color: white; }\n</style>\n\n<div style=\"padding:50px; height:1500px\">\n\t<div style=\"background-color:#F0F0F0;height:500px;margin:200px 0 0\">\n\t\t<ui-component name=\"sticker\" config=\"parent:parent\" style=\"position:absolute\">\n\t\t\tThis text will depent on parent position.\n\t\t</ui-component>\n\t</div>\n</div>\n<hr />\n<div style=\"padding:50px; height:1500px\">\n\t<ui-component name=\"sticker\" config=\"on:relative;off:fixed\">\n\t\tThis text will be on fixed position.\n\t</ui-component>\n</div>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/suggestion","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/suggestion/","markdown_url":"https://forge.totaljs.es/en/components/suggestion.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"a0aec67b47fb06e299b17ccb99d81560b6eb663c8fcfd29c7a72022005c255bd","checked_at":"2026-10-20T12:23:54.841Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Suggestion","description":"If the value isn't defined then the component uses name as value.","messages":[{"role":"user","content":"j-Suggestion\n\nIf the value isn't defined then the component uses name as value."},{"role":"assistant","content":"- supports 3 orientations `left`, `center` and `right`\n- singleton\n- works with Bootstrap Grid System\n- works with touches\n- supports __dark mode__\n\n\n__Methods__:\n\nMethod: `component.show(orientation, targetElement, [items/function(search, next)], clickCallback)`\n\n- `orientation {String}` can be `left`, `center` or `right`\n- `targetElement {Selector/jQuery/Element}` a target where the component will be visible\n- `items {Array}` optional and must have this structure: `[{ name: String, value: String, icon: String }]` otherwise will be items read from the target's `data-options` attribute.\n- `callback(selectedItem) {Function}` is triggered when the user clicks on the item\n\nMethod: `component.hide()`\n\n__Attributes__:\n- `targetElement` can contain `data-options=\"Name 1|ti-building|Value 1;Name 2|ti-github|Value 2;\"` attribute with all items of the context menu.\n\n__Configuration__:\n- `placeholder` - a placeholder for the search input\n\n__Server-side searching__:\n\n```javascript\nSETTER('suggestion', 'show', element, function(search, next) {\n\tnext([{ name: 'Item 1' }, { name: 'Item 2' }]);\n}, function(selected) {\n\tconsole.log(selected);\n});\n```\n\nIf the `value` isn't defined then the component uses `name` as `value`.\n\n__Global events__:\n\n```javascript\nON('suggestion', function(visible, component, target) {\n\tconsole.log('Suggestion is', visible ? 'visible' : 'hidden');\n});\n```\n\nUse it with `<ui-component name=\"suggestion\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\tbody { background-color: #F0F0F0; }\n</style>\n\n<ui-component name=\"suggestion\" config=\"placeholder:Search ...\"></ui-component>\n\n<div class=\"container\">\n\t<a href=\"javascript:void(0)\">Suggest</a>\n</div>\n\n<script>\n\t$(document).on('click', 'a', function() {\n\t\tSETTER('suggestion/show', 'left', this, [{ name: 'Total.js' }, { name: 'Express.js' }, { name: 'Meteor.js' }, { name: 'Koa.js' }, { name: 'ASP.NET' }, { name: 'PHP' }, { name: 'Python' }, { name: 'Node.js' }], function(value) {\n\t\t\tconsole.log('CLICK', value);\n\t\t});\n\t});\n</script>\n```"}],"knowledge":{"content_sha256":"fec33fb3c9f3732bf1a452d7e8e08c7634cac710cfee99d57c80e7f64bafc045","source_sha256":"a0aec67b47fb06e299b17ccb99d81560b6eb663c8fcfd29c7a72022005c255bd","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["ASP","CDN","CLICK","DEF","F0F0F0","MIT","NET","PHP","SETTER","Total.js"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\tbody { background-color: #F0F0F0; }\n</style>\n\n<ui-component name=\"suggestion\" config=\"placeholder:Search ...\"></ui-component>\n\n<div class=\"container\">\n\t<a href=\"javascript:void(0)\">Suggest</a>\n</div>\n\n<script>\n\t$(document).on('click', 'a', function() {\n\t\tSETTER('suggestion/show', 'left', this, [{ name: 'Total.js' }, { name: 'Express.js' }, { name: 'Meteor.js' }, { name: 'Koa.js' }, { name: 'ASP.NET' }, { name: 'PHP' }, { name: 'Python' }, { name: 'Node.js' }], function(value) {\n\t\t\tconsole.log('CLICK', value);\n\t\t});\n\t});\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/svgicons","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/svgicons/","markdown_url":"https://forge.totaljs.es/en/components/svgicons.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"96c05368e4f6c1138f4a18e1575016269507678f160c9127c994cf821ed1bf4c","checked_at":"2026-10-20T12:23:55.716Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-SVGIcons","description":"This component registers a new Web component in the form <ui-svgicon name='iconname'></ui-svgicon>.","messages":[{"role":"user","content":"j-SVGIcons\n\nThis component registers a new Web component in the form <ui-svgicon name='iconname'></ui-svgicon>."},{"role":"assistant","content":"This component registers a new Web component in the form `<ui-svgicon name=\"icon_name\"></ui-svgicon>`.\n\n- Use SVG sprites generator <https://svgsprit.es/>\n\n__Configuration__:\n\n- `url {String}` a URL address to the SVG file (default: `empty`)\n- `size {Number}` a default image width (default: `empty`)\n\n__Attributes `<ui-svgicon>`:__\n\n- `name {String}` optional, icon name\n- `size {Number}` optional, a size of image (default: `config.size`)\n- `selected {String}` optional, selected icon name (it will be displayed if the parent element will contain `.selected` class)\n\nUse it with `<ui-component name=\"svgicons\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"svgicons\" config=\"url:/svgicons.svg;size:30\"></ui-component>\n\n<div class=\"padding\">\n\t<ui-svgicon name=\"phone\"></ui-svgicon>\n\t<ui-svgicon name=\"phone\"></ui-svgicon>\n\t<ui-svgicon name=\"phone\" size=\"50\"></ui-svgicon>\n\t<span class=\"toggle\">\n\t\t<ui-svgicon name=\"profile\" selected=\"profilefull\"></ui-svgicon>\n\t</span>\n</div>\n\n<script>\n\n\tsetInterval(function() {\n\t\t$('.toggle').tclass('selected');\n\t}, 1500);\n\n</script>\n```"}],"knowledge":{"content_sha256":"1f8677cd1bf4affcf17b4ca89470f42a558aacd0e22d60732439682916846bf2","source_sha256":"96c05368e4f6c1138f4a18e1575016269507678f160c9127c994cf821ed1bf4c","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","SVG","URL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"svgicons\" config=\"url:/svgicons.svg;size:30\"></ui-component>\n\n<div class=\"padding\">\n\t<ui-svgicon name=\"phone\"></ui-svgicon>\n\t<ui-svgicon name=\"phone\"></ui-svgicon>\n\t<ui-svgicon name=\"phone\" size=\"50\"></ui-svgicon>\n\t<span class=\"toggle\">\n\t\t<ui-svgicon name=\"profile\" selected=\"profilefull\"></ui-svgicon>\n\t</span>\n</div>\n\n<script>\n\n\tsetInterval(function() {\n\t\t$('.toggle').tclass('selected');\n\t}, 1500);\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/swiper","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/swiper/","markdown_url":"https://forge.totaljs.es/en/components/swiper.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"01831f5d89495e4e9dd91dccf8704bc958b4e50c98a060750bbbb1dff1b4a272","checked_at":"2026-10-20T12:23:57.047Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Swiper","description":"j-Swiper: wrapper for [Swiper.js library](https://swiperjs.com/); easy usage","messages":[{"role":"user","content":"j-Swiper\n\nj-Swiper: wrapper for [Swiper.js library](https://swiperjs.com/); easy usage"},{"role":"assistant","content":"- wrapper for [Swiper.js library](https://swiperjs.com/)\n__Configuration__:\n\n- `slidesperview {Number}` count of the items in the one slide (default: `1`)\n- `spacebetween {Number}` a space between items (default: `0`)\n- `navigation {Boolean}` show/hide navigation arrows (default: `false`)\n- `pagination {Boolean}` show/hide pagination bullets (default: `false`)\n- `autoplay {Boolean}` allow autoplay (default: `false`)\n- `delay {Number}` a delay between slides change, working with autoplay only (default: `2500`)\n- `loop {Boolean}` infinity loop through the slides (default: `false`)\n- __NEW__ `reset {Number}` idle time after which the swiper restarts and returns to the first image (enter in minutes)\n- __NEW__ `freemode {Boolean}` moving slides without fixed alignment to individual slides (default: `false`)\n\nUse it with `<ui-component name=\"swiper\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Dodo Marton. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"margin-bottom:30px\">\n\t<ui-component name=\"swiper\" path=\"datasource\" config=\"slidesperview:3;spacebetween:30;pagination:1;navigation:1\" class=\"hidden invisible\">\n\t\t<script type=\"text/html\">\n\t\t\t<div style=\"background-color:#E0E0E0;height:150px;color:#000\">{{ name }}</div>\n\t\t</script>\n\t</ui-component>\n</div>\n\n<div>\n\t<ui-component name=\"swiper\" config=\"slidesperview:3;autoplay:1\" class=\"hidden invisible\">\n\t\t<figure class=\"swiper-slide\"><div style=\"background-color:#E0E0E0;height:150px;color:#000\">A</div></figure>\n\t\t<figure class=\"swiper-slide\"><div style=\"background-color:#E0E0E0;height:150px;color:#000\">B</div></figure>\n\t\t<figure class=\"swiper-slide\"><div style=\"background-color:#E0E0E0;height:150px;color:#000\">C</div></figure>\n\t\t<figure class=\"swiper-slide\"><div style=\"background-color:#E0E0E0;height:150px;color:#000\">D</div></figure>\n\t\t<figure class=\"swiper-slide\"><div style=\"background-color:#E0E0E0;height:150px;color:#000\">E</div></figure>\n\t\t<figure class=\"swiper-slide\"><div style=\"background-color:#E0E0E0;height:150px;color:#000\">F</div></figure>\n\t</ui-component>\n</div>\n<script>\n\tvar datasource = [{ name: 'A' }, { name: 'B' }, { name: 'C' }, { name: 'D' }, { name: 'E' }, { name: 'F' }, { name: 'G' }, { name: 'H' }, { name: 'I' }, { name: 'J' }]\n</script>\n```"}],"knowledge":{"content_sha256":"4b36a89fe5de71f62485c2b96d511f38963c44008775b3901d630b5397d2c04a","source_sha256":"01831f5d89495e4e9dd91dccf8704bc958b4e50c98a060750bbbb1dff1b4a272","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","E0E0E0","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"margin-bottom:30px\">\n\t<ui-component name=\"swiper\" path=\"datasource\" config=\"slidesperview:3;spacebetween:30;pagination:1;navigation:1\" class=\"hidden invisible\">\n\t\t<script type=\"text/html\">\n\t\t\t<div style=\"background-color:#E0E0E0;height:150px;color:#000\">{{ name }}</div>\n\t\t</script>\n\t</ui-component>\n</div>\n\n<div>\n\t<ui-component name=\"swiper\" config=\"slidesperview:3;autoplay:1\" class=\"hidden invisible\">\n\t\t<figure class=\"swiper-slide\"><div style=\"background-color:#E0E0E0;height:150px;color:#000\">A</div></figure>\n\t\t<figure class=\"swiper-slide\"><div style=\"background-color:#E0E0E0;height:150px;color:#000\">B</div></figure>\n\t\t<figure class=\"swiper-slide\"><div style=\"background-color:#E0E0E0;height:150px;color:#000\">C</div></figure>\n\t\t<figure class=\"swiper-slide\"><div style=\"background-color:#E0E0E0;height:150px;color:#000\">D</div></figure>\n\t\t<figure class=\"swiper-slide\"><div style=\"background-color:#E0E0E0;height:150px;color:#000\">E</div></figure>\n\t\t<figure class=\"swiper-slide\"><div style=\"background-color:#E0E0E0;height:150px;color:#000\">F</div></figure>\n\t</ui-component>\n</div>\n<script>\n\tvar datasource = [{ name: 'A' }, { name: 'B' }, { name: 'C' }, { name: 'D' }, { name: 'E' }, { name: 'F' }, { name: 'G' }, { name: 'H' }, { name: 'I' }, { name: 'J' }]\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/switchbutton","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/switchbutton/","markdown_url":"https://forge.totaljs.es/en/components/switchbutton.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"eae3f0f1fb0b39e54573163eeccee7a99ed8ec9dcb4f222587e2b116012dcd00","checked_at":"2026-10-20T12:23:57.929Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-SwitchButton","description":"j-SwitchButton options: required, disabled, border, align, description","messages":[{"role":"user","content":"j-SwitchButton\n\nj-SwitchButton options: required, disabled, border, align, description"},{"role":"assistant","content":"__Configuration__:\n\n- `required {Boolean}` optional, enables \"required\" (default: `false`)\n- `disabled {Boolean}` optional, disables this component\n- `border {Boolean}` optional, allow border bottom (default: `false`)\n- `align {String}` left/right alignment of the switch element (default: `left`)\n- `description {String}` optional\n\nUse it with `<ui-component name=\"switchbutton\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Tomáš Novák. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"width:500px\">\n\t<ui-component name=\"switchbutton\" path=\"form.newsletter\" config=\"description:Product updates;border:1\">Receive messages from our platform</ui-component>\n\t<ui-component name=\"switchbutton\" path=\"form.terms\" config=\"description:Promotions and tips;border:1\">Receive coupons, promotions, surveys</ui-component>\n\t<ui-component name=\"switchbutton\" path=\"form.marketing\" config=\"description:Marketing information;required:1;align:right;border:1\">Receive marketing somarines</ui-component>\n\t<ui-component name=\"switchbutton\" path=\"form.disabled\" config=\"description:Acceptance of 2023;disabled:1;align:right\">Documents are not ready</ui-component>\n</div>\n<script>\n\tvar form = {};\n\tform.newsletter = true;\n</script>\n```"}],"knowledge":{"content_sha256":"4e87cc115f80fd162a2ada96a27c713148d71963f35a4566cd183bdde8d67cc9","source_sha256":"eae3f0f1fb0b39e54573163eeccee7a99ed8ec9dcb4f222587e2b116012dcd00","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"width:500px\">\n\t<ui-component name=\"switchbutton\" path=\"form.newsletter\" config=\"description:Product updates;border:1\">Receive messages from our platform</ui-component>\n\t<ui-component name=\"switchbutton\" path=\"form.terms\" config=\"description:Promotions and tips;border:1\">Receive coupons, promotions, surveys</ui-component>\n\t<ui-component name=\"switchbutton\" path=\"form.marketing\" config=\"description:Marketing information;required:1;align:right;border:1\">Receive marketing somarines</ui-component>\n\t<ui-component name=\"switchbutton\" path=\"form.disabled\" config=\"description:Acceptance of 2023;disabled:1;align:right\">Documents are not ready</ui-component>\n</div>\n<script>\n\tvar form = {};\n\tform.newsletter = true;\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/table","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/table/","markdown_url":"https://forge.totaljs.es/en/components/table.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"7d7a297e3a19b7e093d17cce37570e3780fa5f5fe21b4710f91140126540b7a4","checked_at":"2026-10-20T12:23:58.811Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Table","description":"Display records in a configurable table.","messages":[{"role":"user","content":"j-Table\n\nDisplay records in a configurable table."},{"role":"assistant","content":"- `j-Table` is a very very very simple alternative to `j-DataGrid` targeted for UI\n- `data-source` expects `Array` or `Object` with `{ page: 1, pages: 10, limit: 50, items: [], count: 1000 }`\n\n__Configuration__:\n\n- `highlight {Boolean}` enables highlighting of the row (default: `true`)\n- `unhighlight {Boolean}` enables undo-highlighting of the row (default: `true`)\n- `multiple {Boolean}` enables multiple row highlighting (default: `false`)\n- `detail {Boolean/String}` if `string` then the value must be a link to `function(row_data, next_fn(new_data_for_detail_template_OR__new_html), tdelement)`\n- `exec {String}` a path to `function(row/rows)` or path to `variable`\n- `remember {Boolean}` enables remembering of last selected rows (default: `false`)\n- `pk {String}` a primary key (default: `id`)\n- `border {Boolean}` enables border (default: `true`)\n- `scrollbar {Boolean}` enables custom scrollbar (default: `false`) + important: `height` must be specified\n- `height {Number/String}` height of grid or can contain selector `window`, `parent` or custom selector `.ui-viewbox-body`\n- `paginate {String}` a link to `function(model)` for performing pagination `{ page: 1, limit: 10, sort: [] }`\n- `redraw {String}` a link to function, it's evaluated when the table is redrawing\n- `filter {String}` a link to function, it's evaluated before the table is rendering\n- `dblclick {String}` a link to function, it's evaluated when the user performs double-click\n- `datasource {String}` path to data-source (for other extra data in the template. Usage: `{{ $.data.something }}`)\n- `click {String}` a path to `function(button_name, row, button_element)`, is executed when the user will click on the button\n- `flat {Boolean}` removes padding in cells (default: `false`)\n- `noborder {Boolean}` removes border around (default: `false`)\n- __NEW__: `hidewhenempty {Boolean}` hides table when empty (default: `false`)\n\n__Good to know__:\n\n- each `tr` element contains `data-index` attribute with `row` index\n- each `td` __element of detail__ contains `data-index` attribute with `row` index\n\n__HTML definition__:\n\n```html\n<ui-component name=\"table\" path=\"\" config=\"\">\n\n\t<!--\n\t\tdata-size=\"TD1,TD2,TD3\"      : column width, optional (can contain pixels or percentage), \"0\" means \"auto\" width\n\t\tdata-head=\"TD1,TD2,TD3\"      : column names, optional\n\t\tdata-align=\"TD1,TD2,TD3\"     : column align, optional (0: left, 1: center, 2: right)\n\t\tdata-type=\"TYPE\"             : can be \"detail\" (Tangular template) or \"empty\" (empty is rendered when the Array is empty)\n\t\tdata-display=\"MD,XS\"         : can contain display types \"LG\" large, \"MD\" medium, \"SM\" small, \"XS\" extra small\n\t\tdata-sort=\"name,,price\"      : enables sorting, can contain the name of fields (a column with empty value will have disabled sorting)\n\t-->\n\t<script type=\"text/html\" data-size=\"0,150px,50px\" data-head=\"Name,Created,Opt\" data-align=\"0,0,1\" data-sort=\"1\">\n\t\t<tr>\n\t\t\t<td>{{ name }}</td>\n\t\t\t<td>{{ dtcreated | format('[date]') }}</td>\n\t\t\t<td>OK</td>\n\t\t</tr>\n\t</script>\n\n\t<script type=\"text/html\" data-type=\"detail\">\n\t\tDETAIL: {{ name }}\n\t\tHIDDEN COMMANDS: {{ $.index }} or {{ $.user }}\n\t</script>\n\n\t<script type=\"text/html\" data-type=\"empty\">\n\t\tEMPTY\n\t</script>\n\n</ui-component>\n```\n\n__Good to know__:\n\n- `data-head` can contain Font Awesome icons, it must be wrapped like this `data-head=\"Name,'cog'\"` or `data-name=\"Name,'youtube fab'\"`\n- `NULL('link.to.data')` evaluates `config.paginate`\n\nUse it with `<ui-component name=\"table\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\tbody{padding:50px;margin:0;font-family:Arial;height:100%;}\n\thtml{height:100%;}\n</style>\n\n<ui-component name=\"table\" path=\"items\" config=\"detail:true;datasource:extra\">\n\n\t<!-- WITHOUT HEADERS -->\n\n\t<script type=\"text/html\" data-size=\"0,120px,120px\" data-head=\"Name,Price,Controls\" data-align=\"0,2,1\" data-sort=\"name,price,\">\n\t\t<tr>\n\t\t\t<td><b>{{ $.data.author }}:</b> {{ name }}</td>\n\t\t\t<td class=\"right\">{{ price | format(2) }} &euro;</td>\n\t\t\t<td class=\"center\"><button><i class=\"ti ti-search\"></i></button><button><i class=\"ti ti-pencil\"></i></button><button class=\"red\" disabled><i class=\"ti ti-trash\"></i></button></td>\n\t\t</tr>\n\t</script>\n\n\t<script type=\"text/html\" data-type=\"detail\">\n\t\t<div class=\"padding npt npb\">\n\t\t\t<div>Author: <b>{{ $.data.author }}</b></div>\n\t\t\t<div>Role: <b>{{ $.data.role }}</b></div>\n\t\t\t<div>Name: <b>{{ name }}</b></div>\n\t\t\t<div>Description: <b>{{ description }}</b></div>\n\t\t</div>\n\t</script>\n\n\t<script type=\"text/html\" data-type=\"empty\">\n\t\tNo data\n\t</script>\n\n</ui-component>\n\n<br />\n\n<ui-component name=\"table\" path=\"items\" config=\"border:0\">\n\n\t<!-- WITHOUT HEADERS -->\n\t<script type=\"text/html\" data-size=\"0,120px,120px\">\n\t\t<tr>\n\t\t\t<td>{{ name }}</td>\n\t\t\t<td class=\"right\">{{ price | format(2) }} &euro;</td>\n\t\t\t<td class=\"center\">{{ created | format('yyyy-MM-dd') }}</td>\n\t\t</tr>\n\t</script>\n\n\t<script type=\"text/html\" data-type=\"empty\">\n\t\tNo data\n\t</script>\n\n</ui-component>\n\n<script>\n\n\tvar items = [];\n\tvar extra = {\n\t\tauthor: 'Brutus',\n\t\trole: 'Planter'\n\t};\n\n\titems.push({ name: '1 Lorem ipsum dolor.', description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consectetur, mollitia!', price: 233, created: new Date().add('-5 days') });\n\titems.push({ name: '2 Lorem ipsum dolor.', description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consectetur, mollitia!', price: 122, created: new Date().add('-1 year') });\n\titems.push({ name: '3 Lorem ipsum dolor.', description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consectetur, mollitia!', price: 119, created: new Date().add('-1 month') });\n\titems.push({ name: '4 Lorem ipsum dolor.', description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consectetur, mollitia!', price: 35.34, created: new Date().add('-14 days') });\n\titems.push({ name: '5 Lorem ipsum dolor.', description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consectetur, mollitia!', price: 439.33, created: new Date().add('-5 hours') });\n\titems.push({ name: '6 Lorem ipsum dolor.', description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consectetur, mollitia!', price: 111.12, created: new Date().add('-349 minutes') });\n\n</script>\n```"}],"knowledge":{"content_sha256":"651ade85a957c60b98ba9284ba40e67593b296ad9763c24befeff7b6e1088e52","source_sha256":"7d7a297e3a19b7e093d17cce37570e3780fa5f5fe21b4710f91140126540b7a4","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","COMMANDS","DEF","DETAIL","EMPTY","HEADERS","HIDDEN","MIT","NULL","TD1","TD2","TD3","TYPE","WITHOUT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\tbody{padding:50px;margin:0;font-family:Arial;height:100%;}\n\thtml{height:100%;}\n</style>\n\n<ui-component name=\"table\" path=\"items\" config=\"detail:true;datasource:extra\">\n\n\t<!-- WITHOUT HEADERS -->\n\n\t<script type=\"text/html\" data-size=\"0,120px,120px\" data-head=\"Name,Price,Controls\" data-align=\"0,2,1\" data-sort=\"name,price,\">\n\t\t<tr>\n\t\t\t<td><b>{{ $.data.author }}:</b> {{ name }}</td>\n\t\t\t<td class=\"right\">{{ price | format(2) }} &euro;</td>\n\t\t\t<td class=\"center\"><button><i class=\"ti ti-search\"></i></button><button><i class=\"ti ti-pencil\"></i></button><button class=\"red\" disabled><i class=\"ti ti-trash\"></i></button></td>\n\t\t</tr>\n\t</script>\n\n\t<script type=\"text/html\" data-type=\"detail\">\n\t\t<div class=\"padding npt npb\">\n\t\t\t<div>Author: <b>{{ $.data.author }}</b></div>\n\t\t\t<div>Role: <b>{{ $.data.role }}</b></div>\n\t\t\t<div>Name: <b>{{ name }}</b></div>\n\t\t\t<div>Description: <b>{{ description }}</b></div>\n\t\t</div>\n\t</script>\n\n\t<script type=\"text/html\" data-type=\"empty\">\n\t\tNo data\n\t</script>\n\n</ui-component>\n\n<br />\n\n<ui-component name=\"table\" path=\"items\" config=\"border:0\">\n\n\t<!-- WITHOUT HEADERS -->\n\t<script type=\"text/html\" data-size=\"0,120px,120px\">\n\t\t<tr>\n\t\t\t<td>{{ name }}</td>\n\t\t\t<td class=\"right\">{{ price | format(2) }} &euro;</td>\n\t\t\t<td class=\"center\">{{ created | format('yyyy-MM-dd') }}</td>\n\t\t</tr>\n\t</script>\n\n\t<script type=\"text/html\" data-type=\"empty\">\n\t\tNo data\n\t</script>\n\n</ui-component>\n\n<script>\n\n\tvar items = [];\n\tvar extra = {\n\t\tauthor: 'Brutus',\n\t\trole: 'Planter'\n\t};\n\n\titems.push({ name: '1 Lorem ipsum dolor.', description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consectetur, mollitia!', price: 233, created: new Date().add('-5 days') });\n\titems.push({ name: '2 Lorem ipsum dolor.', description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consectetur, mollitia!', price: 122, created: new Date().add('-1 year') });\n\titems.push({ name: '3 Lorem ipsum dolor.', description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consectetur, mollitia!', price: 119, created: new Date().add('-1 month') });\n\titems.push({ name: '4 Lorem ipsum dolor.', description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consectetur, mollitia!', price: 35.34, created: new Date().add('-14 days') });\n\titems.push({ name: '5 Lorem ipsum dolor.', description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consectetur, mollitia!', price: 439.33, created: new Date().add('-5 hours') });\n\titems.push({ name: '6 Lorem ipsum dolor.', description: 'Lorem ipsum dolor sit amet, consectetur adipisicing elit. Consectetur, mollitia!', price: 111.12, created: new Date().add('-349 minutes') });\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/tablegrid","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/tablegrid/","markdown_url":"https://forge.totaljs.es/en/components/tablegrid.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"c36bcc6b514e99ff0c86bfe340a9de37705fd5f0b39351fc54da98089c176361","checked_at":"2026-10-20T12:23:59.696Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-TableGrid","description":"The component makes a simple table grid (composed of div elements) divided by a simple border that you can modify.","messages":[{"role":"user","content":"j-TableGrid\n\nThe component makes a simple table grid (composed of div elements) divided by a simple border that you can modify."},{"role":"assistant","content":"The component makes a simple table grid (composed of `div` elements) divided by a simple border that you can modify.\n\n\n__Configuration__:\n\n- `count {Number}` a default count of items per row\n- `countxs {Number}` items per row for mobile display\n- `countsm {Number}` items per row for small display\n- `countmd {Number}` items per row for medium display\n- `countlg {Number}` items per row for a large display\n- `parent {String}` for obtaining size (default: `window`)\n- `margin {Number}` a vertical margin (default: `0`)\n- `scrollbar {Boolean}` enables custom scrollbar (default: `false`)\n- `fill {Boolean}` fills the grid with empty rowsm need to define `row` and `scrollbar`\n- `row {Number}` a default row height for all displays (default: `0`)\n- `rowxs {Number}` a default row height for mobile display (default: `0`)\n- `rowsm {Number}` a default row height for small display (default: `0`)\n- `rowmd {Number}` a default row height for medium display (default: `0`)\n- `rowlg {Number}` a default row height for large display (default: `0`)\n- `scrolltop {Boolean}` scrolls to the top after change data (default: `true`)\n\n\n__Classes__:\n\n- `ui-tablegrid-bl` means border-left\n- `ui-tablegrid-bt` means border-top\n- `ui-tablegrid-first` first item in the row\n- `ui-tablegrid-last` last item in the row\n- `ui-tablegrid-empty` empty item\n\nUse it with `<ui-component name=\"tablegrid\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"width:500px;padding:10px\">\n\t<ui-component name=\"tablegrid\" path=\"items\" config=\"count:3\" style=\"border:1px solid #EBECF2\">\n\t\t<script type=\"text/html\">\n\t\t\t<div style=\"padding:20px 0;text-align:center\">{{ value.name }}</div>\n\t\t</script>\n\t\t<script type=\"text/html\">\n\t\t\t<div style=\"padding:20px 0;text-align:center\" class=\"b\">Optional: empty</div>\n\t\t</script>\n\t</ui-component>\n</div>\n<script>\n\tvar items = [{ name: 'Total.js' }, { name: 'SuperAdmin' }, { name: 'Code Editor' }, { name: 'jComponent' }];\n</script>\n```"}],"knowledge":{"content_sha256":"8d85554e3ce4c5447c8a4dbd54836e8e59fa42799b0d4a6b034a7d721cc0a34d","source_sha256":"c36bcc6b514e99ff0c86bfe340a9de37705fd5f0b39351fc54da98089c176361","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","EBECF2","MIT","Total.js"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"width:500px;padding:10px\">\n\t<ui-component name=\"tablegrid\" path=\"items\" config=\"count:3\" style=\"border:1px solid #EBECF2\">\n\t\t<script type=\"text/html\">\n\t\t\t<div style=\"padding:20px 0;text-align:center\">{{ value.name }}</div>\n\t\t</script>\n\t\t<script type=\"text/html\">\n\t\t\t<div style=\"padding:20px 0;text-align:center\" class=\"b\">Optional: empty</div>\n\t\t</script>\n\t</ui-component>\n</div>\n<script>\n\tvar items = [{ name: 'Total.js' }, { name: 'SuperAdmin' }, { name: 'Code Editor' }, { name: 'jComponent' }];\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/tabmenu","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/tabmenu/","markdown_url":"https://forge.totaljs.es/en/components/tabmenu.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"b45d1f989d68514638e2ae2ca1e98206d8daaeeb27ad02baf3471ecd9e2084ba","checked_at":"2026-10-20T12:24:00.422Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-TabMenu","description":"j-TabMenu options: class, selector, datasource, disabled, exec, attr","messages":[{"role":"user","content":"j-TabMenu\n\nj-TabMenu options: class, selector, datasource, disabled, exec, attr"},{"role":"assistant","content":"__Configuration__:\n\n- `class {String}` optional, is a class name for a selected element (default: `selected`)\n- `selector {String}` optional, is a jQuery selector (default: `li`)\n- `datasource {String}` optional, a path to a data-source, content must be Tangular template\n- `disabled {Boolean}` optional, it can disable tabs\n- `exec {String}` optional, a link to the function (it will be executed when the tab is changed)\n- `attr {String}` optional, a default attribute for obtaining tab identifier (default: `id`)\n\nUse it with `<ui-component name=\"tabmenu\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<h2>Static tabs</h2>\n\n<ui-component name=\"tabmenu\" path=\"tab\">\n\t<ul>\n\t\t<li data-id=\"tab1\">Tab 1</li>\n\t\t<li data-id=\"tab2\">Tab 2</li>\n\t\t<li data-id=\"tab3\">Tab 3</li>\n\t</ul>\n</ui-component>\n\n<ui-bind path=\"tab\" config=\"show:value==='tab1'\" class=\"block hidden\">\n\tTAB 1 content\n</ui-bind>\n<ui-bind path=\"tab\" config=\"show:value==='tab2'\" class=\"block hidden\">\n\tTAB 2 content\n</ui-bind>\n<ui-bind path=\"tab\" config=\"show:value==='tab3'\" class=\"block hidden\">\n\tTAB 3 content\n</ui-bind>\n\n<br />\n\n<h2>Dynamic tabs</h2>\n<ui-component name=\"tabmenu\" path=\"tab\" config=\"datasource:tabs\">\n\t<script type=\"text/html\">\n\t\t<ul>\n\t\t\t{{ foreach m in value }}\n\t\t\t<li data-id=\"{{ m.id }}\">{{ m.name }}</li>\n\t\t\t{{ end }}\n\t\t</ul>\n\t</script>\n</ui-component>\n\n<ui-bind path=\"tab\" config=\"show:value==='tab1'\" class=\"block hidden\">\n\tDynamic TAB 1 content\n</ui-bind>\n<ui-bind path=\"tab\" config=\"show:value==='tab2'\" class=\"block hidden\">\n\tDynamic TAB 2 content\n</ui-bind>\n<ui-bind path=\"tab\" config=\"show:value==='tab3'\" class=\"block hidden\">\n\tDynamic TAB 3 content\n</ui-bind>\n\n<script>\n\tvar tab = 'tab1';\n\tvar tabs = [{ name: 'Tab 1', id: 'tab1' }, { name: 'Tab2', id: 'tab2' }, { name: 'Tab 3', id: 'tab3' }];\n</script>\n```"}],"knowledge":{"content_sha256":"356a21b1d81326e4755421f6f32b3ccc9a187c8632538d642678df7f8cd8836e","source_sha256":"b45d1f989d68514638e2ae2ca1e98206d8daaeeb27ad02baf3471ecd9e2084ba","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","TAB"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<h2>Static tabs</h2>\n\n<ui-component name=\"tabmenu\" path=\"tab\">\n\t<ul>\n\t\t<li data-id=\"tab1\">Tab 1</li>\n\t\t<li data-id=\"tab2\">Tab 2</li>\n\t\t<li data-id=\"tab3\">Tab 3</li>\n\t</ul>\n</ui-component>\n\n<ui-bind path=\"tab\" config=\"show:value==='tab1'\" class=\"block hidden\">\n\tTAB 1 content\n</ui-bind>\n<ui-bind path=\"tab\" config=\"show:value==='tab2'\" class=\"block hidden\">\n\tTAB 2 content\n</ui-bind>\n<ui-bind path=\"tab\" config=\"show:value==='tab3'\" class=\"block hidden\">\n\tTAB 3 content\n</ui-bind>\n\n<br />\n\n<h2>Dynamic tabs</h2>\n<ui-component name=\"tabmenu\" path=\"tab\" config=\"datasource:tabs\">\n\t<script type=\"text/html\">\n\t\t<ul>\n\t\t\t{{ foreach m in value }}\n\t\t\t<li data-id=\"{{ m.id }}\">{{ m.name }}</li>\n\t\t\t{{ end }}\n\t\t</ul>\n\t</script>\n</ui-component>\n\n<ui-bind path=\"tab\" config=\"show:value==='tab1'\" class=\"block hidden\">\n\tDynamic TAB 1 content\n</ui-bind>\n<ui-bind path=\"tab\" config=\"show:value==='tab2'\" class=\"block hidden\">\n\tDynamic TAB 2 content\n</ui-bind>\n<ui-bind path=\"tab\" config=\"show:value==='tab3'\" class=\"block hidden\">\n\tDynamic TAB 3 content\n</ui-bind>\n\n<script>\n\tvar tab = 'tab1';\n\tvar tabs = [{ name: 'Tab 1', id: 'tab1' }, { name: 'Tab2', id: 'tab2' }, { name: 'Tab 3', id: 'tab3' }];\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/tabs","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/tabs/","markdown_url":"https://forge.totaljs.es/en/components/tabs.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"2fe53860582db7cacd5a86e36648bfc1eec7bcec8fdf14b77a208389d611715c","checked_at":"2026-10-20T12:24:01.301Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Tabs","description":"j-Tabs options: icon, data-id, title, icon","messages":[{"role":"user","content":"j-Tabs\n\nj-Tabs options: icon, data-id, title, icon"},{"role":"assistant","content":"__Configuration__:\n\n- `icon {String}` a label icon for every panel (default: `empty`)\n\n### How to define panels?\n\n```html\n\n<ui-component name=\"tabs\" path=\"*tab\" class=\"invisible\">\n\n\t<section data-id=\"logs\" title=\"Logs\" data-icon=\"ti ti-history\">\n\n\t\tHTML CONTENT\n\n\t</section>\n\n\t<section data-id=\"parts\" title=\"Parts\" data-icon=\"ti ti-code-branch\">\n\n\t\tHTML CONTENT\n\n\t</section>\n\n</ui-component>\n```\n\n__Attributes__:\n\n- `data-id` __required__, it must contain an identifier of the panel\n- `title` __required__, it must contain a panel name\n- `icon` optional, it can contain Total.js Icon (default: `config.icon`)\n\nUse it with `<ui-component name=\"tabs\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"tabs\" path=\"*tab\" class=\"invisible\">\n\t<section data-id=\"logs\" title=\"Logs\" data-icon=\"ti ti-history\">\n\t\tLog\n\t</section>\n\t<section data-id=\"parts\" title=\"Parts\" data-icon=\"ti ti-code-branch\">\n\t\tParts\n\t</section>\n\t<section data-id=\"files\" title=\"Files\" data-icon=\"ti ti-copy\">\n\t\tFiles\n\t</section>\n\t<section data-id=\"relations\" title=\"Relations\" data-icon=\"ti ti-link\">\n\t\tRelations\n\t</section>\n\t<section data-id=\"maintenance\" title=\"Maintenance plan\" data-icon=\"ti ti-calendar\">\n\t\tMaintenance plan\n\t</section>\n</ui-component>\n\n<script>\n\tvar common = { tab: 'files' };\n</script>\n```"}],"knowledge":{"content_sha256":"ee2dcea90df616c20f5b94647872138a94e0d647102b4d67986e763bea48dbdb","source_sha256":"2fe53860582db7cacd5a86e36648bfc1eec7bcec8fdf14b77a208389d611715c","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","CONTENT","DEF","HTML","MIT","Total.js"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"tabs\" path=\"*tab\" class=\"invisible\">\n\t<section data-id=\"logs\" title=\"Logs\" data-icon=\"ti ti-history\">\n\t\tLog\n\t</section>\n\t<section data-id=\"parts\" title=\"Parts\" data-icon=\"ti ti-code-branch\">\n\t\tParts\n\t</section>\n\t<section data-id=\"files\" title=\"Files\" data-icon=\"ti ti-copy\">\n\t\tFiles\n\t</section>\n\t<section data-id=\"relations\" title=\"Relations\" data-icon=\"ti ti-link\">\n\t\tRelations\n\t</section>\n\t<section data-id=\"maintenance\" title=\"Maintenance plan\" data-icon=\"ti ti-calendar\">\n\t\tMaintenance plan\n\t</section>\n</ui-component>\n\n<script>\n\tvar common = { tab: 'files' };\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/tagger","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/tagger/","markdown_url":"https://forge.totaljs.es/en/components/tagger.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"74b5f4fc94b3f3b24f77d4574152106df5914e41cfcf34cf39a539f60c0359fd","checked_at":"2026-10-20T12:24:02.185Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Tagger","description":"Tagger can render items in the array and each item can be removed.","messages":[{"role":"user","content":"j-Tagger\n\nTagger can render items in the array and each item can be removed."},{"role":"assistant","content":"Tagger can render items in the array and each item can be removed.\n\n__Configuration__:\n\n- `text` {String} a property name for the label (default: `name`)\n- `fullwidth` {Boolean} enables fullwidth tags (default: `false`)\n\nUse it with `<ui-component name=\"tagger\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"width:300px\">\n\t<ui-component name=\"tagger\" path=\"mydata\"></ui-component>\n\t<br />\n</div>\n\n<script>\n\tvar mydata = [{ id: 1, name: 'Total.js' }, { id: 2, name: 'jComponent' }];\n</script>\n```"}],"knowledge":{"content_sha256":"4d1c04153eacff41b55dda4f4e155c5192e132490d5414a645bc0ab66034d39c","source_sha256":"74b5f4fc94b3f3b24f77d4574152106df5914e41cfcf34cf39a539f60c0359fd","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","Total.js"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"width:300px\">\n\t<ui-component name=\"tagger\" path=\"mydata\"></ui-component>\n\t<br />\n</div>\n\n<script>\n\tvar mydata = [{ id: 1, name: 'Total.js' }, { id: 2, name: 'jComponent' }];\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/template","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/template/","markdown_url":"https://forge.totaljs.es/en/components/template.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"1f7df3a77ff8a78bad91dba9fd506c372cf58d52f0075f2bd93158c24711ef7f","checked_at":"2026-10-20T12:24:03.060Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Template","description":"j-Template: when the model is null or undefined then the template will be hidden","messages":[{"role":"user","content":"j-Template\n\nj-Template: when the model is null or undefined then the template will be hidden"},{"role":"assistant","content":"- when the model is `null` or `undefined` then the template will be hidden\n\n__Configuration__:\n\n- `properties {String}` watch only specific properties in the string e.g. `name,email` (optional)\n\nUse it with `<ui-component name=\"template\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"template\" path=\"somemodel\">\n\t<script type=\"text/html\">\n\t\t<table class=\"table\">\n\t\t\t<tr>\n\t\t\t\t<td>Name</td>\n\t\t\t\t<td>{{ name }}</td>\n\t\t\t</tr>\n\t\t\t<tr>\n\t\t\t\t<td>Version</td>\n\t\t\t\t<td>{{ version }}</td>\n\t\t\t</tr>\n\t\t</table>\n\t</script>\n</ui-component>\n\n<script>\n\tvar somemodel = { name: 'jComponent', version: 'v2.1.3' };\n</script>\n```"}],"knowledge":{"content_sha256":"293a93cbf60aecb105c2e06e693e718518939bbee158fc63c76f85f4d389a7f0","source_sha256":"1f7df3a77ff8a78bad91dba9fd506c372cf58d52f0075f2bd93158c24711ef7f","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"template\" path=\"somemodel\">\n\t<script type=\"text/html\">\n\t\t<table class=\"table\">\n\t\t\t<tr>\n\t\t\t\t<td>Name</td>\n\t\t\t\t<td>{{ name }}</td>\n\t\t\t</tr>\n\t\t\t<tr>\n\t\t\t\t<td>Version</td>\n\t\t\t\t<td>{{ version }}</td>\n\t\t\t</tr>\n\t\t</table>\n\t</script>\n</ui-component>\n\n<script>\n\tvar somemodel = { name: 'jComponent', version: 'v2.1.3' };\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/templates","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/templates/","markdown_url":"https://forge.totaljs.es/en/components/templates.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"8a0dd7b851d21284b30277f31e69915e1646cd12d8453994eed34dac7e5a26a2","checked_at":"2026-10-20T12:24:03.795Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Templates","description":"Templates must be defined in <script type='text/html' data-name='NAMEOFTEMPLATE'>Tangular template</script> element. This component supports one special template called empty that will be visible if t","messages":[{"role":"user","content":"j-Templates\n\nTemplates must be defined in <script type='text/html' data-name='NAMEOFTEMPLATE'>Tangular template</script> element. This component supports one special template called empty that will be visible if t"},{"role":"assistant","content":"- multiple templates\n- drag & drop\n- custom scrollbars\n- context menu\n\n__Configuration__:\n\n- `template {String}` a path to the watching a template name\n- `scrollbar {Boolean}` uses a custom scrollbar (default: `true`)\n- `scrolltop {Boolean}` scrolls to top if the source is changed (default: `true`)\n- `visibleY {Boolean}` shows scrollbar Y (default: `true`)\n- `margin {Number}` a vertical margin (default: `0`)\n- `parent {String}` jQuery selector to parent container due to `scrollbar` (default: `auto`)\n- `contextmenu {String}` a path to the method `function(e)` if the user triggers context-menu\n- `emptyif {String}` a condition for showing of empty content (default: `!value || !value.length`)\n- `drop {String}` a path to the method `function(e, target, selel)`\n\t- the method is evaluated if the user drops some files, then `selel` is undefined\n\t- or the user drags & drops element with the `draggable` attribute (`selel` argument) into the `target` argument (target must contain `droppable` class)\n\nTemplates must be defined in `<script type=\"text/html\" data-name=\"NAME_OF_TEMPLATE\">Tangular template</script>` element. This component supports one special template called `empty` that will be visible if the `emptyif` option will be valid.\n\nUse it with `<ui-component name=\"templates\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div>\n\t<span data-name=\"list\" onclick=\"changetemplate(this)\" style=\"text-decoration:underline;cursor:pointer;margin-right:5px\">List</span>\n\t<span data-name=\"icons\" onclick=\"changetemplate(this)\" style=\"text-decoration:underline;cursor:pointer\">Icons</span>\n</div>\n<br />\n\n<div style=\"width:500px;height:300px;border:1px solid #E0E0E0\">\n\t<ui-component name=\"templates\" path=\"myfiles\" config=\"template:mytemplate;drop:template_drop\">\n\n\t\t<script type=\"text/html\" data-name=\"list\">\n\t\t\t<div class=\"list padding\">\n\t\t\t\t{{ foreach m in value }}\n\t\t\t\t\t<div draggable=\"true\"{{ if m.isfolder }} class=\"droppable\"{{ fi }}>{{ m.name }}</div>\n\t\t\t\t{{ end }}\n\t\t\t</div>\n\t\t</script>\n\n\t\t<script type=\"text/html\" data-name=\"icons\">\n\t\t\t<div class=\"icons padding\">\n\t\t\t\t{{ foreach m in value }}\n\t\t\t\t\t<span draggable=\"true\" class=\"mr5 {{ if m.isfolder }} droppable{{ fi }}\"><i class=\"{{ m.icon }} mr5\"></i>{{ m.name }}</span>\n\t\t\t\t{{ end }}\n\t\t\t</div>\n\t\t</script>\n\n\t</ui-component>\n\n</div>\n<br />\n<div><b>Without multiple templates:</b></div>\n<div style=\"width:500px;height:300px;border:1px solid #E0E0E0;margin-top:10px\">\n\t<ui-component name=\"templates\" path=\"myfiles\">\n\t\t<script type=\"text/html\">\n\t\t\t<div class=\"list padding\">\n\t\t\t\t{{ foreach m in value }}\n\t\t\t\t\t<div draggable=\"true\"{{ if m.isfolder }} class=\"droppable\"{{ fi }}>{{ m.name }}</div>\n\t\t\t\t{{ end }}\n\t\t\t</div>\n\t\t</script>\n\t</ui-component>\n</div>\n\n<script>\n\tvar myfiles = [{ icon: 'ti ti-folder', name: 'temp', isfolder: true }, { icon: 'ti ti-file', name: 'logo.png' }, { icon: 'ti ti-file', name: 'logo.svg' }];\n\tvar mytemplate = 'list';\n\n\tfunction changetemplate(el) {\n\t\tSET('mytemplate', el.getAttribute('data-name'));\n\t}\n\n\tfunction template_drop(e, target, item) {\n\n\t\tconsole.log(e, target, item);\n\t\t// @target {jQuery element}\n\n\t\tif (item) {\n\t\t\t// @item {jQuery element}\n\t\t} else {\n\t\t\tvar files = e.target.files || e.originalEvent.dataTransfer.files;\n\t\t}\n\t}\n\n</script>\n```"}],"knowledge":{"content_sha256":"e5f3357bc56d202555787c596f876f93207a56e17030a8c3f144706aba799bf1","source_sha256":"8a0dd7b851d21284b30277f31e69915e1646cd12d8453994eed34dac7e5a26a2","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","E0E0E0","MIT","NAME_OF_TEMPLATE","SET"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div>\n\t<span data-name=\"list\" onclick=\"changetemplate(this)\" style=\"text-decoration:underline;cursor:pointer;margin-right:5px\">List</span>\n\t<span data-name=\"icons\" onclick=\"changetemplate(this)\" style=\"text-decoration:underline;cursor:pointer\">Icons</span>\n</div>\n<br />\n\n<div style=\"width:500px;height:300px;border:1px solid #E0E0E0\">\n\t<ui-component name=\"templates\" path=\"myfiles\" config=\"template:mytemplate;drop:template_drop\">\n\n\t\t<script type=\"text/html\" data-name=\"list\">\n\t\t\t<div class=\"list padding\">\n\t\t\t\t{{ foreach m in value }}\n\t\t\t\t\t<div draggable=\"true\"{{ if m.isfolder }} class=\"droppable\"{{ fi }}>{{ m.name }}</div>\n\t\t\t\t{{ end }}\n\t\t\t</div>\n\t\t</script>\n\n\t\t<script type=\"text/html\" data-name=\"icons\">\n\t\t\t<div class=\"icons padding\">\n\t\t\t\t{{ foreach m in value }}\n\t\t\t\t\t<span draggable=\"true\" class=\"mr5 {{ if m.isfolder }} droppable{{ fi }}\"><i class=\"{{ m.icon }} mr5\"></i>{{ m.name }}</span>\n\t\t\t\t{{ end }}\n\t\t\t</div>\n\t\t</script>\n\n\t</ui-component>\n\n</div>\n<br />\n<div><b>Without multiple templates:</b></div>\n<div style=\"width:500px;height:300px;border:1px solid #E0E0E0;margin-top:10px\">\n\t<ui-component name=\"templates\" path=\"myfiles\">\n\t\t<script type=\"text/html\">\n\t\t\t<div class=\"list padding\">\n\t\t\t\t{{ foreach m in value }}\n\t\t\t\t\t<div draggable=\"true\"{{ if m.isfolder }} class=\"droppable\"{{ fi }}>{{ m.name }}</div>\n\t\t\t\t{{ end }}\n\t\t\t</div>\n\t\t</script>\n\t</ui-component>\n</div>\n\n<script>\n\tvar myfiles = [{ icon: 'ti ti-folder', name: 'temp', isfolder: true }, { icon: 'ti ti-file', name: 'logo.png' }, { icon: 'ti ti-file', name: 'logo.svg' }];\n\tvar mytemplate = 'list';\n\n\tfunction changetemplate(el) {\n\t\tSET('mytemplate', el.getAttribute('data-name'));\n\t}\n\n\tfunction template_drop(e, target, item) {\n\n\t\tconsole.log(e, target, item);\n\t\t// @target {jQuery element}\n\n\t\tif (item) {\n\t\t\t// @item {jQuery element}\n\t\t} else {\n\t\t\tvar files = e.target.files || e.originalEvent.dataTransfer.files;\n\t\t}\n\t}\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/textarea","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/textarea/","markdown_url":"https://forge.totaljs.es/en/components/textarea.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"572a3ad89e29f67b8a4a1c298130a64bbc7bd5f08c9596f3813c2dbb49cb6141","checked_at":"2026-10-20T12:24:04.680Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Textarea","description":"j-Textarea: NEW supports hidden scrollbar","messages":[{"role":"user","content":"j-Textarea\n\nj-Textarea: NEW supports hidden scrollbar"},{"role":"assistant","content":"- __NEW__ supports hidden scrollbar\n\n__Configuration__:\n\n- `required {Boolean}` (optional) enables \"required\" (default: `false`)\n- `icon {String}` (optional) icon for the label without `ti-` e.g. `home`, `cog`, etc.\n- `label {String}` (optional) label (default `innerHTML`)\n- `autofocus {Boolean}` (optional) focuses the input (default: `false`)\n- `placeholder {String}` (optional) adds a `placeholder` text into the input\n- `maxlength {Number}` (optional) sets a maximum length of chars\n- `disabled {Boolean}` (optional) disables this component\n- `height {Number}` (optional) a maximum height for the input\n- `error {String}` (optional) adds an `error` text under the input\n- `monospace {Boolean}` (optional) sets `monospace` font for the input (default: `false`)\n- `readonly {Boolean}` optional, default `false`\n- `scrollbar {Boolean}` optional, default `true`\n\nUse it with `<ui-component name=\"textarea\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div>\n\t<ui-component name=\"textarea\" path=\"form.body\" config=\"required:true;icon:pencil;scrollbar:false\">Body</ui-component>\n\t<br />\n\t<ui-component name=\"textarea\" path=\"form.note\" config=\"placeholder:Internal note\">Note</ui-component>\n</div>\n\n<script>\n\tvar form = {};\n\tform.body = 'Hello';\n\tform.note = '';\n</script>\n```"}],"knowledge":{"content_sha256":"44bbb92890034397c6335cc86a6ab60d9f97619303388625112fb77a3ea724b4","source_sha256":"572a3ad89e29f67b8a4a1c298130a64bbc7bd5f08c9596f3813c2dbb49cb6141","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div>\n\t<ui-component name=\"textarea\" path=\"form.body\" config=\"required:true;icon:pencil;scrollbar:false\">Body</ui-component>\n\t<br />\n\t<ui-component name=\"textarea\" path=\"form.note\" config=\"placeholder:Internal note\">Note</ui-component>\n</div>\n\n<script>\n\tvar form = {};\n\tform.body = 'Hello';\n\tform.note = '';\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/textbox","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/textbox/","markdown_url":"https://forge.totaljs.es/en/components/textbox.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"d1739f1243eb2c6e595e933e5aa16536ef524991b65c98bb8212675c4b14dbe0","checked_at":"2026-10-20T12:24:05.559Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Textbox","description":"j-Textbox: NEW innerlabel {Boolean} optional, enables inner label (default: true)","messages":[{"role":"user","content":"j-Textbox\n\nj-Textbox: NEW innerlabel {Boolean} optional, enables inner label (default: true)"},{"role":"assistant","content":"__Configuration__:\n\n- `type {String}` (optional) can be `email`, `phone`, `password`, `date`, `number`, `search`, `lower`, `upper` or empty (default)\n- `required {Boolean}` (optional) enables \"required\" (default: `false`)\n- `icon {String}` (optional) icon for label e.g. `home`, `cog`, etc.\n- `icon2 {String}` (optional) icon in the right box e.g. `home`, `cog`, etc.\n- `label {String}` (optional) label (default is HTML content)\n- `autofocus {Boolean}` (optional) focuses the input (default: `false`)\n- `align {String}` (optional) `left` (default), `right` or `center`\n- `autofill {Boolean}` (optional) enables the browser's autofill feature (default: `false`)\n- `placeholder {String}` (optional) adds a `placeholder` text into the input\n- `maxlength {Number}` (optional) sets a maximum length of chars\n- `minlength {Number}` (optional) sets a minimum length of chars\n- `validation {String}` (optional) a condition for `evaluation` e.g. `value.match(/[a-z]+/) !== null`\n- `format {String}` (optional) output formatting e.g. for dates (default: `yyyy-MM-dd`)\n- `increment {Boolean}` (optional) enables controls for incrementing of numbers (default: `false`)\n- `keypress {Boolean}` (optional) can disable real-time binding values (default: `true`)\n- `delay {Number}` (optional) can increase/decrease delay for real-time binding (default: `300` ms)\n- `disabled {Boolean}` (optional) disables this component\n- `error {String}` (optional) adds a `string` text under the input\n- `iconclick {String}` (optional) needs to contain a link to a function\n- `autocomplete {String}` (optional) needs to contain a link to a function, is triggered on the `focus` event\n- `spaces {Boolean}` (optional) enables spaces otherwise it removes them (default: `true`)\n- `readonly {Boolean}` optional, default `false`\n- __NEW__ `innerlabel {Boolean}` optional, enables inner label (default: `true`)\n\n__Interesting:__\n\n- `type:date` uses the `calendar` component\n\nUse it with `<ui-component name=\"textbox\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"width:400px\">\n\t<ui-component name=\"textbox\" path=\"form.name\" config=\"required:true;placeholder:Your name;icon2:pencil;error:Please enter your name\">Name</ui-component>\n\t<br />\n\t<ui-component name=\"textbox\" path=\"form.email\" config=\"required:true;type:email;autofill:email;icon:envelope;error:E-mail must be in format email@domain.com\">E-mail</ui-component>\n\t<br />\n\t<ui-component name=\"textbox\" path=\"form.password\" config=\"type:password\">Password</ui-component>\n\t<br />\n\t<ui-component name=\"textbox\" path=\"form.note\">Note</ui-component>\n\t<br />\n\t<ui-component name=\"textbox\" path=\"form.phone\" config=\"type:phone\">Phone</ui-component>\n\t<br />\n\t<ui-component name=\"textbox\" path=\"form.age\" config=\"type:number;increment:true\">Age</ui-component>\n\t<br />\n\t<ui-component name=\"textbox\" path=\"form.search\" config=\"type:search\">Search</ui-component>\n\t<hr />\n\t<h2>Inner label</h2>\n\t<ui-component name=\"textbox\" path=\"form.name\" config=\"required:true;innerlabel:true;placeholder:Your name;icon2:pencil;error:Please enter your name\">Name</ui-component>\n</div>\n\n<script>\n\tvar form = {};\n\tform.email = '@';\n\tform.age = 30;\n</script>\n```"}],"knowledge":{"content_sha256":"bfd0aed862812132366675bed7e4237f5fe8e87f543590157571139c2b850ee4","source_sha256":"d1739f1243eb2c6e595e933e5aa16536ef524991b65c98bb8212675c4b14dbe0","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","HTML","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"width:400px\">\n\t<ui-component name=\"textbox\" path=\"form.name\" config=\"required:true;placeholder:Your name;icon2:pencil;error:Please enter your name\">Name</ui-component>\n\t<br />\n\t<ui-component name=\"textbox\" path=\"form.email\" config=\"required:true;type:email;autofill:email;icon:envelope;error:E-mail must be in format email@domain.com\">E-mail</ui-component>\n\t<br />\n\t<ui-component name=\"textbox\" path=\"form.password\" config=\"type:password\">Password</ui-component>\n\t<br />\n\t<ui-component name=\"textbox\" path=\"form.note\">Note</ui-component>\n\t<br />\n\t<ui-component name=\"textbox\" path=\"form.phone\" config=\"type:phone\">Phone</ui-component>\n\t<br />\n\t<ui-component name=\"textbox\" path=\"form.age\" config=\"type:number;increment:true\">Age</ui-component>\n\t<br />\n\t<ui-component name=\"textbox\" path=\"form.search\" config=\"type:search\">Search</ui-component>\n\t<hr />\n\t<h2>Inner label</h2>\n\t<ui-component name=\"textbox\" path=\"form.name\" config=\"required:true;innerlabel:true;placeholder:Your name;icon2:pencil;error:Please enter your name\">Name</ui-component>\n</div>\n\n<script>\n\tvar form = {};\n\tform.email = '@';\n\tform.age = 30;\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/textboxlist","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/textboxlist/","markdown_url":"https://forge.totaljs.es/en/components/textboxlist.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"d7334c1960f5c447492d68c4d09355aef647aae10bd9d9e9ecbe50d300067478","checked_at":"2026-10-20T12:24:06.437Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-TextboxList","description":"j-TextboxList: NEW movable {Boolean} enables changing of position for existing items","messages":[{"role":"user","content":"j-TextboxList\n\nj-TextboxList: NEW movable {Boolean} enables changing of position for existing items"},{"role":"assistant","content":"__Configuration__:\n\n- `icon {String}` icon for label\n- `label {String}` label (default `innerHTML`)\n- `placeholder {String}` adds a `placeholder` text into the input\n- `maxlength {Number}` sets a maximum length of chars\n- `disabled {Boolean}` disables this component\n- `limit {Number}` sets a maximum length of lists\n- `error {String}` error message when a limit is reached (default `You reach the maximum limit`)\n- `required {Boolean}` whether a list is required (default `false`)\n- `type {String}` can be `email`, `url`, `currency`, `number`, `date`, `lower`, `upper`, `a-z` or empty (default)\n- __NEW__ `movable {Boolean}` enables changing of position for existing items\n\nUse it with `<ui-component name=\"textboxlist\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"textboxlist\" path=\"redirects\" config=\"maxlength:50;type:url;placeholder:e.g. www.totaljs.com and press enter;limit:5;error:Limit is 5 lists;icon:arrow-left\">Redirects</ui-component>\n<br>\n<ui-component name=\"textboxlist\" path=\"emails\" config=\"maxlength:50;type:email;placeholder:e.g. name@domain.com and press enter;limit:5;error:Limit is 5 lists;icon:envelope-o;\">Emails</ui-component>\n<br>\n<ui-component name=\"textboxlist\" path=\"empty\" config=\"maxlength:50;required:true;placeholder:e.g. Enter a value and press enter;limit:5;error:Limit is 5 lists;\">Required</ui-component>\n<br>\n<ui-component name=\"textboxlist\" path=\"empty\" config=\"maxlength:50;required:true;type:number;placeholder:e.g. Enter any number;limit:5;error:Limit is 5 lists;\">Required Numbers</ui-component>\n\n<script>\n    var redirects = ['www.totaljs.com'];\n    var emails = ['name@domain.com', 'invalidemailaddress'];\n    var empty = [];\n</script>\n```"}],"knowledge":{"content_sha256":"47684939c4f5efcc24d1a01594ac0e3a1f20e8f3d4f1512d4f2ab334ca7df025","source_sha256":"d7334c1960f5c447492d68c4d09355aef647aae10bd9d9e9ecbe50d300067478","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"textboxlist\" path=\"redirects\" config=\"maxlength:50;type:url;placeholder:e.g. www.totaljs.com and press enter;limit:5;error:Limit is 5 lists;icon:arrow-left\">Redirects</ui-component>\n<br>\n<ui-component name=\"textboxlist\" path=\"emails\" config=\"maxlength:50;type:email;placeholder:e.g. name@domain.com and press enter;limit:5;error:Limit is 5 lists;icon:envelope-o;\">Emails</ui-component>\n<br>\n<ui-component name=\"textboxlist\" path=\"empty\" config=\"maxlength:50;required:true;placeholder:e.g. Enter a value and press enter;limit:5;error:Limit is 5 lists;\">Required</ui-component>\n<br>\n<ui-component name=\"textboxlist\" path=\"empty\" config=\"maxlength:50;required:true;type:number;placeholder:e.g. Enter any number;limit:5;error:Limit is 5 lists;\">Required Numbers</ui-component>\n\n<script>\n    var redirects = ['www.totaljs.com'];\n    var emails = ['name@domain.com', 'invalidemailaddress'];\n    var empty = [];\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/time","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/time/","markdown_url":"https://forge.totaljs.es/en/components/time.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"50fbfb79724e66fd1aa3ecea22f9d2b0c068aae96d53d7a0a394e29b41989f02","checked_at":"2026-10-20T12:24:07.312Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Time","description":"This component renders a current time.","messages":[{"role":"user","content":"j-Time\n\nThis component renders a current time."},{"role":"assistant","content":"This component renders a current time.\n\n\n__Configuration__:\n\n- `icon {String}` icon (default: `ti ti-clock`)\n- `format {String}` default time format (default: `DEF.timeformat`)\n\nUse it with `<ui-component name=\"time\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"time\"></ui-component>\n```"}],"knowledge":{"content_sha256":"95178553bfe1bdf2fd1a99d3436255f5d4bd2189a9f162d83a82ff49c24ab028","source_sha256":"50fbfb79724e66fd1aa3ecea22f9d2b0c068aae96d53d7a0a394e29b41989f02","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"time\"></ui-component>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/timepicker","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/timepicker/","markdown_url":"https://forge.totaljs.es/en/components/timepicker.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"b63e5babca5074a111a79c0dbc7bf6182dfc29769a11eb7571a061bb9d2183de","checked_at":"2026-10-20T12:24:08.184Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-TimePicker","description":"The component must be called manually or works with j-Input component automatically. This component is a singleton.","messages":[{"role":"user","content":"j-TimePicker\n\nThe component must be called manually or works with j-Input component automatically. This component is a singleton."},{"role":"assistant","content":"- supports __dark mode__\n\n\n__Configuration__:\n\n- `seconds {Boolean}` optional, enables seconds (default: `false`)\n- `ampm {Boolean}` optional, enables 12 hours time format (default: `false`)\n\nThe component must be called manually or works with `j-Input` component automatically. This component is a singleton.\n\n- __important__ add the component under `<body>` element\n\n## Usage\n\n```javascript\nvar opt = {};\n\n// opt.offsetX {Number}    : adds X offset (default: 0)\n// opt.offsetY {Number}    : adds Y offset (default: 0)\n// opt.seconds {Boolean}   : shows seconds (default: false)\n// opt.ampm    {Boolean}   : shows 12 hours time format (default: false)\n// opt.align   {left|center|right} (default: left)\n\nopt.value = new Date();\n// or\n// opt.value = 'path.to.date' (in this case you don't need to declare \"opt.callback\")\n\nopt.element = YOUR_ELEMENT;\n\nopt.callback = function(date) {\n\tconsole.log(date);\n};\n\n// opt.close {Function} optional, this function can determine the closing of TimePicker\n\nSETTER('timepicker/show', opt);\n```\n\nUse it with `<ui-component name=\"timepicker\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<a href=\"#timepicker\">SHOW TIMEPICKER <ui-bind path=\"form.date\" config=\"html:value.format('HH\\:mm\\:ss')\"></ui-bind></a>\n<ui-component name=\"timepicker\"></ui-component>\n\n<script>\n\tvar form = {};\n\tform.date = new Date();\n\n\t$('a[href=\"#timepicker\"]').on('click', function(e) {\n\t\tvar opt = {};\n\t\topt.element = this;\n\t\topt.value = 'form.date';\n\t\topt.seconds = true;\n\t\tSETTER('timepicker/toggle', opt);\n\t});\n</script>\n```"}],"knowledge":{"content_sha256":"453c001d6268617294247689b41b0338e981e0a3dd038febb3b00c7d34abd521","source_sha256":"b63e5babca5074a111a79c0dbc7bf6182dfc29769a11eb7571a061bb9d2183de","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","SETTER","SHOW","TIMEPICKER","YOUR_ELEMENT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<a href=\"#timepicker\">SHOW TIMEPICKER <ui-bind path=\"form.date\" config=\"html:value.format('HH\\:mm\\:ss')\"></ui-bind></a>\n<ui-component name=\"timepicker\"></ui-component>\n\n<script>\n\tvar form = {};\n\tform.date = new Date();\n\n\t$('a[href=\"#timepicker\"]').on('click', function(e) {\n\t\tvar opt = {};\n\t\topt.element = this;\n\t\topt.value = 'form.date';\n\t\topt.seconds = true;\n\t\tSETTER('timepicker/toggle', opt);\n\t});\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/title","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/title/","markdown_url":"https://forge.totaljs.es/en/components/title.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"a5a04dc91f0850a63ff593776a86fa094c370400783dc3823164016215fc518f","checked_at":"2026-10-20T12:24:08.920Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Title","description":"j-Title: easy usage; singleton","messages":[{"role":"user","content":"j-Title\n\nj-Title: easy usage; singleton"},{"role":"assistant","content":"- singleton\n\n__Configuration__:\n\n- `separator {String}` a character for separation (default: `-`)\n- `name {String}` a name of the website (default: `document.title`)\n- `empty {String}` a string, which is filled, if there is no value in the path\n- `plus {Boolean}` adds a default title as a plus phrase (default: `true`)\n\n__Methods__:\n\n- `component.rename(value)`\n\nUse it with `<ui-component name=\"title\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Pavol Danko. Source: the Componentator components repository, MIT license.\n\n```html example.html\nTitle has been changed with the value defined in the <code>common.name</code>.\n\n<ui-component name=\"title\" path=\"common.name\"></ui-component>\n\n<script>\n\tvar common = { name: 'Page name' };\n</script>\n```"}],"knowledge":{"content_sha256":"d1a284aa788b61f01364da6bc4591357f0108369ab7318dc3333bab446e29ce8","source_sha256":"a5a04dc91f0850a63ff593776a86fa094c370400783dc3823164016215fc518f","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"Title has been changed with the value defined in the <code>common.name</code>.\n\n<ui-component name=\"title\" path=\"common.name\"></ui-component>\n\n<script>\n\tvar common = { name: 'Page name' };\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/togglebox","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/togglebox/","markdown_url":"https://forge.totaljs.es/en/components/togglebox.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"b691910a1951588bf02a5dcf199dce2d766b5cb25b99ca8ef8579f8ada12e066","checked_at":"2026-10-20T12:24:09.798Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-ToggleBox","description":"ToggleBox can re-order or hide items in Array. The component expects Array.","messages":[{"role":"user","content":"j-ToggleBox\n\nToggleBox can re-order or hide items in Array. The component expects Array."},{"role":"assistant","content":"ToggleBox can re-order or hide items in `Array`. The component expects `Array`.\n\n__Configuration__:\n\n- `dragdrop` {Boolean} Enables/Disables drag&drop (default: true)\n- `text` {String} A property name for text (it needs to be `String`, default: `name`)\n- `hidden` {String} A property for show/hide (it needs to be `Boolean`, default: `hidden`)\n- `disabled` {Boolean} Can disable the control (default: `false`)\n- `replace` {Boolean} Can replace the position of elements (default: `false`)\n\nUse it with `<ui-component name=\"togglebox\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"width:200px\">\n\t<ui-component name=\"togglebox\" path=\"items\"></ui-component>\n</div>\n\n<script>\n\tvar items = [{ name: 'Column 1', hidden: false }, { name: 'Column 2', hidden: true }, { name: 'Column 3', hidden: false }, { name: 'Column 4', hidden: false }, { name: 'Column 5', hidden: true }, { name: 'Column 6', hidden: false }, { name: 'Column 7', hidden: false }];\n</script>\n```"}],"knowledge":{"content_sha256":"5179ab7dddfb7390ae7180c922d7c8f2575e6a3c7ffde1cc5951b45598933d97","source_sha256":"b691910a1951588bf02a5dcf199dce2d766b5cb25b99ca8ef8579f8ada12e066","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"width:200px\">\n\t<ui-component name=\"togglebox\" path=\"items\"></ui-component>\n</div>\n\n<script>\n\tvar items = [{ name: 'Column 1', hidden: false }, { name: 'Column 2', hidden: true }, { name: 'Column 3', hidden: false }, { name: 'Column 4', hidden: false }, { name: 'Column 5', hidden: true }, { name: 'Column 6', hidden: false }, { name: 'Column 7', hidden: false }];\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/togglebutton","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/togglebutton/","markdown_url":"https://forge.totaljs.es/en/components/togglebutton.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"7bb6c80258d5340de5b863ebc90aa7abcf5aaa374d241bfbdafb9dbaa20671d4","checked_at":"2026-10-20T12:24:10.678Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-ToggleButton","description":"Icons can be in the form: circle or ti ti-circle.","messages":[{"role":"user","content":"j-ToggleButton\n\nIcons can be in the form: circle or ti ti-circle."},{"role":"assistant","content":"__Configuration__:\n\n- `required` {Boolean} optional, enables \"required\" (default: `false`)\n- `disabled` {Boolean} optional, disables this component\n- `icontrue` {String} optional, toggles FontAwesome icons if the value is `true`\n- `iconfalse` {String} optional, toggles FontAwesome icons if the value is `false`\n\nIcons can be in the form: `circle` or `ti ti-circle`.\n\nUse it with `<ui-component name=\"togglebutton\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div class=\"container\">\n\t<div class=\"row\">\n\t\t<div class=\"col-md-4\">\n\t\t\t<ui-component name=\"togglebutton\" path=\"form.darkmode\" config=\"icontrue:lightbulb\"></ui-component>\n\t\t</div>\n\t</div>\n</div>\n\n<script>\n\tvar form = {};\n\tform.darkmode = false;\n\tWATCH('form.darkmode', function(path, value) {\n\t\t$('body').tclass('ui-dark', value === true);\n\t});\n</script>\n```"}],"knowledge":{"content_sha256":"b7010e29f7780c6a476ef1ab11238348047f1bbfc1427fabe6392f4345b460d4","source_sha256":"7bb6c80258d5340de5b863ebc90aa7abcf5aaa374d241bfbdafb9dbaa20671d4","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","WATCH"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div class=\"container\">\n\t<div class=\"row\">\n\t\t<div class=\"col-md-4\">\n\t\t\t<ui-component name=\"togglebutton\" path=\"form.darkmode\" config=\"icontrue:lightbulb\"></ui-component>\n\t\t</div>\n\t</div>\n</div>\n\n<script>\n\tvar form = {};\n\tform.darkmode = false;\n\tWATCH('form.darkmode', function(path, value) {\n\t\t$('body').tclass('ui-dark', value === true);\n\t});\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/togglemore","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/togglemore/","markdown_url":"https://forge.totaljs.es/en/components/togglemore.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"45f08f857ac92c09a7bf843e854ae44d2fda785c5cc83aeca0da762ce6960b1e","checked_at":"2026-10-20T12:24:11.560Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-ToggleMore","description":"Is a simple component to toggle content.","messages":[{"role":"user","content":"j-ToggleMore\n\nIs a simple component to toggle content."},{"role":"assistant","content":"Is a simple component to toggle content.\n\n- You can change CSS for your needs.\n\n\n__Methods__:\n\n- `component.toggle(force)` can toggle the component. The parameter `force` is optional and will force the value. eg. `component.toggle(true);` will show `ui-togglemore-after`.\n\n__Good to know__:\n\nThe content of the `j-ToggleMore` is divided into 2 parts: `before` and `after`. Each part must be defined. The component adds CSS classes to each part, for example first `div` will contain `ui-togglemore-before` and the second `ui-togglemore-after`.\nTo avoid \"blink\" add class `hidden` to the second `div`.\n\n---\n\n```html\n<ui-component name=\"togglemore\">\n\t<div>\n\t\tContent before\n\t</div>\n\t<div class=\"hidden\">\n\t\tContent after\n\t</div>\n</ui-component>\n```\n\nUse it with `<ui-component name=\"togglemore\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Denis Granec. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"width: 400px\">\n\t<ui-component name=\"togglemore\">\n\t\t<div>\n\t\t\tBEFORE long text\n\t\t</div>\n\t\t<div class=\"hidden\">\n\t\t\tAFTER Lorem ipsum dolor sit amet, consectetur adipisicing elit. Assumenda eligendi pariatur perferendis expedita tempora, deserunt sunt, quas similique velit quo, harum odio explicabo ea dicta aliquam ipsa, veniam vitae nemo.\n\t\t</div>\n\t</ui-component>\n</div>\n```"}],"knowledge":{"content_sha256":"f6ebdec8fd8072ee9e42be0e58bf52647868133afa42dc70facb2bfe9e48d187","source_sha256":"45f08f857ac92c09a7bf843e854ae44d2fda785c5cc83aeca0da762ce6960b1e","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["AFTER","BEFORE","CDN","CSS","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"width: 400px\">\n\t<ui-component name=\"togglemore\">\n\t\t<div>\n\t\t\tBEFORE long text\n\t\t</div>\n\t\t<div class=\"hidden\">\n\t\t\tAFTER Lorem ipsum dolor sit amet, consectetur adipisicing elit. Assumenda eligendi pariatur perferendis expedita tempora, deserunt sunt, quas similique velit quo, harum odio explicabo ea dicta aliquam ipsa, veniam vitae nemo.\n\t\t</div>\n\t</ui-component>\n</div>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/tooltip","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/tooltip/","markdown_url":"https://forge.totaljs.es/en/components/tooltip.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"50d1328769653a36f10bdedf58aa55ef0701570cff673e2979e5409dbf2b5c2b","checked_at":"2026-10-20T12:24:12.429Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Tooltip","description":"j-Tooltip: singleton; universal usage","messages":[{"role":"user","content":"j-Tooltip\n\nj-Tooltip: singleton; universal usage"},{"role":"assistant","content":"- singleton\n- universal usage\n- __NEW__ hide (`SETTER('tooltip', 'hide', [force])`) now supports the `force` parameter for instant hide.\n\n__METHODS__\n\n```javascript\nvar opt = {};\n\n// opt.offsetX {Number}    : adds X offset (default: 0)\n// opt.offsetY {Number}    : adds Y offset (default: 0)\n// opt.align {String}      : align `bottom` (default), `top`, `left` or `right`\n// opt.center {Boolean}    : centered position for `left` and `right` align (default: false)\n// opt.timeout {Number}    : hide timeout (in milliseconds), default: `undefined`\n\nopt.element = YOUR_ELEMENT;\nopt.html = 'YOUR_HTML_CONTENT';\n\nSETTER('tooltip', 'show', opt);\n```\n\nUse it with `<ui-component name=\"tooltip\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"tooltip\"></ui-component>\n\n<div style=\"padding:50px\">\n\t<div class=\"tooltip\" data-title=\"Lorem ipsum dolor sit amet 1.\" style=\"text-align:center;background-color:#F0F0F0;padding:10px 0;\">HOVER ME 1</div>\n\t<br />\n\t<br />\n\t<div class=\"tooltip\" data-title=\"Lorem ipsum dolor sit amet, consectetur adipisicing elit. Voluptas, expedita.\" style=\"text-align:center;background-color:#F0F0F0;padding:10px 0;\">HOVER ME 2</div>\n</div>\n\n<script>\n\t$(document).on('mouseenter mouseleave', '.tooltip', function(e) {\n\t\tif (e.type === 'mouseenter') {\n\t\t\tvar el = $(this);\n\t\t\tvar opt = {};\n\t\t\topt.element = el;\n\t\t\topt.html = el.attrd('title');\n\t\t\topt.align = 'bottom';\n\t\t\topt.timeout = 3000;\n\t\t\tSETTER('tooltip', 'show', opt);\n\t\t} else\n\t\t\tSETTER('tooltip', 'hide');\n\t});\n</script>\n```"}],"knowledge":{"content_sha256":"b642a5865b590bea75ca02e6683d626c6e4190d1e4dc0190b9b3c28109923826","source_sha256":"50d1328769653a36f10bdedf58aa55ef0701570cff673e2979e5409dbf2b5c2b","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","F0F0F0","HOVER","MIT","SETTER","YOUR_ELEMENT","YOUR_HTML_CONTENT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"tooltip\"></ui-component>\n\n<div style=\"padding:50px\">\n\t<div class=\"tooltip\" data-title=\"Lorem ipsum dolor sit amet 1.\" style=\"text-align:center;background-color:#F0F0F0;padding:10px 0;\">HOVER ME 1</div>\n\t<br />\n\t<br />\n\t<div class=\"tooltip\" data-title=\"Lorem ipsum dolor sit amet, consectetur adipisicing elit. Voluptas, expedita.\" style=\"text-align:center;background-color:#F0F0F0;padding:10px 0;\">HOVER ME 2</div>\n</div>\n\n<script>\n\t$(document).on('mouseenter mouseleave', '.tooltip', function(e) {\n\t\tif (e.type === 'mouseenter') {\n\t\t\tvar el = $(this);\n\t\t\tvar opt = {};\n\t\t\topt.element = el;\n\t\t\topt.html = el.attrd('title');\n\t\t\topt.align = 'bottom';\n\t\t\topt.timeout = 3000;\n\t\t\tSETTER('tooltip', 'show', opt);\n\t\t} else\n\t\t\tSETTER('tooltip', 'hide');\n\t});\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/touchmove","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/touchmove/","markdown_url":"https://forge.totaljs.es/en/components/touchmove.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"549d01a25e255530f84364bf0ce5606e4bbf73281d64d9c32cc7542d2ac04394","checked_at":"2026-10-20T12:24:13.149Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-TouchMove","description":"Simple touchmove component for easy touch navigation next/prev/up/down.","messages":[{"role":"user","content":"j-TouchMove\n\nSimple touchmove component for easy touch navigation next/prev/up/down."},{"role":"assistant","content":"Simple `touchmove` component for easy touch navigation next/prev/up/down.\n\nUse it with `<ui-component name=\"touchmove\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"touchmove\" config=\"prev:goPrev;next:goNext;up:goUp;down:goDown\">\n\t<br />\n\t<br />\n\t<br />\n\tTRY TO TAP\n\t<br />\n\t<br />\n\t<br />\n</ui-component>\n\n<script>\n\tfunction goPrev() {\n\t\talert('Prev');\n\t}\n\tfunction goNext() {\n\t\talert('Next');\n\t}\n\tfunction goUp() {\n\t\talert('Up');\n\t}\n\tfunction goDown() {\n\t\talert('Down');\n\t}\n</script>\n```"}],"knowledge":{"content_sha256":"bc0aa851e72afc5ec9660215cf67ba31052fa87d94916289664e7e4295577e53","source_sha256":"549d01a25e255530f84364bf0ce5606e4bbf73281d64d9c32cc7542d2ac04394","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","TAP","TRY"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"touchmove\" config=\"prev:goPrev;next:goNext;up:goUp;down:goDown\">\n\t<br />\n\t<br />\n\t<br />\n\tTRY TO TAP\n\t<br />\n\t<br />\n\t<br />\n</ui-component>\n\n<script>\n\tfunction goPrev() {\n\t\talert('Prev');\n\t}\n\tfunction goNext() {\n\t\talert('Next');\n\t}\n\tfunction goUp() {\n\t\talert('Up');\n\t}\n\tfunction goDown() {\n\t\talert('Down');\n\t}\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/tour","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/tour/","markdown_url":"https://forge.totaljs.es/en/components/tour.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"0e9abcd9446834833c87b5e9ff21b2b99d1c3ccd2a9f3e031d62bb82f4a9992c","checked_at":"2026-10-20T12:24:14.939Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Tour","description":"IMPORTANT: Put this component on the bottom of the body element","messages":[{"role":"user","content":"j-Tour\n\nIMPORTANT: Put this component on the bottom of the body element"},{"role":"assistant","content":"- Supports keyboard arrows\n- Supports `tab` key\n- Supports `esc` key\n\n__IMPORTANT: Put this component on the bottom of the body element__\n\n__Configuration__:\n\n- `escape` {Boolean} toggle possibility to hide Tour with `esc` key (default: `true`)\n- `skiptext` {String} a text of the `skip` button (default: `Skip`)\n- `nexttext` {String} a text of the `next` button (default: `Next`)\n- `backtext` {String} a text of the `back` button (default: `Back`)\n- `endtext` {String} a text of the `end` button (default: `End`)\n- `datasource` {String} path to data-source\n- `bghide` {Number} toggle possibility to hide Tour by clicking on the background (default: `false`)\n- `hide` {String} path to method `function(type)` (type - 0: by PATH (e.g. null) 1: end button 2: skip button 3: `esc` key 4: click on bg)\n\n```javascript\n// datasource\nvar example = [\n\t{\n\t\ttarget: '.test1',\n\t\ttitle: 'My title 1',\n\t\ttext: 'Message 1'\n\t},\n\t{\n\t\ttarget: '.test2',\n\t\ttitle: 'My title 2',\n\t\ttext: 'Message with offset',\n\t\toffsetY: 10\n\t}\n];\n```\n\n__PATH is the actual tour `page`__\n\nUse it with `<ui-component name=\"tour\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Denis Granec. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div class=\"row\">\n\t<div class=\"col-md-8 col-md-offset-2\">\n\n\t\t<h1 class=\"test1\">My First Web Page</h1>\n\n\t\t<h2>Headings Are Great Fun</h2>\n\t\t<p>This is my first <b>paragraph</b> in my new <i>webpage</i>. This is going to be great. I am so excited I can hardly contain <b>myself</b>. Don't you just love paragraphs? I find them very useful.</p>\n\n\t\t<h2>Web Pages Are Exciting Too</h2>\n\t\t<p>Yes, that's right - web pages can be a lot of fun. Learning how to create web pages is easy and <b>entertaining</b>. This is my second <i>paragraph</i>. I hope you like it.</p>\n\n\t\t<a class=\"test7\" href=\"https://www.totalavengers.com\" target=\"_blank\">Total Avengers</a>\n\n\t\t<ul class=\"test2\">\n\t\t\t<li>Apples</li>\n\t\t\t<li>Bananas</li>\n\t\t\t<li>Pears</li>\n\t\t\t<li>Oranges</li>\n\t\t\t<li>Grapes</li>\n\t\t</ul>\n\n\t\t<blockquote  class=\"test7\">\"A designer knows he has achieved perfection not when there is nothing left to add, but when there is nothing left to take away.\"\n\t\t - Saint Exupery</blockquote>\n\n\t\t<hr />\n\n\t\t<img class=\"test3\" src=\"https://placegoat.com/300/380\" />\n\n\t\t<div>This is a DIV container</div>\n\t\t<h1 class=\"test4\">My First Web Page</h1>\n\n\t\t<h2>Headings Are Great Fun</h2>\n\t\t<p>This is my first <b>paragraph</b> in my new <i>webpage</i>. This is going to be great. I am so excited I can hardly contain <b>myself</b>. Don't you just love paragraphs? I find them very useful. </p>\n\n\t\t<h2>Web Pages Are Exciting Too</h2>\n\t\t<p class=\"test5\">Yes, that's right - web pages can be a lot of fun. Learning how to create web pages is easy and <b>entertaining</b>. This is my second <i>paragraph</i>. I hope you like it.</p>\n\n\t\t<a href=\"https://www.totalavengers.com\">Total Avengers</a>\n\n\t\t<ul>\n\t\t\t<li>Apples</li>\n\t\t\t<li>Bananas</li>\n\t\t\t<li>Pears</li>\n\t\t\t<li>Oranges</li>\n\t\t\t<li>Grapes</li>\n\t\t</ul>\n\n\t\t<blockquote>\"A designer knows he has achieved perfection not when there is nothing left to add, but when there is nothing left to take away.\"\n\t\t - Saint Exupery</blockquote>\n\n\t\t<hr />\n\n\t\t<img src=\"https://placegoat.com/300/380\" />\n\n\t\t<div>This is a DIV container</div>\n\t\t<h1>My First Web Page</h1>\n\n\t\t<h2 class=\"test6\">Headings Are Great Fun</h2>\n\t\t<p>This is my first <b>paragraph</b> in my new <i>webpage</i>. This is going to be great. I am so excited I can hardly contain <b>myself</b>. Don't you just love paragraphs? I find them very useful. </p>\n\n\t\t<h2>Web Pages Are Exciting Too</h2>\n\t\t<p>Yes, that's right - web pages can be a lot of fun. Learning how to create web pages is easy and <b>entertaining</b>. This is my second <i>paragraph</i>. I hope you like it.</p>\n\n\t\t<a href=\"https://www.totalavengers.com\">Total Avengers</a>\n\n\t\t<ul>\n\t\t\t<li>Apples</li>\n\t\t\t<li>Bananas</li>\n\t\t\t<li>Pears</li>\n\t\t\t<li>Oranges</li>\n\t\t\t<li>Grapes</li>\n\t\t</ul>\n\n\t\t<blockquote>\"A designer knows he has achieved perfection not when there is nothing left to add, but when there is nothing left to take away.\"\n\t\t - Saint Exupery</blockquote>\n\n\t\t<hr />\n\n\t\t<img src=\"https://placegoat.com/300/380\" />\n\n\t\t<div>This is a DIV container</div>\n\t\t<h1>My First Web Page</h1>\n\n\t\t<h2>Headings Are Great Fun</h2>\n\t\t<p>This is my first <b>paragraph</b> in my new <i>webpage</i>. This is going to be great. I am so excited I can hardly contain <b>myself</b>. Don't you just love paragraphs? I find them very useful. </p>\n\n\t\t<h2>Web Pages Are Exciting Too</h2>\n\t\t<p>Yes, that's right - web pages can be a lot of fun. Learning how to create web pages is easy and <b>entertaining</b>. This is my second <i>paragraph</i>. I hope you like it.</p>\n\n\t\t<a href=\"https://www.totalavengers.com\">Total Avengers</a>\n\n\t\t<ul class=\"test8\">\n\t\t\t<li>Apples</li>\n\t\t\t<li>Bananas</li>\n\t\t\t<li>Pears</li>\n\t\t\t<li>Oranges</li>\n\t\t\t<li>Grapes</li>\n\t\t</ul>\n\n\t\t<blockquote>\"A designer knows he has achieved perfection not when there is nothing left to add, but when there is nothing left to take away.\"\n\t\t - Saint Exupery</blockquote>\n\n\t\t<hr />\n\n\t\t<img src=\"https://placegoat.com/300/380\" />\n\t\t<div class=\"test9\">This is a DIV container</div>\n\t</div>\n</div>\n\n\n<ui-component name=\"tour\" path=\"page\" config=\"datasource:example\"></ui-component>\n\n<script>\n\tvar example = [\n\t\t{\n\t\t\ttarget: '.test1',\n\t\t\ttitle: 'My title 1',\n\t\t\ttext: 'Welcome on Componentator.com'\n\t\t},\n\t\t{\n\t\t\ttarget: '.test2',\n\t\t\ttitle: 'My title 2',\n\t\t\ttext: 'Message with offset',\n\t\t\toffsetY: 10\n\t\t},\n\t\t{\n\t\t\ttarget: '.test3',\n\t\t\ttitle: 'My title 3',\n\t\t\ttext: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Aliquet enim tortor at auctor urna. Id diam maecenas ultricies mi eget mauris pharetra et.'\n\t\t},\n\t\t{\n\t\t\ttarget: '.test4',\n\t\t\ttitle: 'My title 4',\n\t\t\ttext: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Aliquet enim tortor at auctor urna. Id diam maecenas ultricies mi eget mauris pharetra et.'\n\t\t},\n\t\t{\n\t\t\ttarget: '.test5',\n\t\t\ttitle: 'My title 5',\n\t\t\ttext: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Aliquet enim tortor at auctor urna. Id diam maecenas ultricies mi eget mauris pharetra et.'\n\t\t},\n\t\t{\n\t\t\ttarget: '.test6',\n\t\t\ttitle: 'My title 6',\n\t\t\ttext: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Aliquet enim tortor at auctor urna. Id diam maecenas ultricies mi eget mauris pharetra et.'\n\t\t},\n\t\t{\n\t\t\ttarget: '.test7',\n\t\t\ttitle: 'My title 7',\n\t\t\ttext: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Aliquet enim tortor at auctor urna. Id diam maecenas ultricies mi eget mauris pharetra et.'\n\t\t},\n\t\t{\n\t\t\ttarget: '.test8',\n\t\t\ttitle: 'My title 8',\n\t\t\ttext: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Aliquet enim tortor at auctor urna. Id diam maecenas ultricies mi eget mauris pharetra et.'\n\t\t},\n\t\t{\n\t\t\ttarget: '.test9',\n\t\t\ttitle: 'My title 9',\n\t\t\ttext: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Aliquet enim tortor at auctor urna. Id diam maecenas ultricies mi eget mauris pharetra et.'\n\t\t}\n\t];\n\n\tsetTimeout(function() {\n\t\tSET('page', 0);\n\t}, 1000);\n</script>\n```"}],"knowledge":{"content_sha256":"3d1894fc358d7d07d376e6536140f75d995603a6e5c0e61473b1afd5074c2d3b","source_sha256":"0e9abcd9446834833c87b5e9ff21b2b99d1c3ccd2a9f3e031d62bb82f4a9992c","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","DIV","MIT","PATH","SET"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div class=\"row\">\n\t<div class=\"col-md-8 col-md-offset-2\">\n\n\t\t<h1 class=\"test1\">My First Web Page</h1>\n\n\t\t<h2>Headings Are Great Fun</h2>\n\t\t<p>This is my first <b>paragraph</b> in my new <i>webpage</i>. This is going to be great. I am so excited I can hardly contain <b>myself</b>. Don't you just love paragraphs? I find them very useful.</p>\n\n\t\t<h2>Web Pages Are Exciting Too</h2>\n\t\t<p>Yes, that's right - web pages can be a lot of fun. Learning how to create web pages is easy and <b>entertaining</b>. This is my second <i>paragraph</i>. I hope you like it.</p>\n\n\t\t<a class=\"test7\" href=\"https://www.totalavengers.com\" target=\"_blank\">Total Avengers</a>\n\n\t\t<ul class=\"test2\">\n\t\t\t<li>Apples</li>\n\t\t\t<li>Bananas</li>\n\t\t\t<li>Pears</li>\n\t\t\t<li>Oranges</li>\n\t\t\t<li>Grapes</li>\n\t\t</ul>\n\n\t\t<blockquote  class=\"test7\">\"A designer knows he has achieved perfection not when there is nothing left to add, but when there is nothing left to take away.\"\n\t\t - Saint Exupery</blockquote>\n\n\t\t<hr />\n\n\t\t<img class=\"test3\" src=\"https://placegoat.com/300/380\" />\n\n\t\t<div>This is a DIV container</div>\n\t\t<h1 class=\"test4\">My First Web Page</h1>\n\n\t\t<h2>Headings Are Great Fun</h2>\n\t\t<p>This is my first <b>paragraph</b> in my new <i>webpage</i>. This is going to be great. I am so excited I can hardly contain <b>myself</b>. Don't you just love paragraphs? I find them very useful. </p>\n\n\t\t<h2>Web Pages Are Exciting Too</h2>\n\t\t<p class=\"test5\">Yes, that's right - web pages can be a lot of fun. Learning how to create web pages is easy and <b>entertaining</b>. This is my second <i>paragraph</i>. I hope you like it.</p>\n\n\t\t<a href=\"https://www.totalavengers.com\">Total Avengers</a>\n\n\t\t<ul>\n\t\t\t<li>Apples</li>\n\t\t\t<li>Bananas</li>\n\t\t\t<li>Pears</li>\n\t\t\t<li>Oranges</li>\n\t\t\t<li>Grapes</li>\n\t\t</ul>\n\n\t\t<blockquote>\"A designer knows he has achieved perfection not when there is nothing left to add, but when there is nothing left to take away.\"\n\t\t - Saint Exupery</blockquote>\n\n\t\t<hr />\n\n\t\t<img src=\"https://placegoat.com/300/380\" />\n\n\t\t<div>This is a DIV container</div>\n\t\t<h1>My First Web Page</h1>\n\n\t\t<h2 class=\"test6\">Headings Are Great Fun</h2>\n\t\t<p>This is my first <b>paragraph</b> in my new <i>webpage</i>. This is going to be great. I am so excited I can hardly contain <b>myself</b>. Don't you just love paragraphs? I find them very useful. </p>\n\n\t\t<h2>Web Pages Are Exciting Too</h2>\n\t\t<p>Yes, that's right - web pages can be a lot of fun. Learning how to create web pages is easy and <b>entertaining</b>. This is my second <i>paragraph</i>. I hope you like it.</p>\n\n\t\t<a href=\"https://www.totalavengers.com\">Total Avengers</a>\n\n\t\t<ul>\n\t\t\t<li>Apples</li>\n\t\t\t<li>Bananas</li>\n\t\t\t<li>Pears</li>\n\t\t\t<li>Oranges</li>\n\t\t\t<li>Grapes</li>\n\t\t</ul>\n\n\t\t<blockquote>\"A designer knows he has achieved perfection not when there is nothing left to add, but when there is nothing left to take away.\"\n\t\t - Saint Exupery</blockquote>\n\n\t\t<hr />\n\n\t\t<img src=\"https://placegoat.com/300/380\" />\n\n\t\t<div>This is a DIV container</div>\n\t\t<h1>My First Web Page</h1>\n\n\t\t<h2>Headings Are Great Fun</h2>\n\t\t<p>This is my first <b>paragraph</b> in my new <i>webpage</i>. This is going to be great. I am so excited I can hardly contain <b>myself</b>. Don't you just love paragraphs? I find them very useful. </p>\n\n\t\t<h2>Web Pages Are Exciting Too</h2>\n\t\t<p>Yes, that's right - web pages can be a lot of fun. Learning how to create web pages is easy and <b>entertaining</b>. This is my second <i>paragraph</i>. I hope you like it.</p>\n\n\t\t<a href=\"https://www.totalavengers.com\">Total Avengers</a>\n\n\t\t<ul class=\"test8\">\n\t\t\t<li>Apples</li>\n\t\t\t<li>Bananas</li>\n\t\t\t<li>Pears</li>\n\t\t\t<li>Oranges</li>\n\t\t\t<li>Grapes</li>\n\t\t</ul>\n\n\t\t<blockquote>\"A designer knows he has achieved perfection not when there is nothing left to add, but when there is nothing left to take away.\"\n\t\t - Saint Exupery</blockquote>\n\n\t\t<hr />\n\n\t\t<img src=\"https://placegoat.com/300/380\" />\n\t\t<div class=\"test9\">This is a DIV container</div>\n\t</div>\n</div>\n\n\n<ui-component name=\"tour\" path=\"page\" config=\"datasource:example\"></ui-component>\n\n<script>\n\tvar example = [\n\t\t{\n\t\t\ttarget: '.test1',\n\t\t\ttitle: 'My title 1',\n\t\t\ttext: 'Welcome on Componentator.com'\n\t\t},\n\t\t{\n\t\t\ttarget: '.test2',\n\t\t\ttitle: 'My title 2',\n\t\t\ttext: 'Message with offset',\n\t\t\toffsetY: 10\n\t\t},\n\t\t{\n\t\t\ttarget: '.test3',\n\t\t\ttitle: 'My title 3',\n\t\t\ttext: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Aliquet enim tortor at auctor urna. Id diam maecenas ultricies mi eget mauris pharetra et.'\n\t\t},\n\t\t{\n\t\t\ttarget: '.test4',\n\t\t\ttitle: 'My title 4',\n\t\t\ttext: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Aliquet enim tortor at auctor urna. Id diam maecenas ultricies mi eget mauris pharetra et.'\n\t\t},\n\t\t{\n\t\t\ttarget: '.test5',\n\t\t\ttitle: 'My title 5',\n\t\t\ttext: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Aliquet enim tortor at auctor urna. Id diam maecenas ultricies mi eget mauris pharetra et.'\n\t\t},\n\t\t{\n\t\t\ttarget: '.test6',\n\t\t\ttitle: 'My title 6',\n\t\t\ttext: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Aliquet enim tortor at auctor urna. Id diam maecenas ultricies mi eget mauris pharetra et.'\n\t\t},\n\t\t{\n\t\t\ttarget: '.test7',\n\t\t\ttitle: 'My title 7',\n\t\t\ttext: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Aliquet enim tortor at auctor urna. Id diam maecenas ultricies mi eget mauris pharetra et.'\n\t\t},\n\t\t{\n\t\t\ttarget: '.test8',\n\t\t\ttitle: 'My title 8',\n\t\t\ttext: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Aliquet enim tortor at auctor urna. Id diam maecenas ultricies mi eget mauris pharetra et.'\n\t\t},\n\t\t{\n\t\t\ttarget: '.test9',\n\t\t\ttitle: 'My title 9',\n\t\t\ttext: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Aliquet enim tortor at auctor urna. Id diam maecenas ultricies mi eget mauris pharetra et.'\n\t\t}\n\t];\n\n\tsetTimeout(function() {\n\t\tSET('page', 0);\n\t}, 1000);\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/tree","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/tree/","markdown_url":"https://forge.totaljs.es/en/components/tree.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"ccad3265f3896fc45da65a2d8d2800250c9590d275b7f5bdd73d97a20f8fcb4f","checked_at":"2026-10-20T12:24:15.834Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Tree","description":"Setter automatically extends each object by adding .$pointer property with a tree index.","messages":[{"role":"user","content":"j-Tree\n\nSetter automatically extends each object by adding .$pointer property with a tree index."},{"role":"assistant","content":"__Configuration__:\n\n- `first {Boolean}` selects the first tree item, default: `true` (optional)\n- `exec {String}` __required__: link to a function, this function is executed when the node is selected in the tree\n- `autoreset {Boolean}` resets state when datas-source is changed (optional, default: `false`)\n- `selected {String}` selected class (optional, default: `selected`)\n- `checked {String}` optional, a path to `function(arr, component)` or path to `property`\n- `checkednested {Boolean}` optional, checks all nested items (default: `true`)\n- `upload {String}` optional, a path to `function(item, files)` (enables drag & drop files)\n- `dragdrop {String}` optional, a path to `function(item, target_item, itemel, targetel)` (enables drag & drop items)\n- `options {String}` optional, a path to `function(item, el)` enables a small button for an inline context menu for each item\n- `rename {String}` optional, a path to `function(item, newname, fn_accept(true))`\n- `unselectexpand {Boolean}` optional, can `unselect` selected item when the user clicks on the expandable item (default: `false`)\n- `reselect {Boolean}` optional, evaluates `exec` again if the tree is refreshed (default: `false`)\n- `selectexpand {Boolean}` optional, enables adding `selected` class to expandable items (default: `false`)\n- `iconoptions {String}` icon to options (default: `ti ti-ellipsis-h`)\n- `expanded {Boolean}` expands all children (default: `false`)\n- __NEW__: `contextmenu {String}` optional, a path to `function(item, el)` enables mouse contextmenu\n- __NEW__: `key {String}` a value can be used as `index` in below methods (default: `path`)\n\n__Data structure__:\n\n- Object Array\n- Object has to contain `name:String` and `children:[another similar object]` or `children:null` (with no children)\n- Object item can contain `..., icon: 'home' }`, it can change a file icon\n- Object item can contain `..., classname: 'css_custom_class' }`, it adds a CSS class onto the \"label\" element\n- Object item can contain `..., html: 'RAW_HTML'` {String} renders HTML tags instead of `name` field\n\n__Methods__:\n\n- `component.expand([index])` - expands `index` or `all` (with no defined `index`) nodes\n- `component.collapse([index])` - collapses `index` or `all` (with no defined `index`) nodes\n- `component.clear()` - clears a cache for refreshing the source\n- `component.select(index)` - selects node\n- `component.unselect()` - unselects node\n- `component.rename(index)` - shows `input` for name renaming (it works with `config.rename`)\n\n__Good to know__:\n\nSetter automatically extends each object by adding `.$pointer` property with a tree index.\n\nUse it with `<ui-component name=\"tree\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\t.ui-tree { border: 1px solid #E0E0E0; }\n\t.ui-dark .ui-tree { border: 1px solid #404040; }\n</style>\n\n<div class=\"container\">\n\t<div class=\"col-md-6\">\n\t\t<div>Open Web Developer Console</div>\n\t\t<br />\n\t\t<ui-component name=\"tree\" path=\"nodes\" config=\"exec:fn_selected;checked:fn_checked\"></ui-component>\n\t</div>\n</div>\n\n<script>\n\tvar nodes = [];\n\tnodes.push({ name: 'Item 1', children: null });\n\tnodes.push({ name: 'Item 2', children: null });\n\tnodes.push({ name: 'Item 3', children: [{ name: 'SubItem 3 / 1', children: [{ name: 'SubItem 3 / 1 / 1', children: null }]}]});\n\tnodes.push({ name: 'Item 4', children: null });\n\tnodes.push({ name: 'Item 5', children: [{ name: 'Subitem 5 / 1', children: null }, { name: 'Subitem 5 / 2', children: [{ name: 'Subitem 5 / 2 / 1', children: [{ name: 'Subitem 5 / 2 / 1 / 1', children: null }]}] }, { name: 'Subitem 5 / 3', children: null }]});\n\n\tfunction fn_selected(node, isGroup, expanded) {\n\t\tconsole.log('SELECTED NODE:', node, isGroup, expanded);\n\t}\n\n\tfunction fn_checked(checked) {\n\t\tconsole.log('CHECKED', checked);\n\t}\n\n</script>\n```"}],"knowledge":{"content_sha256":"6de32fb568f7cd85c102f575c5c5cd2f47b1dc0116a3d93017bcd8ddf921c5e8","source_sha256":"ccad3265f3896fc45da65a2d8d2800250c9590d275b7f5bdd73d97a20f8fcb4f","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","CHECKED","CSS","DEF","E0E0E0","HTML","MIT","NODE","RAW_HTML","SELECTED"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\t.ui-tree { border: 1px solid #E0E0E0; }\n\t.ui-dark .ui-tree { border: 1px solid #404040; }\n</style>\n\n<div class=\"container\">\n\t<div class=\"col-md-6\">\n\t\t<div>Open Web Developer Console</div>\n\t\t<br />\n\t\t<ui-component name=\"tree\" path=\"nodes\" config=\"exec:fn_selected;checked:fn_checked\"></ui-component>\n\t</div>\n</div>\n\n<script>\n\tvar nodes = [];\n\tnodes.push({ name: 'Item 1', children: null });\n\tnodes.push({ name: 'Item 2', children: null });\n\tnodes.push({ name: 'Item 3', children: [{ name: 'SubItem 3 / 1', children: [{ name: 'SubItem 3 / 1 / 1', children: null }]}]});\n\tnodes.push({ name: 'Item 4', children: null });\n\tnodes.push({ name: 'Item 5', children: [{ name: 'Subitem 5 / 1', children: null }, { name: 'Subitem 5 / 2', children: [{ name: 'Subitem 5 / 2 / 1', children: [{ name: 'Subitem 5 / 2 / 1 / 1', children: null }]}] }, { name: 'Subitem 5 / 3', children: null }]});\n\n\tfunction fn_selected(node, isGroup, expanded) {\n\t\tconsole.log('SELECTED NODE:', node, isGroup, expanded);\n\t}\n\n\tfunction fn_checked(checked) {\n\t\tconsole.log('CHECKED', checked);\n\t}\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/treeview","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/treeview/","markdown_url":"https://forge.totaljs.es/en/components/treeview.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"736102b02d2dbf949295d0cf01e1a20415418f253ab4a02b446bb9bc26324b0c","checked_at":"2026-10-20T12:24:16.714Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-TreeView","description":"This component is only a beta preview. It's still under construction.","messages":[{"role":"user","content":"j-TreeView\n\nThis component is only a beta preview. It's still under construction."},{"role":"assistant","content":"This component is only a beta preview. It's still under construction.\n\n\n__Configuration__:\n\n- `parent {String}` a container with fixed height, can be `window`. Default value: `parent` element.\n- `visibleY {Boolean}` still shows `Y` scrollbar (default: `true`)\n- `movable {Boolean}` enables the moving of nodes (default: `true`)\n- `cachexpand {Boolean}` remembers the previous state (default: `true`)\n- `arrows {Boolean}` enables arrows for folders (default: `true`)\n- `expanded {Boolean}` expands all nodes (default: `false`)\n- `autosort {Boolean}` sorts nodes according to `item.sort` (if exists) or `item.name` (if exists), default: `true`\n- `margin {Number}` top/bottom margin together (default: `0`)\n- `marginxs {Number}` top/bottom margin together for `xs` screen width\n- `marginsm` top/bottom margin together for `sm` screen width\n- `marginmd` top/bottom margin together for `md` screen width\n- `marginlg` top/bottom margin together for `lg` screen width\n- `disabled {Boolean}` disables the entire component\n- `parentselectable {Boolean}` parent will be selectable (default: `true`)\n\n__Data structure__:\n\n- Object Array\n- Object has to contain `id:String` which is the main ID of each item\n- Object has to contain `name:String` and `children:[another similar object]` or `children:null` (with no children)\n- Object has to contain `html:String` is rendered as content (if it doesn't exist then `name` is rendered)\n- Object item can contain `..., classname: 'css_custom_class' }`, it adds a CSS class onto the \"label\" element\n- Object item supports property `actions: { expand: true, drop: true, move: true }`\n\nUse it with `<ui-component name=\"treeview\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"width:300px;height:200px;border:1px solid #E0E0E0;margin:50px\">\n\t<ui-component name=\"treeview\" path=\"nodes\" config=\"exec:fn_selected\"></ui-component>\n</div>\n\n<script>\n\tvar nodes = [];\n\tnodes.push({ id: '1', name: 'Item 1', html: '<b>Item 1</b>', children: null });\n\tnodes.push({ id: '2', name: 'Item 2', children: null });\n\tnodes.push({ id: '3', name: 'Item 3', children: [{ id: 'A', name: 'SubItem 3 / 1', children: [{ id: 'AA', name: 'SubItem 3 / 1 / 1', children: null }]}]});\n\tnodes.push({ id: '4', name: 'Item 4', children: null });\n\tnodes.push({ id: '5', name: 'Item 5', children: [{ id: 'B', name: 'Subitem 5 / 1', children: null }, { name: 'Subitem 5 / 2', children: [{ id: 'BB', name: 'Subitem 5 / 2 / 1', children: [{ id: 'BC', name: 'Subitem 5 / 2 / 1 / 1', children: null }]}] }, { id: 'BD', name: 'Subitem 5 / 3', children: null }]});\n\n\tfunction fn_selected(node, isfolder) {\n\t\tconsole.log('SELECTED NODE:', node, isfolder);\n\t}\n\n</script>\n```"}],"knowledge":{"content_sha256":"d023c6d37125937af82ac37eeb8fc4ef4720f34cdd33a4a937d07d4a1e978cbd","source_sha256":"736102b02d2dbf949295d0cf01e1a20415418f253ab4a02b446bb9bc26324b0c","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","CSS","DEF","E0E0E0","MIT","NODE","SELECTED"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"width:300px;height:200px;border:1px solid #E0E0E0;margin:50px\">\n\t<ui-component name=\"treeview\" path=\"nodes\" config=\"exec:fn_selected\"></ui-component>\n</div>\n\n<script>\n\tvar nodes = [];\n\tnodes.push({ id: '1', name: 'Item 1', html: '<b>Item 1</b>', children: null });\n\tnodes.push({ id: '2', name: 'Item 2', children: null });\n\tnodes.push({ id: '3', name: 'Item 3', children: [{ id: 'A', name: 'SubItem 3 / 1', children: [{ id: 'AA', name: 'SubItem 3 / 1 / 1', children: null }]}]});\n\tnodes.push({ id: '4', name: 'Item 4', children: null });\n\tnodes.push({ id: '5', name: 'Item 5', children: [{ id: 'B', name: 'Subitem 5 / 1', children: null }, { name: 'Subitem 5 / 2', children: [{ id: 'BB', name: 'Subitem 5 / 2 / 1', children: [{ id: 'BC', name: 'Subitem 5 / 2 / 1 / 1', children: null }]}] }, { id: 'BD', name: 'Subitem 5 / 3', children: null }]});\n\n\tfunction fn_selected(node, isfolder) {\n\t\tconsole.log('SELECTED NODE:', node, isfolder);\n\t}\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/typed","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/typed/","markdown_url":"https://forge.totaljs.es/en/components/typed.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"fd25246723c3b42603118f63775f52a57cb35d7add4fd2b669379d571b91f151","checked_at":"2026-10-20T12:24:17.896Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Typed","description":"Config is the same as [typed.js](https://github.com/mattboldt/typed.js#customization). The only difference is that all keys are lowercase. This is to maintain consistency with other components. The on","messages":[{"role":"user","content":"j-Typed\n\nConfig is the same as [typed.js](https://github.com/mattboldt/typed.js#customization). The only difference is that all keys are lowercase. This is to maintain consistency with other components. The on"},{"role":"assistant","content":"- Based on [typed.js](https://github.com/mattboldt/typed.js/) by [Matt Boldt](https://github.com/mattboldt)\n- Value must be __array__\n\n\n__Configuration__:\n\nConfig is the same as [typed.js](https://github.com/mattboldt/typed.js#customization). The only difference is that all keys are lowercase. This is to maintain consistency with other components. The only added configuration is `id`\n\n- __NEW__: `selector {String}` can contain a nested element for rendering text\n- __NEW__: `text {String}` in the form `Text 1|Text 2|Text 3|Text N` otherwise text is loaded according to the `path`\n- `typespeed {Number}` in milliseconds (default: `0`)\n- `startdelay {Number}` in milliseconds (default: `0`)\n- `backspeed {Number}` in milliseconds (default: `0`)\n- `smartbackspace {Boolean}` this would only backspace the words after \"This is a\" (default: `true`)\n- `shuffle {Boolean}` shuffle the strings (default: `true`)\n- `backdelay {String}` time before backspacing (default: `700`)\n- `fadeout {Boolean}` fade out instead of backspace (default: `false`)\n- `fadeoutclass {String}` css class for fade animation (default: `typed-fade-out`)\n- `fadeoutdelay {Number}` fade out delay in milliseconds (default: `500`)\n- `loop {Boolean}` loop strings (default: `false`)\n- `loopcount {String/Number}` amount of loops (default: `Infinity`)\n- `showcursor {Boolean}` show cursor (default: `true`)\n- `cursorchar {String}` cursor character (default: `|`)\n- `autoinsertcss {Boolean}` insert CSS for cursor and fadeOut into HTML `<head>` (default: `true`)\n- `contenttype {String}` contentType `html` or `null` for plaintext (default: `html`)\n\n__Global events__:\n\nAgain, same as [typed.js](https://github.com/mattboldt/typed.js#customization). Added `component` parameter\n\n- `ON('typed.onComplete', function(typed, component) {})`\n- `ON('typed.preStringTyped', function(pos, typed, component) {})`\n- `ON('typed.onStringTyped', function(pos, typed, component) {})`\n- `ON('typed.onLastStringBackspaced', function(typed, component) {})`\n- `ON('typed.onTypingPaused', function(pos, typed, component) {})`\n- `ON('typed.onTypingResumed', function(pos, typed, component) {})`\n- `ON('typed.onReset', function(typed, component) {})`\n- `ON('typed.onStop', function(pos, typed, component) {})`\n- `ON('typed.onStart', function(pos, typed, component) {})`\n- `ON('typed.onDestroy', function(typed, component) {})`\n\nUse it with `<ui-component name=\"typed\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Denis Granec. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div style=\"height:100px\">\n\t<ui-component name=\"typed\" path=\"messages\" config=\"loop:true\" style=\"white-space:pre\"></ui-component>\n</div>\n<div style=\"height:50px\">\n\t<ui-component name=\"typed\" path=\"messages2\" config=\"loop:true;typespeed:100;backspeed:50\"></ui-component>\n</div>\n\n<script>\n\tvar messages = 'npm install total.js^1000\\n`+ <b>total.js@3.1.0</b>` ^1000\\n`added <b>1 package</b> from 23 contributors and audited 1 package in <b>1.348s</b>` ^1000\\n`found 0 vulnerabilities`';\n\tvar messages2 = ['Hello world!', '<b>Total.js</b> is awesome!', 'jComponent is <b>awesome</b>!', 'Lorem ipsum', 'Lorem dolor'];\n\n\tvar count = 0;\n\tON('typed.onComplete', function(typed, component) {\n\t\tif (typed.el.id === 'myconsole') {\n\t\t\tcount++;\n\n\t\t\tif (count === 5)\n\t\t\t\ttyped.loop = false;\n\t\t}\n\t});\n</script>\n```"}],"knowledge":{"content_sha256":"7afa86321c566d14f0d9e3fa8254aaacfa6ed3a86b67308319f22ecb8ccba507","source_sha256":"fd25246723c3b42603118f63775f52a57cb35d7add4fd2b669379d571b91f151","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","CSS","DEF","HTML","MIT","Total.js"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div style=\"height:100px\">\n\t<ui-component name=\"typed\" path=\"messages\" config=\"loop:true\" style=\"white-space:pre\"></ui-component>\n</div>\n<div style=\"height:50px\">\n\t<ui-component name=\"typed\" path=\"messages2\" config=\"loop:true;typespeed:100;backspeed:50\"></ui-component>\n</div>\n\n<script>\n\tvar messages = 'npm install total.js^1000\\n`+ <b>total.js@3.1.0</b>` ^1000\\n`added <b>1 package</b> from 23 contributors and audited 1 package in <b>1.348s</b>` ^1000\\n`found 0 vulnerabilities`';\n\tvar messages2 = ['Hello world!', '<b>Total.js</b> is awesome!', 'jComponent is <b>awesome</b>!', 'Lorem ipsum', 'Lorem dolor'];\n\n\tvar count = 0;\n\tON('typed.onComplete', function(typed, component) {\n\t\tif (typed.el.id === 'myconsole') {\n\t\t\tcount++;\n\n\t\t\tif (count === 5)\n\t\t\t\ttyped.loop = false;\n\t\t}\n\t});\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/uibuilder","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/uibuilder/","markdown_url":"https://forge.totaljs.es/en/components/uibuilder.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"d3a7e9196368f0870c71ed42a0c3a8c1672159f462c71c529cf02ef04ec7d3a5","checked_at":"2026-10-20T12:24:19.228Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-UIBuilder","description":"This component renders the application designed via UI Builder. You can load the app via the path specified in the component or via the config.url option. The path can contain a URL address to the JSO","messages":[{"role":"user","content":"j-UIBuilder\n\nThis component renders the application designed via UI Builder. You can load the app via the path specified in the component or via the config.url option. The path can contain a URL address to the JSO"},{"role":"assistant","content":"This component renders the application designed via UI Builder. You can load the app via the `path` specified in the component or via the `config.url` option. The path can contain a URL address to the `JSON` app schema or the app object (parsed JSON).\n\n\n__Configuration__:\n\n- `app {String}` a link to the method/variable where will be stored `app` instance\n- `url {String}` a link to the app schema (must be in the `json` format)\n- `css {Boolean}` applies app style to the current element (default: `true`)\n- `id {String}` overwrites app ID\n\n__Methods__:\n\n- `component.load(data)` the method loads `app` into the current element\n\n__Properties__:\n\n- `component.app` contains an `app` instance\n\n## How to handle data?\n\n```html\n<ui-component name=\"uibuilder\" ... config=\"app:myapphandler\"></ui-component>\n\n<script>\n\n\tfunction myapphandler(app) {\n\n\t\t// app.component {jComponent}\n\t\t// app.schema {Object} uibuilder compiled design\n\t\t// app.schema.inputs {Array Objects}\n\t\t// app.schema.outputs {Array Objects}\n\n\t\t// How to capture data?\n\t\tapp.on('output', function(meta) {\n\t\t\t// meta.id {String} output identifier\n\t\t\t// meta.data {Object} raw data from the builder\n\t\t\t// meta.app {Object} app instance\n\t\t\t// meta.instance {Object} which instance sent data?\n\t\t\t// meta.instanceid {String} an instance identifier\n\t\t\t// meta.component {Object} which component sent data?\n\t\t\t// meta.componentid {String} a component identifier\n\t\t\t// meta.err {String} optional, error handler (nullable)\n\t\t\t// meta.output {String} an output name\n\t\t\t// meta.note {String} optional, a note\n\t\t\t// meta.ref {String} an internal reference\n\n\t\t\tAJAX('POST /yourapi/', meta.data, function(response) {\n\t\t\t\t// What next?\n\t\t\t});\n\n\t\t});\n\n\t\t// How to inject data?\n\t\t// Example\n\t\tapp.on('ready', function() {\n\t\t\t// First, you must know input identifier\n\t\t\tapp.input('cidlm5caacg_load', { email: '@' }, console.log);\n\t\t});\n\n\t}\n\n</script>\n```\n\nUse it with `<ui-component name=\"uibuilder\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"uibuilder\" config=\"url:https://cdn.componentator.com/uibuilder/demo/render/dashboard.json\"></ui-component>\n```"}],"knowledge":{"content_sha256":"0241ff9d567a5f070753d8a0613d99e4ed489017dbbc352808eb6e37ed87f269","source_sha256":"d3a7e9196368f0870c71ed42a0c3a8c1672159f462c71c529cf02ef04ec7d3a5","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["AJAX","CDN","DEF","JSON","MIT","POST","URL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"uibuilder\" config=\"url:https://cdn.componentator.com/uibuilder/demo/render/dashboard.json\"></ui-component>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/unsaved","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/unsaved/","markdown_url":"https://forge.totaljs.es/en/components/unsaved.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"98d39214e5372dcdc0607cc2789089b438511cbc34c63169d5a8fb45c4db0733","checked_at":"2026-10-20T12:24:19.956Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Unsaved","description":"j-Unsaved: easy usage","messages":[{"role":"user","content":"j-Unsaved\n\nj-Unsaved: easy usage"},{"role":"assistant","content":"- `path` optional, can contain a link to the `Boolean` value, `true` will display a confirmation message\n\n__Configuration__:\n\n- `check {String}` a link to the __check__ `function() {` that must return `Boolean` -> `true` will display a confirmation message (it's synchronous)\n- `message {String}` a message for the user (it may not work with most browsers because they have their own messages)\n\nUse it with `<ui-component name=\"unsaved\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"unsaved\" config=\"check:checkunsaved\"></ui-component>\n\n<script>\n\n\tfunction checkunsaved() {\n\t\t// if \"true\" then the browser will ask about unsaved changes\n\t\treturn true;\n\t}\n\n</script>\n```"}],"knowledge":{"content_sha256":"c6c487e3540c6881efb177ac6b9492de5d9b7d3a0e88696b99a4dbdcd253678a","source_sha256":"98d39214e5372dcdc0607cc2789089b438511cbc34c63169d5a8fb45c4db0733","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"unsaved\" config=\"check:checkunsaved\"></ui-component>\n\n<script>\n\n\tfunction checkunsaved() {\n\t\t// if \"true\" then the browser will ask about unsaved changes\n\t\treturn true;\n\t}\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/upload","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/upload/","markdown_url":"https://forge.totaljs.es/en/components/upload.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"d04f08539bd45ab8094f96e361acca05f7c2d15fa4e5f97e5db59ec5f1e173ec","checked_at":"2026-10-20T12:24:20.686Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Upload","description":"The component can perform uploading of files.","messages":[{"role":"user","content":"j-Upload\n\nThe component can perform uploading of files."},{"role":"assistant","content":"The component can perform __uploading of files__.\n\n- easy and universal usage\n- singleton\n\n__Methods__:\n\n```javascript\nvar opt = {};\n\n// Restricts files according to the content-type\n// opt.accept = 'image/*';\n\n// Enables multiple files\n// opt.multiple = true;\n\n// IMPORTANT:\n// URL address for uploading\nopt.url = '/api/upload/';\n\n// A callback\nopt.callback = function(response) {\n\n};\n\nopt.error = function(err) {\n\t// Optional\n\t// Error handling\n};\n\n// Optional, additional request data\nopt.data = {};\nopt.data.fieldName1 = 'value';\nopt.data.fieldName2 = 'value';\n\n// Shows file browser\nSETTER('upload/show', opt);\n```\n\nFiles are uploaded with these names/keys in the form `file1`, `file2`, `file..N`.\n\nUse it with `<ui-component name=\"upload\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"upload\"></ui-component>\n<br />\n<a href=\"javascript:uploadfile()\">Upload file</a>\n\n<script>\n\tfunction uploadfile() {\n\n\t\tvar opt = {};\n\n\t\topt.url = '/api/upload/files/';\n\t\topt.accept = 'image/*';\n\t\topt.multiple = true;\n\t\topt.callback = function(response) {\n\t\t\tconsole.log(response);\n\t\t};\n\t\t// opt.error = function(err) {};\n\n        SETTER('upload/show', opt);\n    }\n</script>\n```"}],"knowledge":{"content_sha256":"bbfe28b116aa688c8ab8af1fb2fccc876d4e6ab6737b889a4d94eea2e9794839","source_sha256":"d04f08539bd45ab8094f96e361acca05f7c2d15fa4e5f97e5db59ec5f1e173ec","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","IMPORTANT","MIT","SETTER","URL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"upload\"></ui-component>\n<br />\n<a href=\"javascript:uploadfile()\">Upload file</a>\n\n<script>\n\tfunction uploadfile() {\n\n\t\tvar opt = {};\n\n\t\topt.url = '/api/upload/files/';\n\t\topt.accept = 'image/*';\n\t\topt.multiple = true;\n\t\topt.callback = function(response) {\n\t\t\tconsole.log(response);\n\t\t};\n\t\t// opt.error = function(err) {};\n\n        SETTER('upload/show', opt);\n    }\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/validate","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/validate/","markdown_url":"https://forge.totaljs.es/en/components/validate.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"eea06fb89aaf49614ceac8bf9b596426d25a70fb118c1de458794f7ce739fba3","checked_at":"2026-10-20T12:24:21.721Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Validate","description":"The component contains the same source-code as the j-Validation component.","messages":[{"role":"user","content":"j-Validate\n\nThe component contains the same source-code as the j-Validation component."},{"role":"assistant","content":"The component contains the same source-code as the `j-Validation` component.\n\n\n__Configuration__:\n\n- `if {String}` can contain JS condition for evaluation\n\t- `value {Object}` contains entire model\n\t- `path {String}` contains changed path\n\t- __must return boolean__\n- `selector {String}` jQuery selector for disabling (default: `button[name=\"submit\"]`)\n- `delay {Number}` a timeout for validation (default: `100`)\n- `flags {String}` flags for better validation, can contain `visible,hidden,enabled,disabled` components\n\t- `enabled` validate all components which are enabled `input`, `textarea` or `select` elements\n\t- `disabled` validate all components which are disabled `input`, `textarea` or `select` elements\n\t- `hidden` validate all components which are hidden\n\t- `visible` validate all components which are visible\n- `track {String}` can contain paths divided by the comma, and manual changing of each path (via `SET()`, `UPD()`, `INC()`, etc. methods) will evaluate the validation\n- `validonly {Boolean}` skips `dirty` state (default: `false`)\n- __NEW__ `changes {Boolean}` enables comparing changes only between the input model and the form (default: `false`)\n- __NEW__ `strictchanges {Boolean}` enables strict comparing changes (otherwise `null`, `false`, `empty strings` and `0` values will be removed), default: `false`\n- __NEW__ `exec {String}` optional, a link to the `function(data)` that is executed when the model is validated and changed\n- __NEW__ `output {String}` optional, a link to the `function(valid)` or path\n\n__Good to know__:\n\n- component adds `ui-validate-ok` class to the element if the model is valid\n- component adds `ui-validate-no` class to the element if the model is invalid\n- component adds `ui-validate-modified` class to the element if the model is changed\n\nUse it with `<ui-component name=\"validate\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<!-- REMOVE -->\n<ui-plugin path=\"model\">\n\t<ui-component name=\"input\" path=\"?.name\" config=\"required:true\">Name</ui-component>\n\t<br />\n\t<br />\n\t<ui-component name=\"validate\" path=\"?\">\n\t\t<button name=\"submit\" disabled=\"disabled\" onclick=\"show_me()\">SUBMIT FORM</button>\n\t</ui-component>\n</ui-plugin>\n\n<style type=\"text/css\">\n\tbutton:disabled {\n\t\tbackground-color: #cccccc;\n\t\tcolor: #666666;\n\t\tcursor: not-allowed;\n\t}\n</style>\n\n<script>\n    function show_me() {\n        alert('ok');\n    }\n</script>\n```"}],"knowledge":{"content_sha256":"a6625dcae76fb0568325a6b98c518ee5e64b39cfb998515a91a993bcd4a3e397","source_sha256":"eea06fb89aaf49614ceac8bf9b596426d25a70fb118c1de458794f7ce739fba3","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","FORM","INC","MIT","REMOVE","SET","SUBMIT","UPD"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<!-- REMOVE -->\n<ui-plugin path=\"model\">\n\t<ui-component name=\"input\" path=\"?.name\" config=\"required:true\">Name</ui-component>\n\t<br />\n\t<br />\n\t<ui-component name=\"validate\" path=\"?\">\n\t\t<button name=\"submit\" disabled=\"disabled\" onclick=\"show_me()\">SUBMIT FORM</button>\n\t</ui-component>\n</ui-plugin>\n\n<style type=\"text/css\">\n\tbutton:disabled {\n\t\tbackground-color: #cccccc;\n\t\tcolor: #666666;\n\t\tcursor: not-allowed;\n\t}\n</style>\n\n<script>\n    function show_me() {\n        alert('ok');\n    }\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/validation","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/validation/","markdown_url":"https://forge.totaljs.es/en/components/validation.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"0e8eb7f16ce9580c5a867514ac03a4bbc07a487f30b43a891c319dd96bfbbdb0","checked_at":"2026-10-20T12:24:22.605Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Validation","description":"The component contains the same source-code as the j-Validate component.","messages":[{"role":"user","content":"j-Validation\n\nThe component contains the same source-code as the j-Validate component."},{"role":"assistant","content":"The component contains the same source-code as the `j-Validate` component.\n\n\n__Configuration__:\n\n- `if {String}` can contain JS condition for evaluation\n\t- `value {Object}` contains entire model\n\t- `path {String}` contains changed path\n\t- __must return boolean__\n- `selector {String}` jQuery selector for disabling (default: `button[name=\"submit\"]`)\n- `delay {Number}` a timeout for validation (default: `100`)\n- `flags {String}` flags for better validation, can contain `visible,hidden,enabled,disabled` components\n\t- `enabled` validate all components which are enabled `input`, `textarea` or `select` elements\n\t- `disabled` validate all components which are disabled `input`, `textarea` or `select` elements\n\t- `hidden` validate all components which are hidden\n\t- `visible` validate all components which are visible\n- `track {String}` can contain paths divided by the comma, and manual changing of each path (via `SET()`, `UPD()`, `INC()`, etc. methods) will evaluate the validation\n- `validonly {Boolean}` skips `dirty` state (default: `false`)\n- __NEW__ `changes {Boolean}` enables comparing changes only between the input model and the form (default: `false`)\n- __NEW__ `strictchanges {Boolean}` enables strict comparing changes (otherwise `null`, `false`, `empty strings` and `0` values will be removed), default: `false`\n- __NEW__ `exec {String}` optional, a link to the `function(data)` that is executed when the model is validated and changed\n- __NEW__ `output {String}` optional, a link to the `function(valid)` or path\n\n__Good to know__:\n\n- component adds `ui-validate-ok` class to the element if the model is valid\n- component adds `ui-validate-no` class to the element if the model is invalid\n- component adds `ui-validate-modified` class to the element if the model is changed\n\nUse it with `<ui-component name=\"validation\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<!-- REMOVE -->\n<ui-component name=\"textbox\" path=\"model.name\" config=\"required:true\">Name</ui-component>\n<br />\n<br />\n\n<!-- VALIDATION -->\n<ui-component name=\"validation\" path=\"model\">\n\t<button name=\"submit\" disabled=\"disabled\" onclick=\"show_me()\">SUBMIT FORM</button>\n</ui-component>\n\n<style type=\"text/css\">\n\tbutton:disabled {\n\t\tbackground-color: #cccccc;\n\t\tcolor: #666666;\n\t\tcursor: not-allowed;\n\t}\n</style>\n\n<script>\n    function show_me() {\n        alert('ok');\n    }\n</script>\n```"}],"knowledge":{"content_sha256":"af1b737e93200d8952c8cfee90de3dc857e177210426e0fbd5bdaf2a37298311","source_sha256":"0e8eb7f16ce9580c5a867514ac03a4bbc07a487f30b43a891c319dd96bfbbdb0","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","FORM","INC","MIT","REMOVE","SET","SUBMIT","UPD","VALIDATION"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<!-- REMOVE -->\n<ui-component name=\"textbox\" path=\"model.name\" config=\"required:true\">Name</ui-component>\n<br />\n<br />\n\n<!-- VALIDATION -->\n<ui-component name=\"validation\" path=\"model\">\n\t<button name=\"submit\" disabled=\"disabled\" onclick=\"show_me()\">SUBMIT FORM</button>\n</ui-component>\n\n<style type=\"text/css\">\n\tbutton:disabled {\n\t\tbackground-color: #cccccc;\n\t\tcolor: #666666;\n\t\tcursor: not-allowed;\n\t}\n</style>\n\n<script>\n    function show_me() {\n        alert('ok');\n    }\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/view","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/view/","markdown_url":"https://forge.totaljs.es/en/components/view.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"bcd804d6362298c9f1da80016b31d54c00cfdec54b7ad621c15d457826477a63","checked_at":"2026-10-20T12:24:23.480Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-View","description":"This component can be helpful for reusable views in intranet apps. The component wraps the view into the isolated scope, and the value according to the component path will be used as a model for the v","messages":[{"role":"user","content":"j-View\n\nThis component can be helpful for reusable views in intranet apps. The component wraps the view into the isolated scope, and the value according to the component path will be used as a model for the v"},{"role":"assistant","content":"This component can be helpful for reusable views in intranet apps. The component wraps the view into the isolated scope, and the value according to the component path will be used as a model for the view.\n\n\n__Configuration__:\n\n- `url {String}` URL address to the view (`.html` file)\n- `cache {String}` download cache (default: `session`)\n\n### Usage\n\n__app.html__:\n\n```html\n<ui-component name=\"view\" path=\"some.path\" config=\"url:view.html\"></ui-component>\n\n- you can use an unlimited count of the same views (identifier is URL address)\n- each view will have an independent scope/plugin\n\nExample:\n<ui-component name=\"view\" path=\"some.path\" config=\"url:view.html\"></ui-component>\n<ui-component name=\"view\" path=\"some.path\" config=\"url:view.html\"></ui-component>\n<ui-component name=\"view\" path=\"some.path\" config=\"url:view.html\"></ui-component>\n<ui-component name=\"view\" path=\"some.path\" config=\"url:view.html\"></ui-component>\n<ui-component name=\"view\" path=\"some.path\" config=\"url:view.html\"></ui-component>\n```\n\n__view.html__:\n\n```html\n<div>\n\t<h1><ui-bind path=\"?.name\" config=\"text\"></ui-bind></h1>\n\t<button class=\"exec\" data-exec=\"?/uppercase\">To upper-case</button>\n</div>\n\n<script>\n\t// Plugin is optional\n\t// Name of the plugin is internally generated randomly\n\tPLUGIN(function(exports) {\n\n\t\t// Optional\n\t\t// Is executed only one-time\n\t\texports.init = function(el) {\n\t\t\t// @el {jQuery element}\n\t\t};\n\n\t\texports.uppercase = function() {\n\t\t\tvar model = GET('?');\n\t\t\tmodel.name = model.name.toUpperCase();\n\t\t\tUPD('?.name');\n\t\t};\n\n\t});\n</script>\n```\n\nUse it with `<ui-component name=\"view\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div class=\"container\">\n\t<div class=\"row\">\n\t\t<div class=\"col-xs-4\">\n\t\t\t<ui-component name=\"view\" path=\"a\" config=\"url:https://cdn.componentator.com/examples/view.html\"></ui-component>\n\t\t</div>\n\t\t<div class=\"col-xs-4\">\n\t\t\t<ui-component name=\"view\" path=\"b\" config=\"url:[view]\"></ui-component>\n\t\t</div>\n\t\t<div class=\"col-xs-4\">\n\t\t\t<ui-component name=\"view\" path=\"c\" config=\"url:[view]\"></ui-component>\n\t\t</div>\n\t</div>\n</div>\n\n<script>\n\n\tENV('view', 'https://cdn.componentator.com/examples/view.html');\n\n\tvar a = { name: 'Total.js' };\n\tvar b = { name: 'Express.js' };\n\tvar c = { name: 'Meteor.js' };\n\n</script>\n```"}],"knowledge":{"content_sha256":"a43f0a7e2d58519b626b00b2e07712a6efcbb2f72f9e6600691a4270107311ef","source_sha256":"bcd804d6362298c9f1da80016b31d54c00cfdec54b7ad621c15d457826477a63","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","ENV","GET","MIT","PLUGIN","Total.js","UPD","URL"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div class=\"container\">\n\t<div class=\"row\">\n\t\t<div class=\"col-xs-4\">\n\t\t\t<ui-component name=\"view\" path=\"a\" config=\"url:https://cdn.componentator.com/examples/view.html\"></ui-component>\n\t\t</div>\n\t\t<div class=\"col-xs-4\">\n\t\t\t<ui-component name=\"view\" path=\"b\" config=\"url:[view]\"></ui-component>\n\t\t</div>\n\t\t<div class=\"col-xs-4\">\n\t\t\t<ui-component name=\"view\" path=\"c\" config=\"url:[view]\"></ui-component>\n\t\t</div>\n\t</div>\n</div>\n\n<script>\n\n\tENV('view', 'https://cdn.componentator.com/examples/view.html');\n\n\tvar a = { name: 'Total.js' };\n\tvar b = { name: 'Express.js' };\n\tvar c = { name: 'Meteor.js' };\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/viewbox","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/viewbox/","markdown_url":"https://forge.totaljs.es/en/components/viewbox.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"6e8be328ad1a5c6048d8104c0511f68ba6fa9494a911ad29096b0072d3118ecb","checked_at":"2026-10-20T12:24:24.355Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-ViewBox","description":"j-ViewBox: keeps a fixed percentage height; great usage for intranet applications","messages":[{"role":"user","content":"j-ViewBox\n\nj-ViewBox: keeps a fixed percentage height; great usage for intranet applications"},{"role":"assistant","content":"- keeps a fixed percentage height\n- great usage for intranet applications\n- supports `releasing`\n\n__Configuration__:\n\n- `height {Number}` a percentage of height\n- `disabled {Boolean}` optional, can disable content with the layer\n- `parent {String}` optional, a container with fixed height, can be `window`. Default value: `parent` element.\n- `minheight {Number}` a minimal height in pixels (default: `0` - disabled)\n- `scroll {Boolean}` enables vertical scrolling (default: `false` - disabled)\n- `scrollbar {Boolean/String}` shows scrollbar (default: `true` or __new__: `native`)\n- `delay {Number}` `msec.` resizes the box again if the value is changed according to the path (default: `100`)\n- `visibleY {Boolean}` still shows the `Y` scrollbar (default: `false`)\n- `visibleX {Boolean}` still shows the `X` scrollbar (default: `false`)\n- `scrollto {String}` scrolls to top/bottom automatically if the `path` is changed (default: `EMPTY`, can be `top` or `bottom`)\n- `margin {Number}` optional, a top/bottom margin together (supports `auto` which is counted from `offset.top`, default: `0`)\n- `marginxs {Number}` optional, a top/bottom margin together for `xs` screen width\n- `marginsm` optional, a top/bottom margin together for `sm` screen width\n- `marginmd` optional, a top/bottom margin together for `md` screen width\n- `marginlg` optional, a top/bottom margin together for `lg` screen width\n- `invisible {Boolean}` if `true` then viewbox sets `invisible` class when is rendering (default: `true`)\n- `scrollbarshadow {Boolean}` if `true` then the custom scrollbars will contain vertical shadow (default: `false`)\n- `initdelay {Number}` removes the `inivisible` class after specified time (default: `250` ms)\n- `centered {Boolean}` enables vertical and horitonzal centering for the content (default: `false`)\n- `resizedelay {Number}` delay for resizing (default: `200`)\n- __NEW__: `togglescrolled {String}` a DOM selector for the parent elements (it appends `ui-viewbox-scrolled` class when the viewbox is scrolled)\n\n__Methods__:\n\n- `component.scrolltop(val)` scrolls Y\n- `component.scrollbottom(val)` scrolls Y from bottom side\n- `component.resize()` resizes the container (it reacts on `resize` events automatically)\n- `component.resizescrollbar()` resizes custom scrollbar (targeted for special cases)\n\nUse it with `<ui-component name=\"viewbox\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"viewbox\" config=\"height:30;parent:window\" style=\"background-color:#F5F5F5\">\n\tAlways 30%\n</ui-component>\n<ui-component name=\"viewbox\" config=\"height:70;parent:window\" style=\"background-color:#E0E0E0\">\n\tAlways 70%\n</ui-component>\n```"}],"knowledge":{"content_sha256":"02406169db5c40745ff2d8e3a087b131dfad0bab424271cb125a2bf2aedf7084","source_sha256":"6e8be328ad1a5c6048d8104c0511f68ba6fa9494a911ad29096b0072d3118ecb","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","DOM","E0E0E0","EMPTY","F5F5F5","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"viewbox\" config=\"height:30;parent:window\" style=\"background-color:#F5F5F5\">\n\tAlways 30%\n</ui-component>\n<ui-component name=\"viewbox\" config=\"height:70;parent:window\" style=\"background-color:#E0E0E0\">\n\tAlways 70%\n</ui-component>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/virtualwire","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/virtualwire/","markdown_url":"https://forge.totaljs.es/en/components/virtualwire.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"0706ec6ea7f36f55760e2d9ad092b33f3ba44929100228de7168819eada393df","checked_at":"2026-10-20T12:24:25.084Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-VirtualWire","description":"This is a very special UI component that can move elements from element to another element according to the selector and the value of the path. Look to the example for understanding.","messages":[{"role":"user","content":"j-VirtualWire\n\nThis is a very special UI component that can move elements from element to another element according to the selector and the value of the path. Look to the example for understanding."},{"role":"assistant","content":"This is a very special UI component that can move elements from `element` to another `element` according to the selector and the value of the path. Look to the example for understanding.\n\n- `selector {String}` a selector for wires (default: `.virtualwire`)\n- `delay {Number}` a delay in milliseconds for movement of elements (default: `10`)\n- `repeat {Number}` a repeat delay for obtaining of element (default: `50` ms)\n- `max {Number}` a maximum count for repeating (default: `100`)\n\nUse it with `<ui-component name=\"virtualwire\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div class=\"b\">Virtual Wire Container:</div>\n<ui-component name=\"virtualwire\" path=\"common.page\"></ui-component>\n\n<hr />\n\n<!-- YOU CAN HIDE THE ELEMENTS BELOW BY ADDING \"hidden\" CLASS -->\n<div class=\"virtualwire\" data-if=\"users\">\n\t<div>USERS</div>\n</div>\n\n<div class=\"virtualwire\" data-if=\"products\" data-in=\"products_in\" data-out=\"products_out\">\n\t<div>PRODUCTS</div>\n</div>\n\n<script>\n\n\tvar common = {};\n\n\tsetInterval(function() {\n\t\tswitch (common.page) {\n\t\t\tcase 'users':\n\t\t\t\tSET('common.page', 'products');\n\t\t\t\tbreak;\n\t\t\tcase 'products':\n\t\t\t\tNULL('common.page');\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tSET('common.page', 'users');\n\t\t\t\tbreak;\n\t\t}\n\t}, 2000);\n\n\tfunction products_out() {\n\t\tconsole.log('products_out');\n\t}\n\n\tfunction products_in() {\n\t\tconsole.log('products_in');\n\t}\n\n</script>\n```"}],"knowledge":{"content_sha256":"d8a647d2e6ea7c78c2a9130fa48198dbf0f39c87883295cd0196bf74805379ce","source_sha256":"0706ec6ea7f36f55760e2d9ad092b33f3ba44929100228de7168819eada393df","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["ADDING","BELOW","CAN","CDN","CLASS","DEF","ELEMENTS","HIDE","MIT","NULL","PRODUCTS","SET","THE","USERS","YOU"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div class=\"b\">Virtual Wire Container:</div>\n<ui-component name=\"virtualwire\" path=\"common.page\"></ui-component>\n\n<hr />\n\n<!-- YOU CAN HIDE THE ELEMENTS BELOW BY ADDING \"hidden\" CLASS -->\n<div class=\"virtualwire\" data-if=\"users\">\n\t<div>USERS</div>\n</div>\n\n<div class=\"virtualwire\" data-if=\"products\" data-in=\"products_in\" data-out=\"products_out\">\n\t<div>PRODUCTS</div>\n</div>\n\n<script>\n\n\tvar common = {};\n\n\tsetInterval(function() {\n\t\tswitch (common.page) {\n\t\t\tcase 'users':\n\t\t\t\tSET('common.page', 'products');\n\t\t\t\tbreak;\n\t\t\tcase 'products':\n\t\t\t\tNULL('common.page');\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tSET('common.page', 'users');\n\t\t\t\tbreak;\n\t\t}\n\t}, 2000);\n\n\tfunction products_out() {\n\t\tconsole.log('products_out');\n\t}\n\n\tfunction products_in() {\n\t\tconsole.log('products_in');\n\t}\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/watch","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/watch/","markdown_url":"https://forge.totaljs.es/en/components/watch.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"2fd1235aca726ffb33d72d82dab8029062d99b8e939c9dcba87c7dac9867435f","checked_at":"2026-10-20T12:24:25.967Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Watch","description":"The component can evaluate the method in the form function(value, element, type, path) when the value (according to the path) is changed. The component can simplify watching changes in the model.","messages":[{"role":"user","content":"j-Watch\n\nThe component can evaluate the method in the form function(value, element, type, path) when the value (according to the path) is changed. The component can simplify watching changes in the model."},{"role":"assistant","content":"The component can evaluate the method in the form `function(value, element, type, path)` when the value (according to the path) is changed. The component can simplify watching changes in the model.\n\n\n__Configuration__:\n\n- `exec` a link to the method `function(value, element, flags, path)`\n\nUse it with `<ui-component name=\"watch\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"watch\" path=\"common.value\" config=\"exec:changedvalue\"></ui-component>\n\n<div class=\"padding\">\n\t<div id=\"mytitle\"></div>\n</div>\n\n<script>\n\n\tvar common = { value: 'init' };\n\n\tsetInterval(function() {\n\t\tSET('common.value', new Date().format('HH:mm:ss'));\n\t}, 1000);\n\n\tfunction changedvalue(value, element, type, path) {\n\t\t$('#mytitle').html(value);\n\t}\n\n</script>\n```"}],"knowledge":{"content_sha256":"59cab19e83867e2d3e6290bde3f90398d0e0692c87bb806752e747d42f40598c","source_sha256":"2fd1235aca726ffb33d72d82dab8029062d99b8e939c9dcba87c7dac9867435f","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","SET"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"watch\" path=\"common.value\" config=\"exec:changedvalue\"></ui-component>\n\n<div class=\"padding\">\n\t<div id=\"mytitle\"></div>\n</div>\n\n<script>\n\n\tvar common = { value: 'init' };\n\n\tsetInterval(function() {\n\t\tSET('common.value', new Date().format('HH:mm:ss'));\n\t}, 1000);\n\n\tfunction changedvalue(value, element, type, path) {\n\t\t$('#mytitle').html(value);\n\t}\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/watcher","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/watcher/","markdown_url":"https://forge.totaljs.es/en/components/watcher.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"d54463644efbc327ee128c1111e50c3234c0d8eadf0925cc1bfef33a64672eb3","checked_at":"2026-10-20T12:24:26.869Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Watcher","description":"This component executes the code defined in the <script type='text/js'></script> tag.","messages":[{"role":"user","content":"j-Watcher\n\nThis component executes the code defined in the <script type='text/js'></script> tag."},{"role":"assistant","content":"This component executes the code defined in the `<script type=\"text/js\"></script>` tag.\n\n```html\n<script type=\"text/js\">\n\n\t// value {Object} a value bound according to the path\n\t// path {String} a path where the value was bonded\n\t// type {Number} binding type (0: init, 1: manually, 2: by input, 3: default value)\n\t// element {jQuery element} a current element\n\t// datasource {Object}\n\t// component {jComponent}\n\n</script>\n```\n\n__Configuration__:\n\n- `delay {Number}` a delay for evaluating code (default: `0`)\n- __NEW__: `datasource {String}` a link to the datasource (it will be stored in `datasource` property)\n\n__IMPORTANT__:\n\nYou need to use `<script type=\"text/js\">` or `<script type=\"text/plain\">`. Don't use the script element without `type` or with type `text|application/javascript` because the script will be evaluated by the browser automatically.\n\nUse it with `<ui-component name=\"watcher\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"watcher\" path=\"common.value\">\n\t<script type=\"text/js\">\n\t\tconsole.log('-->', value, path, type);\n\t\telement.html(value);\n\t</script>\n</ui-component>\n\n<script>\n\tvar common = { value: 'init' };\n\tsetInterval(function() {\n\t\tSET('common.value', new Date().format('HH:mm:ss'));\n\t}, 1000);\n</script>\n```"}],"knowledge":{"content_sha256":"31f1cf7cf34096c42e1ca2f7ec8685d43f06c1c1f06954e602153e5e04fead9e","source_sha256":"d54463644efbc327ee128c1111e50c3234c0d8eadf0925cc1bfef33a64672eb3","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT","SET"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"watcher\" path=\"common.value\">\n\t<script type=\"text/js\">\n\t\tconsole.log('-->', value, path, type);\n\t\telement.html(value);\n\t</script>\n</ui-component>\n\n<script>\n\tvar common = { value: 'init' };\n\tsetInterval(function() {\n\t\tSET('common.value', new Date().format('HH:mm:ss'));\n\t}, 1000);\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/wiki","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/wiki/","markdown_url":"https://forge.totaljs.es/en/components/wiki.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"5fa318a213895ec7cde5c3de209c6ef3e0b83df0a8f1bc3d2a92c0cfbb91f998","checked_at":"2026-10-20T12:24:28.212Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Wiki","description":"Great component for inline Wiki/Documentation.","messages":[{"role":"user","content":"j-Wiki\n\nGreat component for inline Wiki/Documentation."},{"role":"assistant","content":"Great component for inline Wiki/Documentation.\n\n- supports __dark mode__\n\n__Configuration__:\n\n- `title {String}` a title (default: `Wiki`)\n- `datasource {String}`` a path to data-source (learn from example)\n- `width {Number}` a default width (default: `400`)\n\nUse it with `<ui-component name=\"wiki\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\tbody { background-color: #D0D0D0; }\n</style>\n\n<ui-component name=\"markdown\"></ui-component>\n<ui-component name=\"wiki\" path=\"visible\" config=\"datasource:wiki\"></ui-component>\n\n<script>\n\n\tvar visible = true;\n\tvar wiki = [];\n\n\twiki.push({ name: 'Markdown', body: '# Markdown\\nLorem ipsum dolor sit amet, consectetur adipisicing elit. Veniam repellendus odit tempora iusto ea distinctio, illum. Accusamus fugiat, soluta optio!' });\n\twiki.push({ name: 'Blockquotes', body: '# Markdown\\n> Blockquote works correctly\\n\\n- list item 1\\n- list item 2\\n- list item 3' });\n\twiki.push({ name: 'Syntax highlighter', body: '```javascript\\nalert(\\'OK\\');\\n```' });\n\twiki.push({ name: 'Chart example', body: '# Great charts\\n```barchart\\nYear    |   Apples   |  Bananas   |   Oranges\\n2009    |   80       |  40        |   70\\n2010    |   40       |  50        |   300\\n2011    |   20       |  70        |   120\\n2012    |   100      |  50        |   80\\n2013    |   300      |  150       |   40\\n```\\n\\n# Line chart\\n\\n```linechart\\nYear    |   Apples   |  Bananas   |   Oranges\\n2009    |   80       |  40        |   70\\n2010    |   40       |  50        |   300\\n2011    |   20       |  70        |   120\\n2012    |   100      |  50        |   80\\n2013    |   300      |  150       |   40\\n```' });\n\twiki.push({ name: 'Video', body: '```video\\nhttps://www.youtube.com/watch?v=FyIf2ZiYfT8\\n```' });\n\n</script>\n```"}],"knowledge":{"content_sha256":"aae2bf92e22df5d305909ba10bbbe0cf9bf077eafa66b71a2fe7f200438abc5e","source_sha256":"5fa318a213895ec7cde5c3de209c6ef3e0b83df0a8f1bc3d2a92c0cfbb91f998","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","D0D0D0","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\tbody { background-color: #D0D0D0; }\n</style>\n\n<ui-component name=\"markdown\"></ui-component>\n<ui-component name=\"wiki\" path=\"visible\" config=\"datasource:wiki\"></ui-component>\n\n<script>\n\n\tvar visible = true;\n\tvar wiki = [];\n\n\twiki.push({ name: 'Markdown', body: '# Markdown\\nLorem ipsum dolor sit amet, consectetur adipisicing elit. Veniam repellendus odit tempora iusto ea distinctio, illum. Accusamus fugiat, soluta optio!' });\n\twiki.push({ name: 'Blockquotes', body: '# Markdown\\n> Blockquote works correctly\\n\\n- list item 1\\n- list item 2\\n- list item 3' });\n\twiki.push({ name: 'Syntax highlighter', body: '```javascript\\nalert(\\'OK\\');\\n```' });\n\twiki.push({ name: 'Chart example', body: '# Great charts\\n```barchart\\nYear    |   Apples   |  Bananas   |   Oranges\\n2009    |   80       |  40        |   70\\n2010    |   40       |  50        |   300\\n2011    |   20       |  70        |   120\\n2012    |   100      |  50        |   80\\n2013    |   300      |  150       |   40\\n```\\n\\n# Line chart\\n\\n```linechart\\nYear    |   Apples   |  Bananas   |   Oranges\\n2009    |   80       |  40        |   70\\n2010    |   40       |  50        |   300\\n2011    |   20       |  70        |   120\\n2012    |   100      |  50        |   80\\n2013    |   300      |  150       |   40\\n```' });\n\twiki.push({ name: 'Video', body: '```video\\nhttps://www.youtube.com/watch?v=FyIf2ZiYfT8\\n```' });\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/window","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/window/","markdown_url":"https://forge.totaljs.es/en/components/window.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"5481e55f21840883061f4526d83ec4ebf61aa8b6e712a9cb7d30d202c6a3c331","checked_at":"2026-10-20T12:24:29.100Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Window","description":"A new version of this component supports dynamic evaluation of the content of <script type='text/html'>. The example below contains a script with HTML and the component evaluates the content if the j-","messages":[{"role":"user","content":"j-Window\n\nA new version of this component supports dynamic evaluation of the content of <script type='text/html'>. The example below contains a script with HTML and the component evaluates the content if the j-"},{"role":"assistant","content":"- The component moves the content of this component under the `<body>` tag (because of positioning)\n\n\n__Configuration__:\n\n- `title {String}` window title\n- `if {String}` condition for a showing of the window, it's compared with the value within of `path`\n- `icon {String}` Total.js icons\n- `reload {String}` link to a global `function(window_component)` and it's executed if the form is showing\n- `enter {Boolean}` (optional) captures the `enter` key automatically and performs submit (default: `false`)\n- `autofocus {Boolean/String}` (optional) can focus an input. `String` === `jQuery selector` for the input\n- `default {String}` (optional) a short alias for `DEFAULT(default, true)`\n- `closebutton {Boolean}` (optional) can hide `x` button (default: `false`)\n- `zindex {Number}` (optional) can affect z-index (default: `12`)\n- `scrollbar {Boolean}` enables a custom scrollbar (default: `true`)\n- `scrollbarshadow {Boolean}` optional, enables a shadow in scrollbar (default: `false`)\n- __NEW__ `closeesc {Boolean}` closes the form when the user presses the `ESC` key\n- __NEW__ `independent {Boolean}` closing doesn't depend on the condition according to the path\n\n__Good to know__:\n\nA new version of this component supports dynamic evaluation of the content of `<script type=\"text/html\">`. The example below contains a script with HTML and the component evaluates the content if the j-Window will be displayed (only once).\n\n```html\n<ui-component name=\"window\" path=\"path\" config=\"config\">\n\t<script type=\"text/html\">\n\t\tA CONTENT\n\t</script>\n</ui-component>\n```\n\nUse it with `<ui-component name=\"window\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"window\" path=\"common.window\" config=\"if:user;title:Title of this window\" class=\"hidden\">\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n</ui-component>\n\n<script>\n\tvar common = {};\n\tcommon.window = 'user';\n</script>\n```"}],"knowledge":{"content_sha256":"730f710e05f3006432f07ccd84134fa1d4caa0baaf5d4d96b1e4cd1ded038d8b","source_sha256":"5481e55f21840883061f4526d83ec4ebf61aa8b6e712a9cb7d30d202c6a3c331","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","CONTENT","DEF","DEFAULT","ESC","HTML","MIT","SOME","Total.js"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"window\" path=\"common.window\" config=\"if:user;title:Title of this window\" class=\"hidden\">\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n\t<br />\n\t<div style=\"padding:20px\">SOME CONTENT</div>\n</ui-component>\n\n<script>\n\tvar common = {};\n\tcommon.window = 'user';\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/windows","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/windows/","markdown_url":"https://forge.totaljs.es/en/components/windows.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"0be29f642691ffd0faac17e2025f10244ae080e50c47a2911e1ad77752284575","checked_at":"2026-10-20T12:24:29.986Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Windows","description":"The windows component expects an Array of objects.","messages":[{"role":"user","content":"j-Windows\n\nThe windows component expects an Array of objects."},{"role":"assistant","content":"__Configuration__:\n\n- `menuicon {String}` a menu icon in the window title bar (default `ti ti-navicon`)\n- `reoffsetresize {Boolean}` all windows will be re-offset according to the `main` window dimension (default: `true`)\n- `zindex {Number}` a start z-index positioning (default: `5`)\n- __NEW__: `maximizemargin {String}` a margin for the maximized state in the form `left_px top_px width_px height_px` (default: `0 0 0 0`)\n\nThe `windows` component expects an `Array` of objects.\n\n```javascript\n// Component declaration\nvar win = {\n\n\t// Component ID\n\tid: 'WINDOW_ID',\n\n\t// NEW: cachekey for similar windows\n\t// Optional, default: window ID\n\tcachekey: 'WINDOW_ID',\n\n\t// Positioning\n\t// You can define a specific display size and if the display size is not specified then the window tries to find a size for a larger display\n\thtml: 'THE CONTENT OF WINDOW', // or can be raw HTMLElement\n\n\toffset: { x: 200, y: 100, width: 600, height: 400, minwidth: 200, minheight: 200, maxwidth: 1000, maxheight: 1000, maximized: false }, // minwidth, maxwidth, maxheight, minheight and maximized are optional\n\n\t// Prevents hidden state for \"hide: true\" option\n\t// hidden: false,\n\n\t// Default settings\n\tactions: { move: true, close: true, hide: true, resize: true, menu: false, minimize: true, maximize: true, autosave: false },\n\t// GOOD TO KNOW:\n\t// \"undefined\" close or \"close:false\" and \"hide:true\" don't destroy the instance of window (it will be hidden only)\n\n\t// A window title\n\ttitle: 'Title for the window',\n\n\t// Handlers (optional):\n\tdestroy: function() {\n\t\t// @el {jQuery} a content of the window\n\t\t// @this {Instance}\n\n\t\t// is executed when the window is destroyed\n\t},\n\n\tmake: function(el) {\n\n\t\t// @el {jQuery} a content of the window\n\t\t// @this {Instance}\n\n\t\t// this.meta {Object} a reference to the window object\n\t\t// this.element {jQuery} a content element of the window\n\t\t// this.container {jQuery} a main content element of the window\n\t\t// this.main {Object} a reference to the \"Windows\" component\n\t\t// this.width {Number} a width of the window\n\t\t// this.height {Number} a height of the content of the window\n\t\t// this.x {Number} offset X\n\t\t// this.y {Number} offset Y\n\n\t\t// Methods:\n\t\t// this.setsize(width, height);\n\t\t// this.setcommand(cmd); Allowed commands: \"close\", \"maximize\", \"resetmaximize\", \"togglemaximize\", \"minimize\", \"resetminimize\", \"toggleminimize\", \"resize\", \"move\", \"focus\"\n\t\t// this.setoffset(x, y);\n\n\t\t// is executed when the window is making\n\t},\n\n\tmenu: function(el) {\n\t\t// @el {jQuery} a content element of the window\n\n\t\t// is executed when the user clicks on the menu icon in the window title bar\n\t},\n\n\tdata: function(type, data, el) {\n\n\t\t// @type {String}\n\t\t// @data {Object}\n\t\t// @el {jQuery} a content element of the window\n\t\t// @this {Instance}\n\n\t\t// is executed when data are sent via SETTER('windows', 'send', 'TYPE', 'DATA')\n\t},\n\n\tresize: function(width, height, el, x, y) {\n\n\t\t// @width {Number}\n\t\t// @height {Number}\n\t\t// @el {jQuery} a content of the window\n\t\t// @this {Instance}\n\n\t\t// is executed when the window is resized\n\t},\n\n\tmove: function(x, y, el) {\n\n\t\t// @x {Number}\n\t\t// @y {Number}\n\t\t// @el {jQuery} a content of the window\n\t\t// @this {Instance}\n\n\t\t// is executed when the window is moved\n\t},\n\n\tservice: function(counter, el) {\n\n\t\t// @counter {Number} count of calls\n\t\t// @el {jQuery} a content of the window\n\t\t// @this {Instance}\n\n\t\t// the component will execute this handler each 5 seconds\n\t},\n\n\t// Optional, this delegate rewrites built-in functionality\n\tminimize: function(type) {\n\t\t// @type {String} optional, \"reset\", \"toggle\" or undefined (it means minimize right now)\n\t},\n\n\t// Optional, this delegate rewrites built-in functionality\n\tmaximize: function(type) {\n\t\t// @type {String} optional, \"reset\", \"toggle\" or undefined (it means maximize right now)\n\t},\n\n\tfocus: function() {\n\t\t// window is focused\n\t}\n};\n\nPUSH('windows', win);\n```\n\n__Methods__:\n\n- `component.finditem(window_id)` finds the specific window according to `ID`\n- `component.show(window_id)` shows a window\n- `component.hide(window_id)` hides a window\n- `component.toggle(window_id)` toggles a window\n- `component.focus(window_id)` focuses a window\n- `component.hide(window_id)` closes a window\n- __NEW__: `component.title(window_id, title)` renames title\n\nUse it with `<ui-component name=\"windows\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\tbody{padding:0;margin:0;overflow:hidden;}\n</style>\n\n<ui-component name=\"windows\" path=\"mywindows\"></ui-component>\n\n<script>\n\n\tvar mywindows = [];\n\n\tmywindows.push({\n\t\tid: 'A',\n\t\toffset: { x: 200, y: 100, width: 600, height: 400, minwidth: 200, minheight: 200 },\n\t\ttitle: 'A',\n\t\thtml: '<div class=\"padding\">Lorem ipsum dolor sit amet.</div>',\n\t\tactions: { minimize: true, maximize: true, move: true, resize: true, close: true, menu: true },\n\t\tmenu: function(el) {\n\t\t\talert('MENU');\n\t\t}\n\t});\n\n\tmywindows.push({\n\t\tid: 'B',\n\t\toffset: { x: 600, y: 300, width: 600, height: 400, minwidth: 200, minheight: 200 },\n\t\ttitle: 'B',\n\t\thtml: '<div class=\"padding\">Lorem ipsum dolor sit amet.</div>',\n\t\tactions: { minimize: true, maximize: false, move: true, resize: true, close: true }\n\t});\n\n</script>\n```"}],"knowledge":{"content_sha256":"770d41a849c326b62e7ab9f340d501d34fa073200abe0cc34d5755cc51e710c8","source_sha256":"0be29f642691ffd0faac17e2025f10244ae080e50c47a2911e1ad77752284575","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","CONTENT","DATA","DEF","GOOD","HTMLE","KNOW","MENU","MIT","NEW","PUSH","SETTER","THE","TYPE","WINDOW","WINDOW_ID"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\tbody{padding:0;margin:0;overflow:hidden;}\n</style>\n\n<ui-component name=\"windows\" path=\"mywindows\"></ui-component>\n\n<script>\n\n\tvar mywindows = [];\n\n\tmywindows.push({\n\t\tid: 'A',\n\t\toffset: { x: 200, y: 100, width: 600, height: 400, minwidth: 200, minheight: 200 },\n\t\ttitle: 'A',\n\t\thtml: '<div class=\"padding\">Lorem ipsum dolor sit amet.</div>',\n\t\tactions: { minimize: true, maximize: true, move: true, resize: true, close: true, menu: true },\n\t\tmenu: function(el) {\n\t\t\talert('MENU');\n\t\t}\n\t});\n\n\tmywindows.push({\n\t\tid: 'B',\n\t\toffset: { x: 600, y: 300, width: 600, height: 400, minwidth: 200, minheight: 200 },\n\t\ttitle: 'B',\n\t\thtml: '<div class=\"padding\">Lorem ipsum dolor sit amet.</div>',\n\t\tactions: { minimize: true, maximize: false, move: true, resize: true, close: true }\n\t});\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/wizardexpert","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/wizardexpert/","markdown_url":"https://forge.totaljs.es/en/components/wizardexpert.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"d061bbcdc9d3c35cd2a8a5045a011d77a0e2b0fe9b5186713cb25469a38ad74d","checked_at":"2026-10-20T12:24:30.722Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-WizardExpert","description":"A simple solution for making of advanced wizards. Wizard downloads steps (as HTML parts) according to the schema. Steps are wrapped into custom scopes, so they are under validation.","messages":[{"role":"user","content":"j-WizardExpert\n\nA simple solution for making of advanced wizards. Wizard downloads steps (as HTML parts) according to the schema. Steps are wrapped into custom scopes, so they are under validation."},{"role":"assistant","content":"A simple solution for making of advanced wizards. Wizard downloads steps (as HTML parts) according to the schema. Steps are wrapped into custom scopes, so they are under validation.\n\n__Configuration__\n\n- `output {String}` a path for output, the object will contain entire output (steps will be properties)\n- `exec {String}` a path to `method(output_object)`, it's executed if wizard doesn't contain any next step\n- `next {String}` a path, here will be assigned next step with `enabled {Boolean}` property\n- `back {String}` a path, here will be assigned previous step with `enabled {Boolean}` property\n- `validate {Boolean}` enables validation (default: `true`)\n\n__Schema__:\n\n```js\n{\n\t'step1': { url: 'step1.html', next: 'step2', validate: false },\n\t'step2': { url: 'step2.html', next: 'step3' },\n\t'step3': { url: 'step3.html' }\n}\n```\n\n- component extends each step by adding new properties `enabled {Boolean}`, `id {String}`, `prev {String}`\n\nUse it with `<ui-component name=\"wizardexpert\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<style>\n\tbutton { background-color: white; border: 1px solid #E0E0E0; font-size: 12px; padding: 5px 15px; }\n\tbutton:hover { background-color: #F8F8F8; }\n\tbutton:disabled { background-color: #F0F0F0; cursor: not-allowed; color: #A0A0A0; }\n</style>\n\n<div style=\"border:1px solid #E0E0E0;width:300px;height:150px;padding:20px;margin-bottom:20px\">\n\t<ui-component name=\"wizardexpert\" path=\"currentstep\" config=\"output:currentoutput;next:nextstep;back:prevstep;exec:steps_finish\">\n\t\t<script type=\"text/plain\">\n\t\t\t{\n\t\t\t\t'step1': { url: 'step1.html', next: 'step2', validate: false },\n\t\t\t\t'step2': { url: 'step2.html', next: 'step3' },\n\t\t\t\t'step3': { url: 'step3.html' }\n\t\t\t}\n\t\t</script>\n\t</ui-component>\n\n</div>\n\n<nav>\n\t<ui-component name=\"validation\" path=\"outputdata\">\n\t\t<button is=\"is-button\" path=\"prevstep\" config=\"enabled:value && value.enabled;click:steps_back\" disabled>Back</button>\n\t\t<button is=\"is-button\" path=\"nextstep\" config=\"enabled:!value||value.enabled;html:value?'Next':'Finish';click:steps_next\" disabled>Next</button>\n\t</ui-component>\n</nav>\n\n\n<script type=\"text/html\" id=\"step1\">\n\tWELCOME\n</script>\n\n<script type=\"text/html\" id=\"step2\">\n\t<ui-component name=\"input\" path=\"?.name\" config=\"required:1\">Type your name</ui-component>\n</script>\n\n<script type=\"text/html\" id=\"step3\">\n\tFINISH\n</script>\n\n<script>\n\n\tvar currentstep = 'step1';\n\tvar currentoutput = {};\n\n\tON('request', function(options) {\n\t\t// Hijack response\n\t\tif (options.url.indexOf('step') !== -1)\n\t\t\toptions.respond($('#' + options.url.replace('.html', '')).html());\n\t});\n\n\tfunction steps_next() {\n\t\tSET('currentstep', 'NEXT');\n\t}\n\n\tfunction steps_back() {\n\t\tSET('currentstep', 'BACK');\n\t}\n\n\tfunction steps_finish() {\n\t\talert('DONE: ' + STRINGIFY(currentoutput));\n\t}\n\n</script>\n```"}],"knowledge":{"content_sha256":"32f05cee4e8480f9ef6843570b61f8f37273b8988cfe56129d33dc872a8ed121","source_sha256":"d061bbcdc9d3c35cd2a8a5045a011d77a0e2b0fe9b5186713cb25469a38ad74d","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["A0A0A0","BACK","CDN","DEF","DONE","E0E0E0","F0F0F0","F8F8F8","FINISH","HTML","MIT","NEXT","SET","STRINGIFY","WELCOME"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<style>\n\tbutton { background-color: white; border: 1px solid #E0E0E0; font-size: 12px; padding: 5px 15px; }\n\tbutton:hover { background-color: #F8F8F8; }\n\tbutton:disabled { background-color: #F0F0F0; cursor: not-allowed; color: #A0A0A0; }\n</style>\n\n<div style=\"border:1px solid #E0E0E0;width:300px;height:150px;padding:20px;margin-bottom:20px\">\n\t<ui-component name=\"wizardexpert\" path=\"currentstep\" config=\"output:currentoutput;next:nextstep;back:prevstep;exec:steps_finish\">\n\t\t<script type=\"text/plain\">\n\t\t\t{\n\t\t\t\t'step1': { url: 'step1.html', next: 'step2', validate: false },\n\t\t\t\t'step2': { url: 'step2.html', next: 'step3' },\n\t\t\t\t'step3': { url: 'step3.html' }\n\t\t\t}\n\t\t</script>\n\t</ui-component>\n\n</div>\n\n<nav>\n\t<ui-component name=\"validation\" path=\"outputdata\">\n\t\t<button is=\"is-button\" path=\"prevstep\" config=\"enabled:value && value.enabled;click:steps_back\" disabled>Back</button>\n\t\t<button is=\"is-button\" path=\"nextstep\" config=\"enabled:!value||value.enabled;html:value?'Next':'Finish';click:steps_next\" disabled>Next</button>\n\t</ui-component>\n</nav>\n\n\n<script type=\"text/html\" id=\"step1\">\n\tWELCOME\n</script>\n\n<script type=\"text/html\" id=\"step2\">\n\t<ui-component name=\"input\" path=\"?.name\" config=\"required:1\">Type your name</ui-component>\n</script>\n\n<script type=\"text/html\" id=\"step3\">\n\tFINISH\n</script>\n\n<script>\n\n\tvar currentstep = 'step1';\n\tvar currentoutput = {};\n\n\tON('request', function(options) {\n\t\t// Hijack response\n\t\tif (options.url.indexOf('step') !== -1)\n\t\t\toptions.respond($('#' + options.url.replace('.html', '')).html());\n\t});\n\n\tfunction steps_next() {\n\t\tSET('currentstep', 'NEXT');\n\t}\n\n\tfunction steps_back() {\n\t\tSET('currentstep', 'BACK');\n\t}\n\n\tfunction steps_finish() {\n\t\talert('DONE: ' + STRINGIFY(currentoutput));\n\t}\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/wysiwyg","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/wysiwyg/","markdown_url":"https://forge.totaljs.es/en/components/wysiwyg.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"44b959a03d116c3044d21da78c5c1e05dfba4fe9c164f767bf8ff83ef7f9487f","checked_at":"2026-10-20T12:24:31.600Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Wysiwyg","description":"j-Wysiwyg: supports dark mode","messages":[{"role":"user","content":"j-Wysiwyg\n\nj-Wysiwyg: supports dark mode"},{"role":"assistant","content":"- supports __dark mode__\n\n__Configuration__:\n\n- `placeholder {String}` optional\n- `code {Boolean}` enables/disables word highlighting (default: `true`)\n- `links {Boolean}` enables/disables links (default: `true`)\n- `ul {Boolean}` enables/disables unordered list (default: `true`)\n- `required {Boolean}` optional, enables \"required\" mode (default: `false`)\n- `allowrawpaste {Boolean}` optional, enables raw pasting rich content (default: `false`)\n\nUse it with `<ui-component name=\"wysiwyg\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<div class=\"container\">\n\t<div class=\"row\">\n\t\t<div class=\"col-md-6\">\n\t\t\t<ui-component name=\"wysiwyg\" path=\"myhtml\" config=\"placeholder:Type something\"></ui-component>\n\t\t</div>\n\t</div>\n</div>\n```"}],"knowledge":{"content_sha256":"ec88b562d940b426d5d56a479bf779a25130f9047d08fcaff580d3a5861462eb","source_sha256":"44b959a03d116c3044d21da78c5c1e05dfba4fe9c164f767bf8ff83ef7f9487f","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","MIT"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<div class=\"container\">\n\t<div class=\"row\">\n\t\t<div class=\"col-md-6\">\n\t\t\t<ui-component name=\"wysiwyg\" path=\"myhtml\" config=\"placeholder:Type something\"></ui-component>\n\t\t</div>\n\t</div>\n</div>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/xterm","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/xterm/","markdown_url":"https://forge.totaljs.es/en/components/xterm.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":true,"verification":"executed against jcomponent@20","verification_status":"executed","verification_evidence":{"source":"/validation.json","content_sha256":"435df1efdd2b151992b0c890216352019bfb969a0d6b0da4d2d8d77f82702318","checked_at":"2026-10-20T12:24:33.019Z","framework":"jcomponent@20","node":"v24.14.1","browser":"156.0.8078.4","test_kind":"component-browser","scope":"Component startup, ready instances and absence of page errors; not full functional coverage.","attempts":1},"title":"j-Xterm","description":"The component renders a terminal output/input directly in the web browser. IMPORTANT: the example doesn't work as you expect, it needs a real terminal output.","messages":[{"role":"user","content":"j-Xterm\n\nThe component renders a terminal output/input directly in the web browser. IMPORTANT: the example doesn't work as you expect, it needs a real terminal output."},{"role":"assistant","content":"The component renders a terminal output/input directly in the web browser. __IMPORTANT__: the example doesn't work as you expect, it needs a real terminal output.\n\n\n__Configuration__:\n\n- `cols {Number}` count of cols (default: `80`)\n- `rows {Number}` count of rows (default: `24`)\n- `send {String}` a link to the `function(data)` for the communication with the server\n\n__Methods__:\n\n- `instance.write(data)` writes data into the terminal component\n\n__Used dependencies__:\n\n- [Xterm.js](https://xtermjs.org/)\n- [Full example](https://github.com/totaljs/examples/tree/master/remote-terminal)\n\nUse it with `<ui-component name=\"xterm\">`. When the component is not loaded yet, jComponent downloads it on first use from the address in `DEF.fallback` (the Componentator CDN by default). Author: Peter Širka. Source: the Componentator components repository, MIT license.\n\n```html example.html\n<ui-component name=\"xterm\" config=\"send:send_to_server\"></ui-component>\n\n<script>\n\n\tfunction send_to_server(data) {\n\n\t\t// FAKE:\n\t\t// the below code will append the data directly into the terminal\n\t\tSETTER('xterm/write', data);\n\n\t}\n\n</script>\n```"}],"knowledge":{"content_sha256":"f43d26911d2d7591ffed65de842c3eac83811581e34b71fc59e53c6d77baff11","source_sha256":"435df1efdd2b151992b0c890216352019bfb969a0d6b0da4d2d8d77f82702318","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"not-recorded","symbols":["CDN","DEF","FAKE","MIT","SETTER"],"language":"en"},"files":[{"path":"example.html","language":"html","content":"<ui-component name=\"xterm\" config=\"send:send_to_server\"></ui-component>\n\n<script>\n\n\tfunction send_to_server(data) {\n\n\t\t// FAKE:\n\t\t// the below code will append the data directly into the terminal\n\t\tSETTER('xterm/write', data);\n\n\t}\n\n</script>"}],"test":null,"test_kind":"component-browser"}
{"id":"components/permissionsop","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/permissionsop/","markdown_url":"https://forge.totaljs.es/en/components/permissionsop.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"j-PermissionsOP","description":"Edit role, group and user permissions for OpenPlatform applications.","messages":[{"role":"user","content":"j-PermissionsOP\n\nEdit role, group and user permissions for OpenPlatform applications."},{"role":"assistant","content":"The component edits OpenPlatform user, role and group permissions. Bind the selected permissions to a model path and configure `find` and `read` endpoints. The model contains permission strings:\n\n```\n['C@admin', 'U@admin', 'R@admin', 'D@admin', 'R123456', 'R#employees']\n```\n\n- first letter means allowed type, defined in `types`\n- `@admin` means a role `admin`\n- `#employees` means a group `employees`\n- `123456` means a specific `userid`\n\n__Configuration__:\n\n- `placeholder` {String} a placeholder for `j-Directory` component (default: `Search`)\n- `types` {String} types of permission (array delimited by char, default: `CRUD`)\n- `disabled` {Boolean} optional, disables this component\n- `default` {String} default value of `type` (default: `R`)\n- `dirkey` {String} optional, a key for text in `j-Directory` (default: `name`)\n- `labelrole` {String} optional a label for roles (default: `Role`)\n- `labelgroup` {String} optional a label for groups (default: `Group`)\n- `limit` {Number} a count of allowed permissions (default: `0` = unlimited)\n- `pk` {String} a primary key which value will be binded into the model (default: `id`)\n- __IMPORTANT__ `find` {String} URL address for finding roles, groups and users, the component sends `url?q=SEARCH_QUERY` (it sends empty value)\n- __IMPORTANT__ `read` {String} URL address for reading roles, groups and users, the component sends `url?id=@role,#group,userid,...`\n\n```javascript\n// The component expects data:\n['C@admin', 'U@admin', 'R@admin', 'D@admin', 'R123456', 'R#employees'];\n\n// The component tries to find data:\n// The component tries to read data and it pairs them with the model automatically:\n[{ id: '123456', name: 'Peter Širka' }, { id: '@admin', name: 'Administrators' }, { id: '#employees', name: 'Employees' }];\n```\n\n## Usage\n\n```html\n<ui-component name=\"directory\"></ui-component>\n<ui-component name=\"permissionsop\" path=\"form.permissions\"\n  config=\"find:/api/permissions/search/;read:/api/permissions/read/;types:CRUD\"></ui-component>\n<script>\n  ON('request', function(request) {\n    if (request.method === 'GET' && request.data &&\n        request.url.indexOf('/api/permissions/') === 0) {\n      request.url = QUERIFY(request.url, request.data);\n      request.data = null;\n    }\n  });\n  SET('form.permissions', ['R@admin', 'U#employees']);\n</script>\n```\n\nDeclare `j-Directory` for the selection menu. Both endpoints return an array of objects with `id` and `name` strings. The `find` endpoint receives the search text in `q`. The `read` endpoint receives comma-separated identifiers in `id` and resolves the permissions already in the model. Use `pk` to choose the identifier field; its default is `id`. Prefix role identifiers with `@` and group identifiers with `#`.\n\nSource: [Total.js components](https://github.com/totaljs/components/tree/910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e/j-PermissionsOP). Copyright Peter Širka and contributors. [MIT license](/knowledge-licenses.json)."}],"knowledge":{"content_sha256":"5cebc2e3f5ed7ea0434bf6c6bd5019dc5b3c8630b1a2620c34d60c7a6de0af79","source_sha256":"04758c08ae35e0432d3976fd4e6ffc19392a42e55419f8cf8fe8c97e6b0d237e","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"usage-reviewed","symbols":["CRUD","GET","MIT","QUERIFY","R123456","SEARCH_QUERY","SET","Total.js","URL"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"components/uistudio","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/uistudio/","markdown_url":"https://forge.totaljs.es/en/components/uistudio.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"j-UIStudio","description":"Render a Flow UI Studio application.","messages":[{"role":"user","content":"j-UIStudio\n\nRender a Flow UI Studio application."},{"role":"assistant","content":"The component loads a Flow UI Studio application through its configured endpoint. Set url to that endpoint and handle metadata or application updates with onmeta and onapp.\n\nThis component renders the app according to the Total.js Flow UI Studio schema.\n\n\n__Configuration__:\n\n- `url {String}` the navigation endpoint that accepts POST requests and returns JSON step metadata\n- `css {Boolean}` applies app style to the current element (default: `true`)\n- `ssid {String}` optional, adds a session identifier to the URL address for the Flow\n- `title {Boolean}` optional, rewrites `document.title` by the app name\n- `plus {String}` optional, it works only with `title:true` and it adds `title - config.plus`\n- `onmeta {String}` optional, a link to the `function(meta)` captures a meta data\n- `onapp {String}` optional, a link to the `function(app)` captures a new UI design\n- `origin {String}` optional\n- `onrequest {String}` optional, a link to the `function(payload)` can modify request data\n- `flowoutput {Boolean}` optional, enables outputs only from FlowOutput component (default: `true`)\n\n__Properties__:\n\n- `component.app` contains an `app` instance\n\n__Good to know__:\n\nThe component extends the `app` instance by adding `breadcrumb {Array}` property.\n\n## Usage\n\n```html\n<ui-component name=\"uistudio\"\n  config=\"url:/api/studio/;onapp:studioLoaded;loading:false\"></ui-component>\n<script>\n  function studioLoaded(app) {\n    console.log(app);\n  }\n</script>\n```\n\n## Navigation endpoint\n\nThe component sends a POST request to the configured endpoint. The request carries the current navigation state, including `origin`, `query`, an optional `ssid`, and the current `id`, `output` and `data` when an application emits an output.\n\nReturn an `id` for the step and a `url` for its exported UI Builder design:\n\n```json\n{\n  \"id\": \"home\",\n  \"url\": \"/ui/home.json\",\n  \"data\": {}\n}\n```\n\nServe the design exported from UI Builder at `/ui/home.json`. The component downloads it with GET and creates the application through `UIBuilder.build()`. If the response includes an `input`, the component calls the application input with `input` and `data`. The `onmeta` callback receives the step response, and `onapp` receives the created application instance.\n\n`loading:false` disables the loading indicator. To display it, declare a `j-Loading` component and enable `loading`.\n\nSource: [Total.js components](https://github.com/totaljs/components/tree/910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e/j-UIStudio). Copyright Peter Širka and contributors. [MIT license](/knowledge-licenses.json)."}],"knowledge":{"content_sha256":"a5e5ce2d319ac83c9ff4cd666dee3ad9c188b842feff9e5995385fbeaa3e3f78","source_sha256":"679e59d3a7c61342a66c48a07cb070ee323038446eb5afdec7c4cb46f2b85dee","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"usage-reviewed","symbols":["GET","JSON","MIT","POST","Total.js","UIB","UIS","URL"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"components/floweditor","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/floweditor/","markdown_url":"https://forge.totaljs.es/en/components/floweditor.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"j-FlowEditor","description":"Open the Flow designer for a FlowStream WebSocket endpoint.","messages":[{"role":"user","content":"j-FlowEditor\n\nOpen the Flow designer for a FlowStream WebSocket endpoint."},{"role":"assistant","content":"The component opens the Flow designer in an iframe. Bind its path to the WebSocket URL of the FlowStream to edit. Set the path to null to close the designer.\n\nThe component opens Flow editor in the iframe.\n\n- Flow editor/designer repository <https://github.com/totaljs/flowdesigner> (if you don't want to use cloud version)\n\n__Configuration__:\n\n- `url {String}` a link to the Flow editor (default: `https://flow.totaljs.com`)\n- `top {Number}` default: `1`\n- `left {Number}` default: `1`\n- `right {Number}` default: `0`\n- `bottom {Number}` default: `0`\n- `zindex {Number}` default: `30`\n- `language {String}` default: `eu` (english)\n- `closebutton {Boolean}` default: `true`\n\n__Model__:\n\nIt must be a WebSocket URI to access the FlowStream.\n\n## Usage\n\n```html\n<ui-component name=\"floweditor\" path=\"designer.socket\"></ui-component>\n<script>\n  function openDesigner(socketURL) {\n    SET('designer.socket', socketURL);\n  }\n  function closeDesigner() {\n    SET('designer.socket', null);\n  }\n</script>\n```\n\nSource: [Total.js components](https://github.com/totaljs/components/tree/910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e/j-FlowEditor). Copyright Peter Širka and contributors. [MIT license](/knowledge-licenses.json)."}],"knowledge":{"content_sha256":"1c4735d504ec12b9c0f6b8946c1d8b8323ae948f951c6925f9fd24491963a7cc","source_sha256":"436c69598df31fff9ed3355f0f1648842c1bb34bbf6a3f8808592b36c3b58806","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"usage-reviewed","symbols":["MIT","SET","Total.js","URI","URL"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"components/uieditor","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/uieditor/","markdown_url":"https://forge.totaljs.es/en/components/uieditor.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"j-UIEditor","description":"Open UI Builder and receive saved designs.","messages":[{"role":"user","content":"j-UIEditor\n\nOpen UI Builder and receive saved designs."},{"role":"assistant","content":"The component opens UI Builder in an iframe. Bind its path to an object containing the design and callbacks for saving, publishing or previewing it. Set the path to null to close the editor.\n\nThe component opens UI Builder in the iframe.\n\n- UI Builder editor repository <https://github.com/totaljs/uibuilder> (if you don't want to use cloud version)\n\n__Configuration__:\n\n- `url {String}` a link to the UI Builder (default: `https://uibuilder.totaljs.com`)\n- `top {Number}` default: `0`\n- `left {Number}` default: `0`\n- `right {Number}` default: `0`\n- `bottom {Number}` default: `0`\n- `zindex {Number}` default: `30`\n- `language {String}` default: `eu` (english)\n- `onsave {String}` optional, a link to `function(data)`\n- `onpublish {String}` optional, a link to `function(data)`\n- `onrender {String}` optional, a link to `function(data)`\n- `closebutton {Boolean}` default: `true`\n\n__Model__:\n\n```js\n{\n\tdata: { .. UI BUILDER DESIGN .. },\n\tupload: 'https://.../upload/', // optional\n\tgroups: [{ id: 'group1', name: 'Group 1' }], // optional\n\tapps: [{ id: 'app1', name: 'App 1' }], // optional\n\tviews: [{ id: 'view1', name: 'View 1' }], // optional\n\tclose: function() {}, // optional, it's executed when the editor is closed\n\tsave: function(data) {}, // optional, it's executed when the user saves design\n\tpublish: function(data) {}, // optional, it's executed when the user compiles design\n\trender: function(data) {}, // optional, it's executed when the user wants to preview a design\n}\n```\n\n## Usage\n\n```html\n<ui-component name=\"uieditor\" path=\"editor.current\"></ui-component>\n<script>\n  function openDesign(design) {\n    SET('editor.current', {\n      data: design,\n      save: function(updatedDesign) {\n        SET('form.design', updatedDesign);\n      }\n    });\n  }\n</script>\n```\n\nSource: [Total.js components](https://github.com/totaljs/components/tree/910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e/j-UIEditor). Copyright Peter Širka and contributors. [MIT license](/knowledge-licenses.json)."}],"knowledge":{"content_sha256":"c5382071ed0d83170e1897275e901dcc5989c57e5da4ddf341fb1deb6110470d","source_sha256":"b7dd1f627569ba4a8bbc75a11c20419e56fc18cc0b4911b392df5aa91d3e83f3","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"usage-reviewed","symbols":["BUILDER","DESIGN","MIT","SET","Total.js","UIE"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"components/serviceworker","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/serviceworker/","markdown_url":"https://forge.totaljs.es/en/components/serviceworker.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"j-ServiceWorker","description":"Cache SPA assets and an offline fallback with a service worker.","messages":[{"role":"user","content":"j-ServiceWorker\n\nCache SPA assets and an offline fallback with a service worker."},{"role":"assistant","content":"The component registers /sw-jcomponent.js and sends it the asset list, cache version and offline fallback from its datasource. Serve the application over HTTPS or localhost, and place the worker at the root URL so it can handle the application pages.\n\n- This component is a little bit special. First of all, you need put the code below into file e.g. `sw-jcomponent.js` and place it to the `public` (e.g. `myproject/public/sw-jcomponent.js`)\n\n```js\nvar prefix='jc';function hash(s,unsigned){var hash=0,i,char;if(!s.length)return hash;var l=s.length;for(i=0;i<l;i++){char=s.charCodeAt(i);hash=((hash<<5)-hash)+char;hash|=0;}return unsigned?hash>>>0:hash}self.addEventListener('install',()=>{self.skipWaiting()});self.addEventListener('message',function(e){if(e.data.action==='jc-sw'){var data=e.data.data;data.name=prefix+'-'+data.version+'-'+hash(data.assets.toString(),true);addcache(e.data.data)}});self.addEventListener('fetch',function(e){e.respondWith(fetch(e.request).catch(function(){return caches.match(e.request).then(function(response){if(response)return response;if(e.request.mode==='navigate'||(e.request.method==='GET'&&e.request.headers.get('accept').includes('text/html')))return caches.match('jc-offline-fallback')})}))});function addcache(data){caches.open(data.name).then(function(cache){if(data.assets&&data.assets.length)cache.addAll(data.assets);if(data.fallback){fetch(data.fallback,{mode:'no-cors'}).then(function(response){return cache.put('jc-offline-fallback',response)})}}).then(function(){caches.keys().then(function(keys){return Promise.all(keys.map(function(key){if(data.name!==key)return caches.delete(key)}))})})}\n```\n\n- The component will communicate with this _service worker_ code.\n\n__Configuration__:\n\n- `expire {String}` expiration data from users storage (default: `1 day`) __we don't recommend changing this__\n- `debug {Boolean}` enables `debug` mode. It'll refresh assets every reload (so you don't need to change the version) (default: `false`)\n- `datasource {String}` path to data-source (data-source is a object)\n\n__Datasource structure__:\n\n```js\n{\n\tversion: '1.00',\n\tfallback: '/',\n\tassets: [\n\t\t'/js/default.js'\n\t]\n}\n```\n\n__Good to know__:\n- __PATH__ can be used as dynamic version. It'll refresh assets instantly (without page reloading) e.g. `SET('version', '1.35')`\n- After add/remove something from assets, you doesn't need to increase version\n- __fallback__ performs offline version of html. e.g. `fallback: '/offline.html'`\n- Assets can be everything: fonts, partial pages, css files, javascript files\n- __It's work only in Single Page Applications like [this](https://github.com/totaljs/spa) or [this](https://github.com/totaljs/emptyproject-pwa)__\n\n__Usage__:\n\n```html\n<ui-component name=\"serviceworker\" path=\"version\" config=\"datasource:mysource\"></ui-component>\n\n<script>\n\tvar version = '1.34';\n\tvar mysource = {\n\t\tversion: version,\n\t\tfallback: '/',\n\t\tassets: [\n\t\t\t'/pages/settings.html',\n\t\t\t'/pages/articles.html',\n\t\t\t'/pages/dashboard.html'\n\t\t]\n\t};\n</script>\n```\n\nSource: [Total.js components](https://github.com/totaljs/components/tree/910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e/j-ServiceWorker). Copyright Denis Granec. [MIT license](/knowledge-licenses.json)."}],"knowledge":{"content_sha256":"db7ce63bfad9ca3799e0164bf5dfdebe02eeade49ceedf536d1bb2e934d2ad6b","source_sha256":"bcb02e00f86e6218d87ce449220272ef7276d3fc5ff6f11e19a613f43e1b7368","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"usage-reviewed","symbols":["GET","HTTPS","MIT","SET","Total.js","URL"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"components/paper","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/paper/","markdown_url":"https://forge.totaljs.es/en/components/paper.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"j-Paper","description":"Edit rich content as a list of widgets.","messages":[{"role":"user","content":"j-Paper\n\nEdit rich content as a list of widgets."},{"role":"assistant","content":"The component edits a document as an array of widget objects. Bind a model path to that array, configure the widget repository, and use autosave to write edits back to the model.\n\n- output is a simple `Object Array` structure:\n\t- `[{ id: String, widget: String, newbie: Boolean, changed: Boolean, config: Object }]`\n- widgets repository: https://github.com/totaljs/parts/tree/main/paper\n\n__Configuration__:\n\n- `readonly {Number}` enables/disables readonly mode `0=disabled`, `1=partially enabled`, `2=fully enabled`\n- `parent {String}` optional, a parent element for min-height (default: `null`)\n- `margin {Number}` additional margin for min-height (default: `0`)\n- `autosave {Boolean}` changes are serialized according to the path automatically\n- `widgets {String}` a link to the widgets (default: Componentator CDN)\n\t- it can contain multiple addresses divided by the comma `mywidget1.html,/mywidget2.html,etc..`\n\t- or you can keep [CDN structure](https://github.com/totaljs/parts/blob/main/cdn/paper/db.json)\n- `icon {String}` a link to the `function(opt, callback)`\n\t- `opt.element {jQuery element}`\n\t- __IMPORTANT__: call the `callback` argument if the icon has been changed in the element\n- `link {String}` a link to the `function(opt)`\n\t- `opt.element {jQuery element}`\n\t- `opt.href {String}`\n\t- `opt.target {String}`\n\t- `opt.widget {Object}` a widget instance\n- `command {String}` a link to the `function(cmd)`\n\t- `cmd.widget {Object}` a temporary widget instance\n\t- `cmd.widgets {Array}` a list of registered widgets\n\t- `cmd.remove() {Function}` removes widget\n\t- `cmd.append(name)` replaces the current temporary widget with a new widget (`name` name of widget)\n- `format {String}` a link to the `function(toolbar)`\n\t- `toolbar` can be `null`\n\t- `toolbar.element {jQuery element}` a current element\n\t- `toolbar.node {HTML element}` a current element\n\t- `toolbar.widget {Object}` a widget instance\n\t- `toolbar.style {Object}` contains a current style e.g. `bold: 1, link: 1`\n- `upload {String}` a link to the `function(opt, callback)`\n\t- `opt.accept {String}` optional\n\t- `opt.files {FileList}` optional\n\t- `opt.multiple {Boolean}` optional, default: `false`\n\t- `opt.width {Number}` optional\n\t- `opt.height {Number}` optional\n\t- __IMPORTANT__: `callback` argument must be executed after upload with the `files {Array}` argument\n- `change {String}` a link to the `function(change)`\n\t- `change.id {String}` identifier\n\t- `change.block {String}` a block identifier\n\t- `change.blockprev {String}` a previous block identifier\n\t- `change.widget {String}` a widget id\n\t- `change.type {String}` operation type (insert, update, remove)\n- `contextmenu {String}` a link to the `function(widget)` when the user pressed the right mouse button on the widget\n- `check {String}` a link to the `function(widget)` that decides whether to enable editing or not\n- `placeholder {String}` a placeholder for the default HTML widget (default: `Press / for adding rich content`)\n\n__Methods__:\n\n- `component.save(function(data, ischange) {` for saving content\n- `component.import(url or url_array, [callback])` for importing additional widgets\n- `component.notify(event, [a], [b], [c], [d])` emits event in every widget\n\n__Good to know__:\n\nThe component creates a global variable called `window.papercache {Object}` used by all `j-Paper` instances.\n\n- the component compares the content when you change data, so you can change data in real-time\n- the component assigns (in the form) `paper-yourwidgetname` class to every widget element\n- reserved HTML classes `readonly`, `widget`, `selected`\n\t- readonly usage: `.readonly .paper-yourwidgetname { ... }`\n- `htmlelement.$widget {Object}` contains a widget instance\n\t- `widget.remove()` method will remove the widget\n\n__Shortcuts in text edit__:\n\n- `CMD + m` highlights text\n- `CMD + l` creates a link\n- `CMD + p` adds an icon\n- `CMD + b` bold\n- `CMD + i` italic\n- `CMD + u` underline\n\n__Good to know__:\n\n- `[data-prevent]` attribute can prevent click event in the widget\n\n## Usage\n\n```html\n<ui-component name=\"paper\" path=\"form.widgets\"\n  config=\"autosave:true;widgets:/paper/db.json\"></ui-component>\n<script>\n  SET('form.widgets', []);\n  function saveDocument() {\n    SETTER('paper/save', function(widgets) {\n      console.log(widgets);\n    });\n  }\n</script>\n```\n\nServe the widget index at /paper/db.json and its widget files from your application. The save callback receives the serialized widget array.\n\nSource: [Total.js components](https://github.com/totaljs/components/tree/910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e/j-Paper). Copyright Peter Širka and contributors. [MIT license](/knowledge-licenses.json)."}],"knowledge":{"content_sha256":"27be8a8200fd381bf1d9ecd1c1215b31459d0476bb1e402451936dd3e34484f7","source_sha256":"cae5a56e3ea94c2aa37f08e93695d6398a7c987b8c9871bb82099afff43d6a7b","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"usage-reviewed","symbols":["CDN","CMD","HTML","MIT","SET","SETTER","Total.js"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"components/changer","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/changer/","markdown_url":"https://forge.totaljs.es/en/components/changer.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"j-Changer","description":"Track changes to a model value for form validation.","messages":[{"role":"user","content":"j-Changer\n\nTrack changes to a model value for form validation."},{"role":"assistant","content":"The component participates in jComponent change tracking for a model path. Use it for values updated by application code, such as a list of uploaded pictures, so that form validation can include their changed state.\n\n\n\n## Usage\n\n```html\n<ui-component name=\"changer\" path=\"form.pictures\"></ui-component>\n<script>\n  SET('form.pictures', []);\n  function addPicture(url) {\n    PUSH('form.pictures @change', { url: url });\n  }\n</script>\n```\n\nSource: [Total.js components](https://github.com/totaljs/components/tree/910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e/j-Changer). Copyright Peter Širka and contributors. [MIT license](/knowledge-licenses.json)."}],"knowledge":{"content_sha256":"a181a855e3cd544142498b950633e3f782e56ab391e5ee28cec862df5fe6e782","source_sha256":"b867fbb7757ff0257d85775901743c9ea832275c44a9af6f88e79f60f9f437ff","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"usage-reviewed","symbols":["MIT","PUSH","SET","Total.js"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"components/websocket","section":"components","kind":"component","framework":"jComponent 20","framework_version":"jcomponent@20","language":"en","url":"https://forge.totaljs.es/en/components/websocket/","markdown_url":"https://forge.totaljs.es/en/components/websocket.md","tags":["component"],"license":"Mixed: Forge prose under CC-BY-4.0; attributed upstream excerpts under MIT","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"j-WebSocket","description":"Send and receive JSON messages through a reconnecting WebSocket.","messages":[{"role":"user","content":"j-WebSocket\n\nSend and receive JSON messages through a reconnecting WebSocket."},{"role":"assistant","content":"The component sends and receives JSON through a reconnecting WebSocket. Configure `url`, bind a model path to receive messages, and call the `send` method to transmit objects.\n\n__Configuration__:\n\n- `url {String}` URL for websocket, it can be relative or absolute\n- `reconnect {Number}` can contain only a number (milliseconds, default: `3000`)\n- `encoder {Boolean}` performs encode/decode messages via `URI` (default: `false`)\n- __NEW__: `statsinterval {Number}` stats interval in milliseconds (default: `1000`)\n- __NEW__: `stats {String}` a link to method `function({ received: Number, sent: Number })` or path to a variable (measured in bytes)\n\n__Methods__:\n\n- `component.send(obj)` - sends JSON serialized object to the server\n- `component.close()` - closes connection\n- `component.connect()` - opens connection\n- `component.idletime(is)` - __NEW__ can cancel/reopen connection\n\n__Events__:\n\n- `ON('message', function(data) {})` - received data\n- `ON('online', function(isOnline) {})` - online/offline\n\n## Usage\n\n```html\n<ui-component name=\"websocket\" path=\"updates.latest\"\n  config=\"url:/updates/\"></ui-component>\n<script>\n  function sendUpdate(value) {\n    SETTER('websocket/send', { type: 'update', value: value });\n  }\n</script>\n```\n\nSource: [Total.js components](https://github.com/totaljs/components/tree/910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e/j-WebSocket). Copyright Peter Širka and contributors. [MIT license](/knowledge-licenses.json)."}],"knowledge":{"content_sha256":"411ad10cdc020aa516fc89fc6167c8788b819b6b49323d5caab3f4f64517115c","source_sha256":"b7e5fc0bed1e8f542be1514bd945928293aa3004a3e8b2a004ccbb9ee13ca99e","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[{"id":"components","url":"https://github.com/totaljs/components","revision":"910ed1a86f6e72b4f031d3ad0dc7844f9fc5cb8e","version":"jcomponent@20","license":"MIT","attribution":"Peter Sirka and Total.js contributors","manifest_sha256":"bdf9173e91c3ca768a75f621808a1fd4612d043de47cd64572e4985f65eaca79","license_files":[{"path":"LICENSE","sha256":"7c913772fcdc7041bbb0318f80a4c2ecb94a71408a644b9205411fb4dfb0454e","text":true,"characters":1098}],"scope":"Pinned reference; not necessarily the deployed runtime"}],"review":"usage-reviewed","symbols":["JSON","MIT","SETTER","Total.js","URI","URL"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"openplatform/what-is-openplatform","section":"openplatform","kind":"recipe","framework":"OpenPlatform","framework_version":"openplatform@5","language":"en","url":"https://forge.totaljs.es/en/openplatform/what-is-openplatform/","markdown_url":"https://forge.totaljs.es/en/openplatform/what-is-openplatform.md","tags":["openplatform","portal","apps","overview"],"license":"CC-BY-4.0","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"What OpenPlatform is: one portal for all your web applications","description":"OpenPlatform is the Total.js enterprise portal that runs, integrates and manages several web applications behind one sign-in, with users, groups, permissions and notifications.","messages":[{"role":"user","content":"What OpenPlatform is: one portal for all your web applications\n\nOpenPlatform is the Total.js enterprise portal that runs, integrates and manages several web applications behind one sign-in, with users, groups, permissions and notifications."},{"role":"assistant","content":"OpenPlatform is a ready-to-run **portal for your web applications**. Your people sign in once, see the applications they are allowed to use and open them from one place; the administrators manage users, groups, permissions and notifications from one screen. It is open source under the **MIT licence**, written with Total.js 5 and PostgreSQL, and the application is the repository [totaljs/openplatform](https://github.com/totaljs/openplatform) (version 5).\n\nThis section describes OpenPlatform (revision `5a54a85` of the repository).\n\n## What it gives you\n\n- **One sign-in for many applications.** An application can be one of your own Total.js apps or any web application: OpenPlatform opens it with a signed token and the application asks OpenPlatform who the user is (see [how an app connects](/en/openplatform/how-an-app-connects/)).\n- **Users, groups and permissions.** A user belongs to groups; a group decides which applications the user sees and which permission values each application receives (see [groups and permissions](/en/openplatform/groups-and-permissions/)).\n- **Notifications.** Applications send notifications to their users through a small HTTP endpoint; the portal shows them and can email a digest of the unread ones (see [notifications and the public API](/en/openplatform/notifications-and-public-api/)).\n- **Sessions the user controls.** Each user can see and remove their own sessions and change their password.\n- **A setup screen** for the administrators (`/setup/`): users, groups, applications, settings, feedback and a dashboard with the visitors.\n\n## How the project is organized\n\nOpenPlatform is a Total.js application with three plugins and a few controllers:\n\n| Part | What it does | Source |\n|---|---|---|\n| `login` plugin | the sign-in page, `POST /api/login/` and `POST /api/reset/` | `plugins/login/` |\n| `portal` plugin | the portal for signed-in users (`GET /*`) | `plugins/portal/` |\n| `setup` plugin | the administration screen and its API under `/setup/` | `plugins/setup/` |\n| account API | `/api/` actions for the session, the account, the apps, the notifications and the sessions of the user | `controllers/api.js`, `schemas/account.js` |\n| app protocol | `GET /verify/`, `POST /notify/`, `GET /session/` for the applications | `controllers/verify.js` |\n| public API | `GET /users/`, uploads of images and the `SOCKET /sync/` of several instances | `controllers/api.js`, `controllers/socket.js` |\n\nThe rest of the section: [installing it](/en/openplatform/install-openplatform/), [registering an application](/en/openplatform/register-an-app/), the [settings and the database](/en/openplatform/settings-and-database/). The official documentation is at [docs.totaljs.com/openplatform](https://docs.totaljs.com/openplatform/), and the native client that Total.js 5 includes for the Open Platform protocol is in the recipe [OPENCLIENT](/en/total5/openclient-protocol/)."}],"knowledge":{"content_sha256":"c5101fd318eb1c8b7ff79aa421c4b307913ede4eac77dc6ba7b0518c17dd4169","source_sha256":"f148be29cac7e08dbe59e1392bc52c08afa5ff0a1d8d8d552d0d91b2d6a0221b","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[],"review":"not-recorded","symbols":["API","GET","HTTP","MIT","OPENCLIENT","POST","SOCKET","Total.js"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"openplatform/install-openplatform","section":"openplatform","kind":"recipe","framework":"OpenPlatform","framework_version":"openplatform@5","language":"en","url":"https://forge.totaljs.es/en/openplatform/install-openplatform/","markdown_url":"https://forge.totaljs.es/en/openplatform/install-openplatform.md","tags":["openplatform","install","docker","postgresql"],"license":"CC-BY-4.0","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"Install OpenPlatform with Node.js and PostgreSQL, Docker or Docker Compose","description":"Start OpenPlatform by hand, with the Docker Hub image or with Docker Compose, what happens on the first start and what to configure right after signing in.","messages":[{"role":"user","content":"Install OpenPlatform with Node.js and PostgreSQL, Docker or Docker Compose\n\nStart OpenPlatform by hand, with the Docker Hub image or with Docker Compose, what happens on the first start and what to configure right after signing in."},{"role":"assistant","content":"OpenPlatform needs the latest **Node.js** and a **PostgreSQL** database. There are three ways to start it, all from the README of the repository. Do **not** run `database.sql` yourself: OpenPlatform uses that script internally on the first start.\n\n## By hand\n\n1. Install Node.js and PostgreSQL and create an empty database for OpenPlatform.\n2. Download the [source code](https://github.com/totaljs/openplatform).\n3. Install the dependencies in the root of the application:\n\n```bash\nnpm install\n```\n\n4. Put the connection string in the file `config` (the file has the line `database : postgresql://user:pass@localhost:5432/database` as a model, and a line `database (env) : DATABASE` that reads it from the environment variable `DATABASE`).\n5. Start it (the `start` script of `package.json` runs `node index.js 8000`):\n\n```bash\nnode index.js\n```\n\n6. Open `http://127.0.0.1:8000` and, **before anything else, open the settings and configure the platform** (the README marks this step as important; the settings are in [settings and database](/en/openplatform/settings-and-database/)).\n\n## Docker Hub\n\n```bash\ndocker pull totalplatform/openplatform\ndocker run --env DATABASE='postgresql://user:pass@hostname/database' -p 8000:8000 totalplatform/openplatform\n```\n\n## Docker Compose\n\n```bash\ngit clone https://github.com/totaljs/openplatform.git\ncd openplatform\ndocker compose up\n```\n\n## What the first start does\n\nWhen the database has no OpenPlatform tables yet (it looks for the table `cl_config` in the schema `op`), the application loads `database.sql`, replacing its placeholders with values it generates: the identifier of the installation, the cookie name, the secrets and salts, the tokens, and the first user and group. It then loads the configuration and starts the authentication (`definitions/init.js`). With the environment variable `DATABASE` (Docker) it waits three seconds before this, so that the database can be up. In both cases the server is paused (`PAUSESERVER('Database')`) until the database is ready.\n\n## The first sign-in\n\nThe README gives the default account:\n\n```text\nlogin : info@totaljs.com\npassword : admin\n```\n\nSign in, **change that password** (the portal has the account action for it) and configure the settings. Remember the rule of the README: every application that you add must be assigned to a group, or nobody sees it ([groups and permissions](/en/openplatform/groups-and-permissions/))."}],"knowledge":{"content_sha256":"73796b2e998f1ca4314bd68628ca8277d707cae92f47c58d14f96500b08b1b82","source_sha256":"4f40e940c3a00f9fe975e1fcb3682aae7045f409a1e637b3a8ea2e6817360558","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[],"review":"not-recorded","symbols":["DATABASE","PAUSESERVER","README"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"openplatform/register-an-app","section":"openplatform","kind":"recipe","framework":"OpenPlatform","framework_version":"openplatform@5","language":"en","url":"https://forge.totaljs.es/en/openplatform/register-an-app/","markdown_url":"https://forge.totaljs.es/en/openplatform/register-an-app.md","tags":["openplatform","apps","tokens","setup"],"license":"CC-BY-4.0","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"Register an application in OpenPlatform","description":"The fields of an application record in OpenPlatform: address, the two shared tokens, the flags that decide how it opens, its permissions and who can reach it.","messages":[{"role":"user","content":"Register an application in OpenPlatform\n\nThe fields of an application record in OpenPlatform: address, the two shared tokens, the flags that decide how it opens, its permissions and who can reach it."},{"role":"assistant","content":"An application is a record that the administrator creates in the **setup** screen (`/setup/`, the apps API of the `setup` plugin). The record is the contract between OpenPlatform and your application: where it lives, how the two of them prove who they are, and what the user may do in it. The schema of the record is `@Apps` in `plugins/setup/schemas/apps.js`.\n\n| Field | Required | Meaning |\n|---|---|---|\n| `name` | yes | the name shown in the portal |\n| `url` | yes | the address that OpenPlatform opens |\n| `reqtoken` | yes | a secret that you choose and that **both sides know**: it signs the request |\n| `restoken` | yes | a second secret, also shared: the application uses it to sign its answer |\n| `color`, `icon` | no | how the application looks in the portal |\n| `meta` | no | an address (URL) that returns the metadata of the application: the setup screen can download it (`Apps/download`) to fill in the record |\n| `sortindex` | no | the order in the list (a new application goes to the end) |\n| `notifications` | no | whether the application may send notifications |\n| `isexternal` | no | the application is **not** opened inside the portal's frame: the answer to the application says `iframe: false` |\n| `isbookmark` | no | the application is a plain link: no token handshake, only a session identifier in the query string |\n| `isnewtab` | no | open it in a new browser tab instead of the frame of the portal (the setting `newtab` does it for every application) |\n| `isscrollbar` | no | the frame of the application in the portal keeps its scrollbar (the portal sets `scrolling` to `yes` or `no`) |\n| `isdisabled` | no | switch it off for everybody: the user gets \"App has been temporary disabled\" |\n| `permissions` | no | the list of permissions of the application: each one has an `id`, a `name` and a `value` (see [groups and permissions](/en/openplatform/groups-and-permissions/)) |\n\nThe table `tbl_app` also has a column `allow` (\"allows only specific IP addresses\"): when it holds addresses, the application can call the protocol endpoints of OpenPlatform only from them. The setup schema above does not list it, so it is a database column that you fill in directly.\n\n## The two tokens\n\n`reqtoken` and `restoken` are never sent to the browser. OpenPlatform derives from them, for each launch, the values that travel: the token in the address of the application and the header that the application sends back. The derivation is in [how an app connects](/en/openplatform/how-an-app-connects/). Use long random values and a different pair for each application.\n\n## Removing and changing\n\nAn application is never deleted from the table: removing it (`Apps/remove`) sets `isremoved` and the date of the removal, clears the cache of the users that had it, and `isdisabled` switches it off without removing it. When the address of the platform (the `url` setting) changes, OpenPlatform clears the notification links of every user and deletes the open application sessions, because both contain that address ([settings and database](/en/openplatform/settings-and-database/)).\n\nRemember the rule of the README: a registered application is visible to nobody until a group is assigned to it."}],"knowledge":{"content_sha256":"44a44499b15a22406e5d90b352edc09e62114d5b1dd40cd81560d5933f43436f","source_sha256":"887ea4a7d4f8c1aaa8bedc8c916a49c41f5051094149252365e0d284759c9ef1","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[],"review":"not-recorded","symbols":["API","README","URL"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"openplatform/groups-and-permissions","section":"openplatform","kind":"recipe","framework":"OpenPlatform","framework_version":"openplatform@5","language":"en","url":"https://forge.totaljs.es/en/openplatform/groups-and-permissions/","markdown_url":"https://forge.totaljs.es/en/openplatform/groups-and-permissions.md","tags":["openplatform","users","groups","permissions"],"license":"CC-BY-4.0","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"Users, groups and permissions in OpenPlatform","description":"How a user reaches an application in OpenPlatform: groups decide which applications the user sees and which permission values each application receives.","messages":[{"role":"user","content":"Users, groups and permissions in OpenPlatform\n\nHow a user reaches an application in OpenPlatform: groups decide which applications the user sees and which permission values each application receives."},{"role":"assistant","content":"OpenPlatform separates **who** (users), **where** (applications) and **what** (permissions) with groups in the middle:\n\n- A **user** has a name, an email, a language, a password and flags (`sa` for administrator, `isconfirmed`, `isdisabled`, `isinactive`, `notifications`, `sounds`...) and belongs to any number of groups (schema `@User`, `plugins/setup/schemas/users.js`).\n- A **group** has a name, a colour, an icon, an optional `reference` and a list of permissions (schema `@Group`, `plugins/setup/schemas/groups.js`).\n- An **application** declares its own permissions: each has a name and a `value`, a plain string that the application understands (`plugins/setup/schemas/apps.js`).\n\n## How the access is computed\n\nThe function `FUNC.permissions(userid, callback)` in `definitions/func.js` builds, for a user:\n\n1. the groups of the user that are not disabled;\n2. the rows of `tbl_group_permission` of those groups: a row with a permission gives that permission of an application, and a row **without** a permission just gives the application (the user sees it and receives an empty list of permissions);\n3. the `value` of each granted permission, only for applications that are not removed and not disabled.\n\nThe result has three parts: the applications of the user (`apps`), the permission values by application (`permissions`) and the groups (`groups`). It is stored as JSON in the column `cache` of the user, with a `cachefilter` that lists the groups (`G<id>`) and the applications (`A<id>`) it depends on. When a group or an application changes, `FUNC.clearcache(id)` clears the cache of every user whose filter contains that identifier, and the next request rebuilds it.\n\n## Where the application sees it\n\n- The portal lists only the applications in `apps` and refuses to open any other one (\"App not found\").\n- The application receives its own list in `permissions` when it verifies a launch (an array of the `value` strings), together with the ids of the groups, so it can decide what to show ([how an app connects](/en/openplatform/how-an-app-connects/)).\n- The setup screen decides who may use it by the flag `sa` of the user.\n\nA good habit, from the way the code works: name the `value` of a permission after the action in your application (`orders.read`, `orders.write`) and map groups to those values in OpenPlatform, so that your application only checks strings."}],"knowledge":{"content_sha256":"bf071417d9206260fbcb3592fc8ca09a567f81e2d1c8ea1f513e1615cfb33afc","source_sha256":"1e669de37b1944bc85daa6bff8590f7295a6bc9ad51764d1f0b733dbda64aeb7","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[],"review":"not-recorded","symbols":["FUNC","JSON"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"openplatform/how-an-app-connects","section":"openplatform","kind":"recipe","framework":"OpenPlatform","framework_version":"openplatform@5","language":"en","url":"https://forge.totaljs.es/en/openplatform/how-an-app-connects/","markdown_url":"https://forge.totaljs.es/en/openplatform/how-an-app-connects.md","tags":["openplatform","protocol","tokens","verify","sso"],"license":"CC-BY-4.0","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"How an application connects to OpenPlatform: the launch and the verification","description":"The exact handshake between OpenPlatform and an application: the address it is opened with, how the application signs its request to /verify/ and the user data it gets back.","messages":[{"role":"user","content":"How an application connects to OpenPlatform: the launch and the verification\n\nThe exact handshake between OpenPlatform and an application: the address it is opened with, how the application signs its request to /verify/ and the user data it gets back."},{"role":"assistant","content":"When a user opens an application, OpenPlatform does not send a password or a cookie to it. It sends a **one-time launch address**, and the application calls back to OpenPlatform to learn who the user is. This page follows the code (`schemas/account.js`, action `Account/run`, and `controllers/verify.js`).\n\n## 1. The launch\n\nWhen the user clicks an application, the portal calls the API `run/{appid}`. OpenPlatform:\n\n1. checks that the application is in the list of the user (`App not found` otherwise) and that it is not disabled;\n2. creates a session of the application (table `tbl_app_session`) that expires after the setting `app_session_expire` (one day by default);\n3. builds the **verification address**: the platform `url` + `/verify/?token=` + the identifier of the session joined to the identifier of the installation with `X`, followed by `X` and a checksum (`FUNC.checksum`, which uses the secret `saltchecksum`);\n4. derives the two tokens of that session from the shared secrets of the application:\n   - `reqtoken` = the MD5 of the verification address salted with the `reqtoken` of the application, in lower case;\n   - `restoken` = the MD5 of that `reqtoken` salted with the `restoken` of the application;\n5. opens the application at its `url` with one extra query parameter: `openplatform=<verification address>~<reqtoken>`.\n\nFor an application marked as a bookmark there is no handshake: the address gets `ssid=<session checksum>` instead.\n\n## 2. The verification\n\nThe application takes the value of `openplatform`, which is the verification address, a `~` and the `reqtoken` of the session, and:\n\n1. computes **its own** `restoken`: the MD5 of the `reqtoken` salted with the `restoken` that it shares with OpenPlatform;\n2. calls `GET` on the whole value of `openplatform` (the `~` and the `reqtoken` stay in the query string) with the header `x-token` set to that `restoken`.\n\nOpenPlatform then (`controllers/verify.js`):\n\n1. splits the query token at the last `~` (the part after it is the `reqtoken` that it sent) and refuses the call when the header or the token is missing;\n2. checks the checksum of the session identifier, reads the session and compares both secrets (`reqtoken` and the `restoken` of the header); any difference is `Invalid token`;\n3. if the application has a list of allowed addresses, checks the address of the caller (`Not allowed IP address`);\n4. reads the user and refuses a disabled user (`User has been disabled`) or an inactive one (`User is inactive`);\n5. makes sure the user has the notification links of this application (table `tbl_user_app`);\n6. builds the answer and passes it through the transformation `verify` of the platform, so a deployment can add fields.\n\nThe answer is a JSON object. Empty values are dropped from it. Its main fields are:\n\n| Field | What it is |\n|---|---|\n| `id`, `name`, `email`, `photo`, `gender`, `reference` | who the user is (`photo` is an absolute address) |\n| `language`, `color`, `interface`, `darkmode`, `sounds` | the preferences (the platform language and colour when the user has none) |\n| `sa` | the user is an administrator of the platform |\n| `permissions` | the `value` strings of the permissions of the user in this application |\n| `groups` | the ids of the groups of the user |\n| `appid`, `ssid` | the application and the checksum of its session |\n| `openplatformid`, `openplatform` | the identifier and the address of the installation |\n| `iframe` | `true` unless the application is external |\n| `notify` | the address to send notifications to, when the user accepts them ([notifications](/en/openplatform/notifications-and-public-api/)) |\n| `platform` | the `name` and `logo` of the platform, its `groups` and the applications of the user |\n\nThe answer is kept in an in-memory cache keyed by the session token (the part before the `~`) and the cache is cleared every two minutes. A call with a token that is in that cache is answered from it, before the secrets are compared again, so a repeated verification inside that time is cheap.\n\n## 3. Asking again later\n\n`GET /session/` takes the `ssid` that the application received (it also accepts `openplatformid`, `token` and `session` as the name of the parameter), checks its checksum and returns the user (`id`, `language`, `name`, `color`, `sa`, `isonline`) while the session of the user has not expired.\n\n## What to remember when you build the application\n\n- Never ship the shared secrets to the browser: only the server of the application needs them.\n- Verify on the **server** and create your own session from the answer; the `openplatform` parameter is for one launch.\n- Use the `permissions` of the answer, not a role that the browser sends.\n- Total.js 5 has a native client for this family of protocols: see [OPENCLIENT](/en/total5/openclient-protocol/)."}],"knowledge":{"content_sha256":"6ca3284d8734f1ae0f727ee0443412c5cba13f9a157aeba40684870245c05268","source_sha256":"5ed04614c382e95c893cdabfc9dd43fa71caf537fd30a712c3283196af2e6c82","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[],"review":"not-recorded","symbols":["API","FUNC","GET","JSON","MD5","OPENCLIENT","Total.js"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"openplatform/notifications-and-public-api","section":"openplatform","kind":"recipe","framework":"OpenPlatform","framework_version":"openplatform@5","language":"en","url":"https://forge.totaljs.es/en/openplatform/notifications-and-public-api/","markdown_url":"https://forge.totaljs.es/en/openplatform/notifications-and-public-api.md","tags":["openplatform","notifications","api","tms"],"license":"CC-BY-4.0","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"Notifications and the public endpoints of OpenPlatform","description":"Send a notification to a user from your application, and use the token API for the list of users, the image upload and the sync socket of several instances.","messages":[{"role":"user","content":"Notifications and the public endpoints of OpenPlatform\n\nSend a notification to a user from your application, and use the token API for the list of users, the image upload and the sync socket of several instances."},{"role":"assistant","content":"Besides the launch and the verification, OpenPlatform has a few endpoints for the applications and for other systems. They are in `controllers/verify.js`, `controllers/api.js` and `controllers/socket.js`.\n\n## Send a notification\n\nThe answer of the verification contains `notify`, an address of the form `<platform url>/notify/?token=<checksum of user and application>`, when the user accepts notifications. The application sends a `POST` to it with the header `x-token` and a JSON body:\n\n| Field | Meaning |\n|---|---|\n| `body` | the text of the notification |\n| `path` | the path inside the application that the notification opens |\n| `icon`, `color` | optional: they replace the icon and the colour of the application |\n\nThe value of `x-token` is the **notify token** that OpenPlatform derives for that user and application: the MD5 of the notify address salted with the `reqtoken` of the application, and the MD5 of that result salted with its `restoken` (`FUNC.makenotify` in `definitions/func.js`). Both secrets are the same ones that the application shares for the launch ([register an app](/en/openplatform/register-an-app/)).\n\nOpenPlatform answers `Invalid token` when the checksum or the header do not match, and `Not allowed notifications` when the user or the application does not accept them. It also checks the allowed addresses of the application and that the user is confirmed, enabled and active. When everything is right it:\n\n1. stores the notification (table `tbl_notification`) and increases the unread counter of the user;\n2. updates the sessions of that user that are open in the portal, so the counter appears at once;\n3. publishes `Notifications.create` by TMS when the setting `$tms` is on;\n4. returns the identifier of the notification.\n\nFor users that are offline and have unread notifications, a service job sends **an email digest** (\"Unread notifications\") when the mail settings are configured, at most once every three days per user (`definitions/init.js`).\n\n## The users API (a token)\n\n`GET /users/` returns the groups and the users of the platform as JSON. It is **disabled** until the settings `allow_token` and `token` are set. The caller sends the token as the query parameter `token` or as the header `x-token`; repeated wrong tries are blocked. The users come without secrets: identifier, reference, language, gender, photo (an absolute address), name, email, colour, preferences, flags (`sa`, `isdisabled`, `isinactive`, `isonline`), dates and the ids of their groups.\n\n## Upload an image\n\n`POST /upload/base64/` (up to 2 MB) accepts a JSON body with the file name and `file` as a data URI, and only **JPEG**. It needs a signed-in user or the query token of the setting `servicetoken` (see [settings and database](/en/openplatform/settings-and-database/) for where that key comes from). The image is stored in the file storage `files` and the answer is its address, `/files/<id and checksum>.jpg`; the address only works with the checksum.\n\n## Several instances: the sync socket\n\n`SOCKET /sync/` connects instances of OpenPlatform. A client is accepted when the setting `sync` is on and the query `token` equals `sync_token`; any other client is closed with the code `4001` half a second after opening. The messages of a confirmed client are relayed to the others, and while a confirmed client is reconnecting the messages are queued for up to ten seconds."}],"knowledge":{"content_sha256":"172196f9c0e9c57455997c1066130953d232b02c2d6225f6a4538bce1f431188","source_sha256":"2acbb343abb161396388eb8a83f3699c772e0e8e1e6e02f990b5f674c0bdd688","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[],"review":"not-recorded","symbols":["API","FUNC","GET","JPEG","JSON","MD5","POST","SOCKET","TMS","URI"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"openplatform/settings-and-database","section":"openplatform","kind":"recipe","framework":"OpenPlatform","framework_version":"openplatform@5","language":"en","url":"https://forge.totaljs.es/en/openplatform/settings-and-database/","markdown_url":"https://forge.totaljs.es/en/openplatform/settings-and-database.md","tags":["openplatform","settings","database","services"],"license":"CC-BY-4.0","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"OpenPlatform settings, services and database tables","description":"The configuration keys OpenPlatform reads, what its background services do and what each table of the op schema is for.","messages":[{"role":"user","content":"OpenPlatform settings, services and database tables\n\nThe configuration keys OpenPlatform reads, what its background services do and what each table of the op schema is for."},{"role":"assistant","content":"## The settings\n\nOpenPlatform keeps its configuration in the table `cl_config` and loads it into Total.js `CONF` at start and every eight hours (`definitions/init.js`). The setup screen reads and saves these keys (`plugins/setup/schemas/settings.js`):\n\n| Key | Meaning |\n|---|---|\n| `name`, `icon`, `color` | the name, logo and colour of the platform |\n| `url` | the public address of the platform (without the final `/`). Changing it clears the notification links and the open application sessions |\n| `language` | the default language |\n| `mail_smtp`, `mail_smtp_options`, `mail_from` | the SMTP server, its options (JSON) and the sender; the setup screen can test them |\n| `$tms`, `secret_tms` | publish the events by TMS, and the secret of that connection |\n| `allow_token`, `token` | enable the public `GET /users/` API and its token |\n| `newtab` | open every application in a new tab |\n| `sync`, `sync_token` | accept the `/sync/` socket of other instances, with its token |\n\nThe first start (`database.sql`) also generates, inside `cl_config`, values that the setup screen does not edit: the installation `id`, the authentication `secret`, the `cookie` name, the `salt` of the passwords, the `saltchecksum` of the checksums and the secret of the TMS connection. It creates the keys with these defaults:\n\n| Key | Default | Meaning |\n|---|---|---|\n| `auth_cookie_expire` | `1 month` | how long the session cookie lives |\n| `auth_expire` | `5 minutes` | how long the signed-in user is kept in memory before it is read again |\n| `auth_cookie_options` | `{\"httponly\":true,\"security\":\"lax\"}` | the options of the session cookie |\n| `auth_strict` | `false` | strict sessions |\n| `app_session_expire` | `1 day` | how long an application session lasts |\n| `cdn` | `https://cdn.componentator.com/` | the CDN of the interface |\n| `language` | `eu` | the default language |\n| `token`, `sync_token` | generated | the token of the users API and of the sync socket (both features are off by default) |\n\nThe key `servicetoken`, which the image upload endpoint compares with its query token, is **not** created by the script and the setup screen does not edit it: it exists only if you add it to `cl_config` yourself.\n\nThe script also creates the group **Admin** and the first user (`info@totaljs.com`, administrator, confirmed) that belongs to it; see [installing it](/en/openplatform/install-openplatform/).\n\n## The background services\n\nTotal.js runs a service every minute. OpenPlatform uses it (`definitions/init.js`) to:\n\n- clear the in-memory cache of the verifications (every 2 minutes);\n- save the daily statistics: users online, the maximum and the number by device (every 3 minutes);\n- reload the configuration from the database (every 8 hours);\n- delete the expired application sessions and platform sessions (every 12 hours);\n- send the email digests of unread notifications (every 11 minutes, when the mail is configured).\n\n## The database\n\nEverything lives in the PostgreSQL schema `op`:\n\n| Table | What it holds |\n|---|---|\n| `cl_config` | the configuration (key, value, type) |\n| `tbl_user` | the users: profile, password hash, flags, counters and the cached permissions |\n| `tbl_group` | the groups |\n| `tbl_user_group` | which user belongs to which group |\n| `tbl_session` | the sessions of the users in the portal (device, address, expiry) |\n| `tbl_app` | the applications: address, secrets, flags and allowed addresses |\n| `tbl_app_permission` | the permissions that each application declares |\n| `tbl_group_permission` | which permissions (or just which applications) each group grants |\n| `tbl_app_session` | the launch sessions of the applications and their derived tokens |\n| `tbl_user_app` | per user and application: the notification links and whether they accept notifications |\n| `tbl_notification` | the notifications |\n| `tbl_visitor` | the daily visitor statistics |\n| `tbl_feedback` | the feedback that users send from the portal |\n\nThe dated files `database-2023-09-15.sql`, `database-2024-03-21.sql`, `database-2024-10-22.sql` and `database-2024-10-30.sql` in the repository are the changes of the schema over time, and `database.sql` is the full script that the first start runs. Back up this schema like any other PostgreSQL database."}],"knowledge":{"content_sha256":"9fcf1fdbe76125ea08f59e9f7d7e04cfb6e6d1c3b744068fafa6f10c09b65e28","source_sha256":"f58761b669b9a34ee744d2cf8669ceaf6dddd12b5d2d728bafb7f80121151401","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[],"review":"not-recorded","symbols":["API","CDN","CONF","GET","JSON","SMTP","TMS","Total.js"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"flow/what-is-flow","section":"flow","kind":"recipe","framework":"Flow","framework_version":"flow@11.1","language":"en","url":"https://forge.totaljs.es/en/flow/what-is-flow/","markdown_url":"https://forge.totaljs.es/en/flow/what-is-flow.md","tags":["flow","flowstream","automation","iot","visual"],"license":"CC-BY-4.0","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"What is Flow: visual automation and IoT with FlowStreams","description":"Flow is the Total.js app that runs FlowStreams, visual diagrams of components connected by messages, for automation, integrations and IoT, with an editor in the browser and an API to manage the streams.","messages":[{"role":"user","content":"What is Flow: visual automation and IoT with FlowStreams\n\nFlow is the Total.js app that runs FlowStreams, visual diagrams of components connected by messages, for automation, integrations and IoT, with an editor in the browser and an API to manage the streams."},{"role":"assistant","content":"**Flow** (`totaljs/flow`, version 11.1 at the time of writing) is the main application of the Total.js family for visual programming. You draw a diagram in the browser: every box is a **component**, the lines between them carry **messages**, and the diagram is a **FlowStream**. Flow keeps many FlowStreams, runs each one, and gives you the editor, the monitoring and an API to manage them.\n\nIt is a Total.js 5 application, MIT licensed, and it needs **no database**: everything lives in two folders of the application (see [install and first start](/en/flow/install-and-first-start/)).\n\n## What you get\n\n- **A visual editor** in the browser (the route `/designer/`) with a library of components, the readme of each component, a console, the variables of the stream and dark mode.\n- **Many independent FlowStreams** in one installation, each with a name, icon, color, group, author, version and notes. By default each stream runs in its **own Node.js process** (`flowstream_worker: fork` in the `config` file), so a stream that fails does not take the others with it, and Flow restarts it.\n- **Components as plain files.** A component is one HTML file with a Node.js part, its settings form and its design (see [write a component](/en/flow/write-a-component/)). The official catalogue is the repository [`flowstreamcomponents`](/en/flow/the-components-repository/).\n- **Ways in for the outside world**: a stream can publish HTTP routes under its own `proxypath`, receive calls on `/notify/{id}/`, and talk over WebSocket.\n- **Import and export** of a stream as one JSON file, pause, restart, statistics and a backup of the database of streams.\n- **Sign-in** with a generated login and password, or through [OpenPlatform](/en/openplatform/what-is-openplatform/) when you install Flow as one of its apps, with the permissions *Create Flows* and *Remove Flows*.\n- **Extensions**: HTML files that you copy to a folder to extend the editor.\n\n## Where it fits\n\nFlow is the tool to connect things without writing a server: read a sensor, call an API every minute, transform a message, store it, send an alert, answer an HTTP request. Because the runtime is the FlowStream of Total.js 5, the same engine can also be embedded in your own application; Flow is the complete application around it.\n\n## The family\n\nFlow has relatives in other repositories of `github.com/totaljs`: [`lesscode`](https://github.com/totaljs/lesscode), [`flowstreamdesigner`](https://github.com/totaljs/flowstreamdesigner), [`flowdesigneroffline`](https://github.com/totaljs/flowdesigneroffline), [`flowdesigner`](https://github.com/totaljs/flowdesigner) and others. They exist as separate projects; this section documents the application `flow` and its components only.\n\n## Next\n\n1. [Install and first start](/en/flow/install-and-first-start/)\n2. [Manage FlowStreams with the API](/en/flow/manage-flowstreams-with-the-api/)\n3. [Settings and files](/en/flow/settings-and-files/)\n4. [Write a component](/en/flow/write-a-component/)\n5. [The components repository](/en/flow/the-components-repository/)"}],"knowledge":{"content_sha256":"1ddb2721c518c5b27cf8e863b76ab7569f9d38fc87393080eab8ca5f52cb57c4","source_sha256":"95490c2d5139c3712567aeeebf5af06a796cb0f87441b622b07e5f6b94064ef5","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[],"review":"not-recorded","symbols":["API","HTML","HTTP","JSON","MIT","Total.js"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"flow/install-and-first-start","section":"flow","kind":"recipe","framework":"Flow","framework_version":"flow@11.1","language":"en","url":"https://forge.totaljs.es/en/flow/install-and-first-start/","markdown_url":"https://forge.totaljs.es/en/flow/install-and-first-start.md","tags":["flow","install","docker","login","first start"],"license":"CC-BY-4.0","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"Install Flow and sign in the first time","description":"Run Flow with Node.js or Docker, open it on port 8000, sign in with the login and password that Flow generates at the first start, and know which folders hold your data.","messages":[{"role":"user","content":"Install Flow and sign in the first time\n\nRun Flow with Node.js or Docker, open it on port 8000, sign in with the login and password that Flow generates at the first start, and know which folders hold your data."},{"role":"assistant","content":"## Run it with Node.js\n\nFlow needs Node.js and nothing else: no database, no external service.\n\n```bash\ngit clone https://github.com/totaljs/flow.git\ncd flow\nnpm install\nnpm run start\n```\n\n`npm run start` runs `node index.js 8000`. The port is optional on the command line and the default is `8000`, so you open `http://localhost:8000`. Add `--release` to run in release mode.\n\n## Run it with Docker\n\n```bash\ndocker pull totalplatform/flow\ndocker run -p 8000:8000 totalplatform/flow\n```\n\nThe `docker-compose.yaml` of the repository maps port 8000 and keeps two volumes: the folder `databases` (settings and the user) and the folder `flowstream` (your streams). Keep those two volumes and you can replace the container whenever you update.\n\n## The first sign-in\n\nAt the first start Flow creates the only user of the application with a **random login and a random password**. Until the first successful sign-in, the sign-in page shows them in a box (*Default account*) with a button **Fill in the form**: click it and sign in. From that moment Flow deletes the plain copy of the password from its preferences and the box no longer appears, so open the password form of the application right away and set a login and a password of your own. Do the first sign-in as soon as you start a new installation, before anyone else can reach the address.\n\nSome details of the sign-in, read from the source:\n\n- The password is stored as a SHA-256 hash with the `cookie_secret` of the installation as the salt, and the session is a cookie of one month encrypted with the same secret. Flow generates the cookie name and the secret the first time and keeps them in `databases/config.json`.\n- After repeated failed attempts an address is blocked (the block is cleared every 15 minutes).\n- When the settings hold the tokens of an OpenPlatform (`op_reqtoken`, `op_restoken`), the sign-in page is replaced by the sign-in of that [OpenPlatform](/en/openplatform/how-an-app-connects/) and the permissions *Create Flows* and *Remove Flows* control who may create and remove streams.\n\n## Where the data lives\n\n| Folder | What it holds |\n|---|---|\n| `databases/` | `config.json` (cookie name and secret) and `memorize_preferences.json` (name, tokens, the user, the `env`, the URLs of the components) |\n| `flowstream/` | `database.json` with every FlowStream: its design, components, variables and notes |\n| `private/` | files that the streams write and that you can list with the token of Flow |\n| `public/cdn/` | cache of the user-interface components that Flow downloads the first time they are needed |\n\nTo move Flow to another machine, copy `databases/` and `flowstream/`.\n\n## Update\n\nDownload the new version of the repository (the file `version` tells you which one you run: `11.1` in the revision documented here) or pull the new Docker image and start it with the same volumes."}],"knowledge":{"content_sha256":"5a25bf52b53d52c97ea7dadedbcef824b1f3079740fe72fe4ebfdade8152bb6b","source_sha256":"10feccb21183621591ac5851efc44ee84e027e7a4eb40f0f717a09f3995dc1d5","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[],"review":"not-recorded","symbols":["SHA","URL"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"flow/manage-flowstreams-with-the-api","section":"flow","kind":"recipe","framework":"Flow","framework_version":"flow@11.1","language":"en","url":"https://forge.totaljs.es/en/flow/manage-flowstreams-with-the-api/","markdown_url":"https://forge.totaljs.es/en/flow/manage-flowstreams-with-the-api.md","tags":["flow","flowstream","api","import","export","notify"],"license":"CC-BY-4.0","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"Manage FlowStreams with the Flow API","description":"Sign in, list, create, import, export, pause, restart and remove FlowStreams with the JSON API at /fapi/, read the statistics and the variables, and call a stream from outside with its notify address or its own proxy path.","messages":[{"role":"user","content":"Manage FlowStreams with the Flow API\n\nSign in, list, create, import, export, pause, restart and remove FlowStreams with the JSON API at /fapi/, read the statistics and the variables, and call a stream from outside with its notify address or its own proxy path."},{"role":"assistant","content":"Everything the editor does goes through one JSON endpoint: `POST /fapi/` with the body `{ \"schema\": \"<name>\", \"data\": { ... } }` (the Total.js API routes, see [API routes](/en/total5/api-routes/)). The address `/fapi/` comes from the line `$api : /fapi/` of the `config` file. Every call except the sign-in needs the session cookie; without it the answer is `401: Unauthorized`.\n\nThese calls were also tried against a local Flow 11.1 (Node.js 24 and `total5@0.0.19`) while this page was written.\n\n## Sign in\n\n`Auth|login` takes `login` and `password` and answers `{\"success\":true}` with the session cookie. A wrong pair answers `Invalid credentials`.\n\n```bash\ncurl -c cookie.txt -X POST http://localhost:8000/fapi/ \\\n  -H 'content-type: application/json' \\\n  -d '{\"schema\":\"Auth|login\",\"data\":{\"login\":\"<your login>\",\"password\":\"<your password>\"}}'\n```\n\nThe next calls use `-b cookie.txt`.\n\n## The calls\n\n| `schema` | `data` | What it does |\n|---|---|---|\n| `Streams` | none | the list of FlowStreams with `id`, `name`, `group`, `author`, `version`, `proxypath`, `size` and the live `stats` of the running ones |\n| `Streams\\|save` | `name`, and optionally `author`, `version`, `icon`, `color`, `group`, `url`, `reference`, `readme`, `memory`, `cloning`, `proxypath`, `proxytimeout` | **creates** a stream when `id` is missing (the permission *create* is checked), otherwise updates the stream with that `id` and restarts it |\n| `Streams\\|read` | `id` | the properties of one stream |\n| `Streams\\|raw` | `id` | the whole stream object, with its design |\n| `Streams\\|export` | `id` | the stream as JSON text in `value`, without the internal fields (`unixsocket`, `env`, `import`, `worker`, `asfiles`) |\n| `Streams\\|import` | `data` (the JSON text) | creates a **new** stream from an export (up to 10 MB), with a new `id`; a repeated `proxypath` is replaced with a random one; answers `{\"success\":true,\"value\":\"<id>\"}` |\n| `Streams\\|pause` | `id`, `is` (`true` or `false`) | pauses or resumes a running stream |\n| `Streams\\|restart` | `id` | restarts the stream |\n| `Streams\\|remove` | `id` | removes the stream and its folder (the permission *remove* is checked) |\n| `Streams\\|stats` | none | the totals of the installation: versions of Node.js, Total.js and Flow, messages, pending, memory and connections |\n| `Variables\\|read` / `Variables\\|save` | `id` (optional), `data` | the variables of one stream, or the **global** variables when `id` is empty; saving pushes them to the running streams |\n| `Console\\|read` / `Console\\|clear` | none | the console of the application |\n| `CDN\\|clear` | none | clears the cache of the downloaded user-interface components |\n| `Settings\\|read` / `Settings\\|save` | the settings (see [settings and files](/en/flow/settings-and-files/)) | only for the super administrator |\n\n## Create a stream and run it\n\nCreate it, then import a design built in the editor (an exported JSON file) to start it:\n\n```bash\ncurl -b cookie.txt -X POST http://localhost:8000/fapi/ \\\n  -H 'content-type: application/json' \\\n  -d '{\"schema\":\"Streams|save\",\"data\":{\"name\":\"Demo\",\"author\":\"Me\"}}'\n```\n\nA stream exported from the editor carries its `design` (the boxes and their connections), its `components` (the source of each one) and its `variables`.\n\n## Rules of `proxypath`\n\nA stream can publish HTTP routes under a path of its own. Flow adds the slashes (`demo` becomes `/demo/`), rejects the paths that belong to Flow itself (`/`, `/cdn/`, `/fapi/`, `/private/`, `/flows/`, `/designer/`, `/parts/`, `/forms/`, `/css/`, `/js/`, `/fonts/`, `/panels/`) with `Proxy endpoint contains reserved path`, and rejects a path that another stream already uses. `proxytimeout` is the time in seconds to wait for the answer of the stream (the default is `120`).\n\n## Calls from outside\n\n| Route | Use |\n|---|---|\n| `GET` or `POST /notify/{id}/` | sends a notification to the stream with that id (the POST body is limited to 1 MB); the components of the stream decide what to do with it. Answers `{\"success\":true}` |\n| `+SOCKET /{id}/` | the WebSocket of the editor for that stream (it needs the session) |\n| `GET /private/?token=<token>` | lists the files of the folder `private/` when the token equals the `token` of the settings; ten wrong tokens block an address for 15 minutes |\n| `+POST /update/` | updates the application from a `.bundle` file; only for the bundled version |\n\nKeep the `token` secret, send these calls over HTTPS and put Flow behind a reverse proxy when it is reachable from the Internet."}],"knowledge":{"content_sha256":"1ca332525e645680edb0d12f3c3453b31624fe152137539464bca8173d80729b","source_sha256":"d8eec3305ad8e41dd475ec870b7ebdc2b0afc2d67c026a44e9e34497ad1ccdeb","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[],"review":"not-recorded","symbols":["API","CDN","GET","HTTP","HTTPS","JSON","POST","SOCKET","Total.js"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"flow/settings-and-files","section":"flow","kind":"recipe","framework":"Flow","framework_version":"flow@11.1","language":"en","url":"https://forge.totaljs.es/en/flow/settings-and-files/","markdown_url":"https://forge.totaljs.es/en/flow/settings-and-files.md","tags":["flow","settings","config","extensions","env"],"license":"CC-BY-4.0","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"Flow settings, config file and folders","description":"The settings of Flow (name, components and templates catalogues, environment, backup, tokens), the lines of the config file, the environment of the streams and the folders for extensions and local components.","messages":[{"role":"user","content":"Flow settings, config file and folders\n\nThe settings of Flow (name, components and templates catalogues, environment, backup, tokens), the lines of the config file, the environment of the streams and the folders for extensions and local components."},{"role":"assistant","content":"## The `config` file\n\nThe file `config` in the root of Flow holds the options of the application. The ones that matter:\n\n| Line | Meaning |\n|---|---|\n| `name` | the name shown by the application (it can be changed in the settings) |\n| `$api : /fapi/` | the address of the JSON API |\n| `flowstream_worker : fork` | each FlowStream runs in its own process |\n| `$proxytimeout : 120` | seconds that a stream may take to answer an HTTP request |\n| `cookie`, `cookie_secret` | generated at the first start and kept in `databases/config.json` |\n\nOther options are read when they exist: `directory` (the folder of the streams; the default is `flowstream/` next to the application), `flowstream_memory` (a default memory limit for new streams), `flowstream_sandbox` and `flowstream_asfiles` (they are passed to every stream), `components` and `templates` (the first values of the catalogues) and `backup`.\n\n## Settings in the application\n\nThe super administrator opens the settings and finds:\n\n- **name** of the installation;\n- **components** and **components2**: the address of the catalogue of components (a `db.json` file, by default `https://cdn.totaljs.com/flowstream/webcomponents/db.json`) and an optional second catalogue, so you can offer your own components next to the official ones;\n- **templates**: the catalogue of templates of streams (by default `https://cdn.totaljs.com/flowstream/templates/db.json`);\n- **env**: `dev`, `test` or `prod`. The streams read it, and changing it restarts all of them one by one;\n- **backup**: each save of the streams renames the previous `database.json` to a file with the date and the minute, `database_yyyyMMddHHmm.bk`;\n- **token**: the random token for `/private/` (see [the API](/en/flow/manage-flowstreams-with-the-api/));\n- **darkmode** and **notify**;\n- **op_reqtoken** and **op_restoken**: the tokens of an [OpenPlatform](/en/openplatform/how-an-app-connects/); when both are set, people sign in with that OpenPlatform.\n\nPlugins can add their own settings to the same form. The repository includes one, *Bookmarks*, a list of saved components that people with the permission `bookmarks` can use.\n\n## Variables\n\nEvery stream has its own **variables** and there is a set of **global variables**, both edited in the editor or with `Variables|read` and `Variables|save`. A component reads them to avoid putting addresses and keys in the design, and a change reaches the running streams at once.\n\n## Local components: `--components--`\n\nThe folder `--components--` of the repository holds a few components that travel with the application: `checkfreespace`, `checklog`, `checkpg`, `checkurl`, `console` and `print`. The official catalogue is much bigger, see [the components repository](/en/flow/the-components-repository/).\n\n## Extensions\n\nAn extension adds something to the editor of every stream. Copy an `.html` file to the folder `extensions/` (the repository documents it in `--extensions--/` with a `test.html` sample) and Flow loads it for each stream: the part between `<script total>` and `</script>` runs with the stream, and the rest of the file is added to the editor.\n\n## The environment of a stream\n\nAt the start of each stream Flow gives it the folder where it keeps its files (`directory`), the global variables, the `env` of the installation, its memory limit, and the extensions loader (`extensions.js`). A stream that crashes is restarted by Flow, and its statistics (messages, pending, errors, memory and process id) are returned by `Streams`."}],"knowledge":{"content_sha256":"52a070f7013e82504d2350a59a14b4bae3a3cbc0d460f3d3b237dde3e5b44741","source_sha256":"d9cd0ca59e7b5d3ed553e0c789a5ae738f04aa37b7c3ae639c953aa6fc1a7a22","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[],"review":"not-recorded","symbols":["API","HTTP","JSON"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"flow/write-a-component","section":"flow","kind":"recipe","framework":"Flow","framework_version":"flow@11.1","language":"en","url":"https://forge.totaljs.es/en/flow/write-a-component/","markdown_url":"https://forge.totaljs.es/en/flow/write-a-component.md","tags":["flow","flowstream","component","make","message"],"license":"CC-BY-4.0","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"Write a Flow component: one HTML file","description":"A Flow component is a single HTML file with a Node.js part, a settings form and a design. Build one that changes the text of a message, learn the parts of the file and how to test it.","messages":[{"role":"user","content":"Write a Flow component: one HTML file\n\nA Flow component is a single HTML file with a Node.js part, a settings form and a design. Build one that changes the text of a message, learn the parts of the file and how to test it."},{"role":"assistant","content":"A component is **one `.html` file**. Flow reads the file, runs its Node.js part inside the stream and shows its design in the editor. The official components of [the repository](/en/flow/the-components-repository/) all use this format, so you can read any of them as an example.\n\n## An example: `shout.html`\n\nIt receives a message, converts the text to upper case, adds a suffix and sends it on.\n\n```html\n<script total>\n\n\texports.id = 'shout';\n\texports.name = 'Shout';\n\texports.group = 'Common';\n\texports.version = '1';\n\texports.icon = 'ti ti-speakerphone';\n\texports.author = 'Forge';\n\texports.config = { suffix: '!' };\n\texports.inputs = [{ id: 'input', name: 'Input' }];\n\texports.outputs = [{ id: 'output', name: 'Output' }];\n\n\texports.make = function(instance, config) {\n\n\t\tlet count = 0;\n\n\t\tinstance.message = function($) {\n\t\t\tcount++;\n\t\t\tinstance.status({ shouted: count });\n\t\t\t$.data = String($.data).toUpperCase() + config.suffix;\n\t\t\t$.send('output');\n\t\t};\n\n\t};\n\n</script>\n\n<settings>\n\t<div class=\"padding\">\n\t\t<ui-component name=\"input\" path=\"?.suffix\" config=\"maxlength:10\">Suffix</ui-component>\n\t</div>\n</settings>\n\n<readme>\nThe component converts the text of a message to upper case and adds a suffix.\n</readme>\n\n<body>\n\t<header>\n\t\t<i class=\"ICON\"></i>NAME\n\t</header>\n\t<footer>\n\t\t<div class=\"padding\">Shouted: <b><ui-bind path=\"STATUS.shouted\" config=\"text:value||0\"></ui-bind></b></div>\n\t</footer>\n</body>\n```\n\n## The parts of the file\n\n| Part | What it is for |\n|---|---|\n| `<script total>` | the Node.js code of the component, executed in the process of the stream |\n| `<settings>` | the form of the configuration, with the components of the user interface (`ui-component`, paths that start with `?.` point to the `config` of the component) |\n| `<readme>` | the help shown in the editor |\n| `<body>` | the box in the diagram: a `<header>` with the placeholders `ICON` and `NAME`, and optionally a `<footer>` or a body, where `<ui-bind path=\"STATUS.x\">` shows the status and `CONFIG.x` the configuration |\n| `<style>` | the CSS of the box; `.CLASS` is replaced with a class unique for the component |\n| `<script>` (second) | optional JavaScript of the editor, for buttons in the box (it can send `{ TYPE: 'trigger', id: ... }` to the stream) |\n\n## The `exports` of the Node.js part\n\n| Export | Meaning |\n|---|---|\n| `id`, `name`, `group`, `version`, `icon`, `author` | the identity of the component and where it appears in the list |\n| `config` | the default configuration; the form of `<settings>` edits it |\n| `inputs`, `outputs` | the list of `{ id, name }` connectors. A component without `inputs` is a source |\n| `kind` | optional: the role of the component in the catalogue (`trigger`, `action` or `output`) |\n| `make(instance, config)` | called once per box that you place in the diagram |\n\n## The `instance`\n\nInside `make` you define the behavior by assigning functions to the instance:\n\n| Function | Called when |\n|---|---|\n| `instance.message = function($) {}` | a message arrives. `$.data` is the data, `$.send('output')` forwards it to the connectors of that output, `$.destroy()` ends it without sending |\n| `instance.configure = function() {}` | the configuration changes, and once at the start |\n| `instance.trigger = function() {}` | the button of the editor or the code asks for it |\n| `instance.pause = function(is) {}` | the stream is paused or resumed |\n| `instance.close = function() {}` | the box is removed or the stream stops: clear your timers here |\n\nAnd you call `instance.send('output', data)` to create a message of your own (as `timer` does), `instance.status(object)` to show a status in the box (`delay.html` passes a time in milliseconds as a second argument to refresh it) and `instance.throw(error)` to report an error.\n\n## Try it\n\n1. Put `shout.html` in a catalogue and write its address in the setting `components2` (see [the repository](/en/flow/the-components-repository/)), or import a stream that contains it in its `components`.\n2. Add a `timer` and a `print` next to it in the editor, connect them and start the stream.\n3. Open the status of the box: the counter grows with each message.\n\nTo test a component without the editor, the repository has a small tester, see [the components repository](/en/flow/the-components-repository/)."}],"knowledge":{"content_sha256":"98e0101ffef81a1e2ff75365737b0c12e754571250c18d5e601aba641db20dff","source_sha256":"a0bdffa789dcd74224754c8c26d4e3fdfaa01b40f750c76f0aeae98a42d5d21b","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[],"review":"not-recorded","symbols":["CLASS","CONFIG","CSS","ICON","NAME","STATUS","TYPE"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"flow/the-components-repository","section":"flow","kind":"recipe","framework":"Flow","framework_version":"flow@11.1","language":"en","url":"https://forge.totaljs.es/en/flow/the-components-repository/","markdown_url":"https://forge.totaljs.es/en/flow/the-components-repository.md","tags":["flow","components","catalogue","db.json","tester"],"license":"CC-BY-4.0","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"The Flow components repository: 146 components, the catalogue and the tester","description":"A tour of totaljs/flowstreamcomponents, the open-source components of Flow: what groups it covers, how db.json is generated, how Flow loads the catalogue and how to test a component with the included tester.","messages":[{"role":"user","content":"The Flow components repository: 146 components, the catalogue and the tester\n\nA tour of totaljs/flowstreamcomponents, the open-source components of Flow: what groups it covers, how db.json is generated, how Flow loads the catalogue and how to test a component with the included tester."},{"role":"assistant","content":"[`totaljs/flowstreamcomponents`](https://github.com/totaljs/flowstreamcomponents) holds the open-source components that the editor of Flow offers. In the revision documented here (`e34d283`) it has **146 components** in `components/`, 32 older ones kept in `archived/`, the generator of the catalogue (`db.js`), the catalogue itself (`db.json`) and a tester.\n\n## What the catalogue covers\n\nThe components are organised in groups. Some of the bigger ones:\n\n| Group | Components (examples) |\n|---|---|\n| Common (40) | `delay`, `print`, `counter`, `switch`, `merge`, `code`, `request`, `throttle`, `transform`, `macro`, `modify`, `stringoperations` |\n| Total.js (17) | `taction`, `troute`, `tschema`, `tcors`, `tresponse`, `tauthtoken`, `twebsocketroute`, `tuploadedfiles`, `tsql`, `querybuilderpg` |\n| Total.js API Services (13) | `tapiocrapi`, `tapigeoipapi`, `tapiweatherapi`, `tapismsapi`, `tapichatgptapi`, `tapiinvoiceparserapi`, `tapicheckvatapi`, `tapiprintapi` |\n| Databases (8) | `postgresql`, `mysql`, `sqlite3`, `mongodb-query`, `mongodb-insert`, `influxdb`, `nosql`, `localstorage` |\n| Triggers (7) | `timer`, `trigger`, `crontab`, `minutely`, `notify` |\n| Files (6) | `filewriter`, `filewatcher`, `fileswatcher`, `fsrename`, `fstouch`, `fsunlink` |\n| Flow, FlowStream, Stats, Checks, Security... | `debug`, `flowstats`, `input`, `output`, `publish`, `subscribe`, `stopwatch`, `checkurl`, `checklog`, `checkfreespace`, `totp`, `iplimiter` |\n| MQTT, AI, Ollama | `mqtt-broker`, `mqtt-publish`, `mqtt-subscribe`, `aiagent`, `aitool`, `aicontext`, `chatgpt`, `ollamaprompt`, `ollamals`, `ollamawebsearch` |\n\nBesides, there are components for parsing (`xmlparser`, `dataparser`, `csvdataset`, `cheerio`), dates and time zones (`date`, `timezone`, `duration`), text and templates (`tangular`, `qrcode`), OAuth2 (`oauth2`), downloads (`downloader`, `urldownload`), notifications (`sms`, `rssreader`, `openweather`, `googlesearch`) and the hardware of Linux (`nvidiasmi`). The folder `archived/` keeps the older generation of the family (Raspberry Pi GPIO and I2C, RabbitMQ, system metrics) for reference.\n\n## How Flow finds them: `db.json`\n\nFlow does not download the HTML of every component. It downloads **a catalogue**, a JSON file (by default `https://cdn.totaljs.com/flowstream/webcomponents/db.json`, set in [the settings](/en/flow/settings-and-files/)) with one entry per component:\n\n```json\n{\n  \"id\": \"taction\",\n  \"group\": \"Total.js\",\n  \"name\": \"Action\",\n  \"url\": \"https://cdn.totaljs.com/flowstream/webcomponents/taction.html\",\n  \"author\": \"Peter Širka | Total.js\",\n  \"icon\": \"ti ti-totaljs\",\n  \"color\": \"\",\n  \"version\": \"2\",\n  \"kind\": \"\",\n  \"readme\": \"This component creates similar functionality as Total.js Actions. ...\"\n}\n```\n\nThe editor shows the list with the `readme` and downloads the `url` of the component when you add it. To offer your own components next to the official ones, publish your HTML files and a `db.json` of your own and write its address in the setting `components2`.\n\n`db.js` generates the catalogue: it reads each file of `components/`, takes `group`, `name`, `author`, `icon`, `color`, `version` and `kind` from its `exports`, takes the text of `<readme>`, builds the `url` on the CDN and writes `db.json` sorted by name. Run it with Node.js after you add or change a component.\n\n## The tester\n\n`tester.js` runs a component without the editor and `tests/` has examples (`counter.js`, `example.js`, `example-multiple.js`, `influxdb.js`, `sqlite3.js`, `trigger.js`) and a `readme.md` with all the options. A test looks like this:\n\n```js\nrequire('../tester')(async function(describe, done) {\n\n\tawait describe(function(test, next) {\n\n\t\tlet messages = 0;\n\t\tlet status = 0;\n\n\t\ttest.status = function(data) {\n\t\t\tstatus = data;\n\t\t};\n\n\t\ttest.input('Hello World!');\n\t\tmessages++;\n\n\t\tsetTimeout(function() {\n\t\t\ttest.ok(status === messages, 'Counting');\n\t\t\tnext();\n\t\t}, 2000);\n\n\t});\n\n\tdone();\n});\n```\n\n`describe` loads the component, `test.input(data)` sends it a message, `test.output` and `test.message` receive what it sends, `test.configure({...})` changes its configuration, `test.trigger()` calls its trigger and `test.ok()` and `test.fail()` mark the result. To run all the tests: `cd flowstreamcomponents`, `npm install total4` once, and `node tester.js`. The tester of this revision is written for Total.js 4 (`require('total4')`), as its readme says.\n\n## Contribute\n\nThe repository asks you to follow the coding standards and guidelines of Total.js, and its readme links them; the contact is `info@totaljs.com`. To write a component, see [write a component](/en/flow/write-a-component/)."}],"knowledge":{"content_sha256":"aeb0ed5cbcc859e08dd0521f12ad833aafcab1a12d2cfa6841f445b8ddbc6630","source_sha256":"8d79e0348e9fcdca353b2e2426a18e6f8754cc5ed8266635bd28c56647e794fb","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[],"review":"not-recorded","symbols":["API","CDN","GPIO","HTML","I2C","JSON","MQTT","Total.js"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"cms/what-is-cms","section":"cms","kind":"recipe","framework":"CMS","framework_version":"cms@8071e48","language":"en","url":"https://forge.totaljs.es/en/cms/what-is-cms/","markdown_url":"https://forge.totaljs.es/en/cms/what-is-cms.md","tags":["cms","pages","layouts","widgets","content"],"license":"CC-BY-4.0","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"What is the Total.js CMS: pages, layouts and widgets with no database","description":"The Total.js CMS is a content management system on Total.js 5 with an admin interface, pages, layouts, navigation, widgets, posts, files, redirects, variables, a sitemap and visitor statistics, MIT licensed and with no database to install.","messages":[{"role":"user","content":"What is the Total.js CMS: pages, layouts and widgets with no database\n\nThe Total.js CMS is a content management system on Total.js 5 with an admin interface, pages, layouts, navigation, widgets, posts, files, redirects, variables, a sitemap and visitor statistics, MIT licensed and with no database to install."},{"role":"assistant","content":"The **Total.js CMS** (`totaljs/cms`) is a content management system written with Total.js 5. You build a website from **layouts** (the frame of a page), **pages** (the content and its address) and **widgets** (reusable blocks), you edit it from an admin interface, and the same application serves the public site. It is open source under the **MIT licence** and it needs **no database**: the content lives in the file storage of Total.js inside the folder of the application.\n\n## What you get\n\n- **An admin interface** at `/admin/` with a plugin for each task: dashboard, pages, layouts, navigation, widgets, posts, files, redirects, variables and settings.\n- **Pages and layouts** whose HTML is stored as files, with a title, description and keywords for search engines, nested addresses, a language, a disabled flag and pages for signed-in visitors (see [pages, layouts and navigation](/en/cms/pages-layouts-and-navigation/)).\n- **Widgets**: blocks with their own style, settings form and, when you need it, code that renders them on the server (see [widgets](/en/cms/widgets/)). The official catalogue has 43 ([the widgets repository](/en/cms/the-widgets-repository/)).\n- **Navigation, redirects and variables**: menus described once and drawn by your layout, permanent or temporary redirects, and `$NAME` placeholders that you change in one place.\n- **A sitemap** (`/sitemap.xml`), a page statistics dashboard and **posts** and **files** managers (see [settings, posts and files](/en/cms/settings-posts-and-files/)).\n- **A JSON API** behind the admin, protected by a session, by a token or by [OpenPlatform](/en/openplatform/what-is-openplatform/) (see [the admin API](/en/cms/the-admin-api/)).\n- **Backup and restore** of the whole site in one file.\n\n## How it is built\n\nThe application is small: `index.js` starts Total.js 5, the folder `plugins/` holds the plugins of the admin, `controllers/default.js` renders the public site and `definitions/func.js` loads and saves the metadata of the site. The admin interface uses the Componentator components, and the public pages are compiled by the CMS compiler that is part of Total.js 5 itself.\n\nThese pages describe the CMS (revision `8071e48` of the repository). Continue with [install and first start](/en/cms/install-and-first-start/). The official documentation is at [docs.totaljs.com/cms](https://docs.totaljs.com/cms/)."}],"knowledge":{"content_sha256":"ea92754b1c3f5bdf6adce7612131cac1cf492e6a847544fec063b68ee51656eb","source_sha256":"5566179bcec09be28320f1a187d5c2040a2a591121f601e774b0c17f9792b272","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[],"review":"not-recorded","symbols":["API","CMS","HTML","JSON","MIT","NAME","Total.js"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"cms/install-and-first-start","section":"cms","kind":"recipe","framework":"CMS","framework_version":"cms@8071e48","language":"en","url":"https://forge.totaljs.es/en/cms/install-and-first-start/","markdown_url":"https://forge.totaljs.es/en/cms/install-and-first-start.md","tags":["cms","install","docker","login","token","openplatform"],"license":"CC-BY-4.0","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"Install the Total.js CMS and sign in the first time","description":"Run the CMS with Node.js or Docker on port 8000, sign in with the login and password it generates at the first start, learn the three ways to authenticate the admin and where the data of the site lives.","messages":[{"role":"user","content":"Install the Total.js CMS and sign in the first time\n\nRun the CMS with Node.js or Docker on port 8000, sign in with the login and password it generates at the first start, learn the three ways to authenticate the admin and where the data of the site lives."},{"role":"assistant","content":"## Run it\n\nThe CMS needs Node.js and nothing else: no database, no external service.\n\n```bash\ngit clone https://github.com/totaljs/cms.git\ncd cms\nnpm install\nnpm run start\n```\n\n`npm run start` runs `node index.js 8000`; the port is optional and the default is `8000`. With Docker:\n\n```bash\ndocker pull totalplatform/cms\ndocker run -p 8000:8000 totalplatform/cms\n```\n\n## The first sign-in\n\nAt the first start the CMS creates its only administrator with a **random login and password**. Open `http://localhost:8000/admin/`: until the first successful sign-in the page shows them in a box with a button that fills in the form. After that the plain copy of the password is deleted from the preferences and the box no longer appears, so use the profile of the admin right away to set a login and password of your own, and do the first sign-in as soon as you start a new installation.\n\nUntil you create the first page, the address `/` redirects to `/admin/` while the password is still the generated one; once a page with the address `/` exists, `/` serves your site.\n\n## Three ways to authenticate the admin\n\nThe admin lives under the address of the line `$api : /admin/` of the file `config`. The function `AUTH` of `definitions/auth.js` decides for the requests of that path:\n\n| Mode | When | How |\n|---|---|---|\n| Session | the default | a cookie encrypted with the `salt` of the account; the login and password are checked with SHA-256 and that salt |\n| Token | the line `token : <value>` exists in `config` | send it in the header `x-token`: the request runs as a built-in user called *Bot*, an administrator (`sa`). Ten wrong attempts block the address, and a wrong token gets no answer at all (the request ends with a timeout) |\n| OpenPlatform | the settings hold `op_reqtoken` and `op_restoken` | people sign in with [OpenPlatform](/en/openplatform/how-an-app-connects/) and its groups decide the permissions `pages`, `layouts`, `widgets`, `navigation`, `posts`, `files`, `redirects`, `variables`, `settings` and `admin` |\n\nThe public pages use the function `FUNC.auth` if your code defines it (see [pages, layouts and navigation](/en/cms/pages-layouts-and-navigation/)); without it every visitor is anonymous.\n\n## Where the data lives\n\n| Place | What it holds |\n|---|---|\n| `databases/config.json` | generated values of the installation |\n| `databases/memorize_account.json` | the administrator: name, login, password hash, cookie name and salt |\n| `databases/fs-db/` | the file storage: the metadata of the site (pages, layouts, widgets, navigation, redirects, variables, configuration) and the HTML of every page and layout, the uploaded files and the posts |\n\nTo move the site, copy the `databases/` folder, or use the backup of the admin ([the admin API](/en/cms/the-admin-api/))."}],"knowledge":{"content_sha256":"e49f68bd6abcc6dc32125a400e6d1dc037447613f204e0c2f7bca2f0b5049d9f","source_sha256":"655ba21e7fffa8447a7ac79476b6b6ab412cd72a29a56f47e04fe3adbea93bb5","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[],"review":"not-recorded","symbols":["API","AUTH","CMS","FUNC","HTML","SHA"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"cms/pages-layouts-and-navigation","section":"cms","kind":"recipe","framework":"CMS","framework_version":"cms@8071e48","language":"en","url":"https://forge.totaljs.es/en/cms/pages-layouts-and-navigation/","markdown_url":"https://forge.totaljs.es/en/cms/pages-layouts-and-navigation.md","tags":["cms","pages","layouts","navigation","redirects","variables","sitemap"],"license":"CC-BY-4.0","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"Pages, layouts, navigation, variables and redirects in the Total.js CMS","description":"How the CMS turns a layout and a page into a public address: the element with id CMS, the fields of a page, variables, navigation scripts written with Tangular, redirects, the sitemap and which pages are visible.","messages":[{"role":"user","content":"Pages, layouts, navigation, variables and redirects in the Total.js CMS\n\nHow the CMS turns a layout and a page into a public address: the element with id CMS, the fields of a page, variables, navigation scripts written with Tangular, redirects, the sitemap and which pages are visible."},{"role":"assistant","content":"The CMS keeps the HTML of every layout and page as a file and its data (address, title, layout...) in the metadata of the site. A request to the public site finds a page by its address and renders it inside its layout.\n\n## Layouts\n\nA **layout** is a full HTML document. The element with `id=\"CMS\"` marks the place where the content of the page goes:\n\n```html\n<!DOCTYPE html>\n<html>\n<head>\n\t<meta charset=\"utf-8\">\n</head>\n<body>\n\t<header>$SITE</header>\n\t<div id=\"CMS\"></div>\n\t<footer>@{year}</footer>\n</body>\n</html>\n```\n\n`Layouts/save` takes `name`, `icon`, `color`, `html` and a list of `scripts` that are added to every page of the layout. In the HTML, `$NAME` is replaced by the **variable** of that name (see below) and `@{year}` by the current year.\n\n## Pages\n\nA **page** has a `name`, a `layoutid` (required), an address `url`, and optional `title`, `description`, `keywords`, `language`, `parentid`, `icon`, `color` and the flags `disabled`, `auth`, `pinned` and `nocache`. Its HTML (saved with `pages_save_html`) is what goes inside the `id=\"CMS\"` element.\n\n- If you leave `url` empty the CMS makes one from the name: `Hello World` becomes `/hello-world/`, and under a parent page the address starts with the address of the parent.\n- The public site serves a page when its `url` equals the requested address, it is not `disabled` and its `language`, when set, equals the language of the request.\n- The response gets `<title>` (the `title`, or the `name`, followed by the name of the site for every page except `/`), the description and the keywords, a script that counts the visits (`/visitors.js`) and the style of the widgets that the page uses.\n- `auth` pages are served only to a signed-in visitor. The CMS asks your code who the visitor is: define `FUNC.auth = function($) { ... }` (for example in a file of `definitions/`) and call `$.success(user)`. Without that function everybody is anonymous and an `auth` page answers 404.\n- A disabled page, an unknown address and a page of another language answer 404. The sitemap `/sitemap.xml` lists the pages that are not disabled.\n\n## Variables\n\nThe plugin *Variables* keeps a list of `NAME: value` pairs (`Variables|save` with `vars`). Any `$NAME` in a layout or a page becomes its value when the page is served, which is useful for a phone number, a company name or a link that appears in many places. An unknown `$NAME` stays as written.\n\n## Navigation\n\nA navigation is a menu described in the admin (a name and a tree of links with `name`, `url`, `title`, `icon`, `color`, `target`, `hidden`, `highlight` and `children`) and drawn by your layout with a template written with Tangular. Put a script in the layout:\n\n```html\n<nav>\n\t<script type=\"text/navigation\" name=\"main\">\n\t\t{{ foreach m in value.children }}\n\t\t\t<a href=\"{{ m.url }}\"{{ if m.url === value.url }} class=\"on\"{{ fi }}>{{ m.name }}</a>\n\t\t{{ end }}\n\t</script>\n</nav>\n```\n\nWhen the layout is saved, a navigation called `main` is created if it does not exist; then edit its links in the plugin *Navigation*. In the template, `value.children` is the list of visible links, `value.url` is the address being served. Give the script a `name`; with only an `id` the compiler accepts it but saving the layout does not answer.\n\nThere is also `<script type=\"text/breadcrumb\">` for the trail of parent pages.\n\n## Redirects\n\nA redirect has a `url`, a `target` and `permanent`. The public site checks the redirects before the pages: `/old/` answered with the page of `/about/` when its target was `/about/`.\n\n## Cache\n\nCompiled pages are cached; saving a page, layout, widget or variable clears what depends on it, and a service that runs every two minutes also clears the compiled views."}],"knowledge":{"content_sha256":"6dafba700a2c7e3e5e54ec031945442938cecfc7f1ff1b34d5c5aba4024662cf","source_sha256":"5b74c601f9fb67d47cb0a6a641506d74459f259dedab34b8e2d7cc0e4a8c0d12","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[],"review":"not-recorded","symbols":["CMS","DOCTYPE","FUNC","HTML","NAME","SITE"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"cms/widgets","section":"cms","kind":"recipe","framework":"CMS","framework_version":"cms@8071e48","language":"en","url":"https://forge.totaljs.es/en/cms/widgets/","markdown_url":"https://forge.totaljs.es/en/cms/widgets.md","tags":["cms","widgets","render","class","data-cms"],"license":"CC-BY-4.0","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"Widgets in the Total.js CMS: use them in a page and write your own","description":"How a widget appears in the HTML of a page (the data-cms attribute and its JSON), the parts of a widget file, the CLASS placeholder, rendering a widget on the server with exports.render and installing widgets through the API.","messages":[{"role":"user","content":"Widgets in the Total.js CMS: use them in a page and write your own\n\nHow a widget appears in the HTML of a page (the data-cms attribute and its JSON), the parts of a widget file, the CLASS placeholder, rendering a widget on the server with exports.render and installing widgets through the API."},{"role":"assistant","content":"A **widget** is a block that you place in a page: a headline, an alert, a gallery. It carries its own style, a form to configure it in the editor and, optionally, code that renders it on the server. A widget is one HTML file, like a component of [Flow](/en/flow/write-a-component/), and the CMS stores it in its metadata.\n\n## A widget inside a page\n\nIn the HTML of a page, a widget is a `div` with two attributes:\n\n```html\n<div data-cms=\"headline__%7B%22h%22%3A%22h1%22%2C%22align%22%3A%22left%22%7D\" data-cms-id=\"h1\">\n\t<h1 class=\"w-headline-h\">Hello CMS</h1>\n\t<p>Some text</p>\n</div>\n```\n\n- `data-cms` is `<widget id>__<configuration>`, where the configuration is a JSON object written with `encodeURIComponent`. The editor writes it; you can write it yourself when you generate pages.\n- `data-cms-id` is the identifier of the instance in the page.\n\nWhen the page is served, the CMS removes the `data-cms` attribute, keeps the HTML that is inside, and adds the style of every widget of the page to the head. If the widget is not installed, its block is dropped.\n\n## The parts of a widget file\n\n| Part | What it is for |\n|---|---|\n| `<script total>` | the metadata and the server code: `exports.id`, `name`, `author`, `version`, `preview` (an image, or a data URI), `config` (the defaults), and optionally `dependencies`, `install`, `uninstall` and `render` |\n| `<style>` | the CSS of the widget |\n| `<body>` | the design of the widget as the editor shows it; elements with the class `CMS_edit` are the editable ones; the other `CMS_*` classes (`CMS_repeat`, `CMS_remove`, `CMS_unwrap`...) are markers for the editor, and the compiler strips them before the page is served |\n| `<settings>` | the form that edits `config` in the editor (jComponent inputs whose path starts with `?.`) and a `<script>` with `exports.save(config, el)` that changes the design when the configuration changes |\n| `<template>` | an optional template for the widget |\n| `<readme>` | the help of the widget |\n\n`CLASS` in the style, the settings, the template and the design is replaced by a class that belongs to the widget (`w-` followed by its id), so two widgets never share a rule: in the widget `alert`, `.CLASS-color1` becomes `.w-alert-color1`.\n\n`exports.dependencies` accepts a list or a string separated with commas: an address ending in `.js` becomes a `<script src>` and one ending in `.css` a `<link rel=\"stylesheet\">`, added to the head of the page.\n\n## Render on the server\n\nA widget without `exports.render` is only cleaned and served as it is. With `render` it can build its HTML from the configuration:\n\n```html\n<script total>\n\n\texports.id = 'hello';\n\texports.name = 'Hello';\n\texports.author = 'Me';\n\texports.version = '1';\n\texports.config = { text: 'world' };\n\n\texports.render = function(opt, next) {\n\t\tnext('<p class=\"w-hello\">Hello ' + (opt.config.text || '').toUpperCase() + '</p>', true);\n\t};\n\n</script>\n\n<style>\n\t.CLASS { color: #07c; }\n</style>\n\n<body>\n\t<p class=\"CLASS CMS_edit\">Hello world</p>\n</body>\n```\n\n`opt.config` is the configuration of the instance, `opt.html` the HTML that was inside the block, and `opt.id`, `opt.uid` and `opt.body` describe it. `next(html, replace)` gives the HTML to serve; with `true` as the second argument it replaces the whole block. We saved this file in a local CMS and a page with `data-cms=\"hello__...\"` and `{\"text\":\"mundo\"}` came out as `<p class=\"w-hello\">Hello MUNDO</p>`, with `.w-hello{color:#07c}` in the head. The official widget `picture` uses the same function to add `loading=\"lazy\"` to its image, and `code` to print the code of its settings.\n\n`exports.install` and `exports.uninstall` run with the database of the CMS when the widget is added, changed or removed.\n\n## Install and remove widgets\n\nThe plugin *Widgets* of the admin imports a file; through the API it is `widgets_save` with `{ \"html\": \"<file content>\", \"singleton\": true }` (`singleton` skips the widget when it already exists), `widgets_list`, `widgets_read`, `widgets_detail` and `widgets_remove`. They need the permission `widgets` (see [the admin API](/en/cms/the-admin-api/)). The 43 official widgets are in [the widgets repository](/en/cms/the-widgets-repository/)."}],"knowledge":{"content_sha256":"7c7f694f4ddf21bd01573bc29d3f7c18c074fe27d66d1f6103dbd3de11083bdb","source_sha256":"07b2f743b41444a9abb8970004aad45f3ab5cf57b4a34b9cf703eef5afbb8a62","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[],"review":"not-recorded","symbols":["API","CLASS","CMS","CMS_","CSS","HTML","JSON","MUNDO","URI"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"cms/the-widgets-repository","section":"cms","kind":"recipe","framework":"CMS","framework_version":"cms@8071e48","language":"en","url":"https://forge.totaljs.es/en/cms/the-widgets-repository/","markdown_url":"https://forge.totaljs.es/en/cms/the-widgets-repository.md","tags":["cms","widgets","catalogue","db.json"],"license":"CC-BY-4.0","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"The CMS widgets repository: 43 widgets and how to add them","description":"A tour of totaljs/cms-widgets, the official widgets of the Total.js CMS: what they cover, which ones render on the server, how db.json is generated and how to install a widget from a file.","messages":[{"role":"user","content":"The CMS widgets repository: 43 widgets and how to add them\n\nA tour of totaljs/cms-widgets, the official widgets of the Total.js CMS: what they cover, which ones render on the server, how db.json is generated and how to install a widget from a file."},{"role":"assistant","content":"[`totaljs/cms-widgets`](https://github.com/totaljs/cms-widgets) holds the official widgets of the Total.js CMS. In the revision documented here (`dc92cb7`) it has **43 widgets** in `widgets/`, one HTML file each, all with the group *Content*, plus the script that generates the catalogue (`db.js`). The format of every file is described in [widgets](/en/cms/widgets/).\n\n## What they cover\n\nThe repository puts every widget in one group; the table below only orders them by what their names suggest, to help you find them.\n\n| Kind | Widgets |\n|---|---|\n| Text | `headline`, `paragraph`, `paragraph2`, `article`, `alert`, `list`, `keyvalue`, `properties`, `summary`, `tags`, `code`, `codehighlighter` |\n| Layout | `container`, `vcontainer`, `columns100`, `columnshalfs`, `columnsthirds`, `panelsthirds`, `box`, `emptyspace`, `line`, `gradient` |\n| Text with image | `textimageleft`, `textimageright`, `textimagecenter`, `textimageflex`, `features2`, `features3`, `insight`, `team`, `partners`, `expansionlist` |\n| Images and media | `picture`, `images`, `imagelinks`, `gallery`, `video`, `bufferwall` |\n| Links and buttons | `linkbutton`, `buttonsgroup`, `socialnetworks` |\n| Integration | `importer`, `uibuilder` (see [UI Builder](/en/uibuilder/what-is-uibuilder/)) |\n\nNine of them use `exports.render` to build their HTML on the server (`bufferwall`, `code`, `codehighlighter`, `images`, `imagelinks`, `importer`, `picture`, `partners` and `uibuilder`); the rest are static blocks that the editor configures and the CMS serves as they are.\n\n## Add a widget to your CMS\n\nEach file is a complete widget. In the admin, open the plugin *Widgets* and import the file, or send it with the API:\n\n```json\n{ \"schema\": \"widgets_save\", \"data\": { \"html\": \"<the content of headline.html>\", \"singleton\": true } }\n```\n\n`POST` it to `/admin/` with the session cookie or the `x-token` header ([the admin API](/en/cms/the-admin-api/)).\n\n## The catalogue: `db.json`\n\nThe editor can list the widgets of a catalogue. `db.js` creates it: it reads every file of `widgets/`, takes `name`, `author` and `preview` from its `exports`, builds the address `https://cdn.totaljs.com/cms/<file>` and writes `db.json` sorted by name, with one entry per widget (`id`, `name`, `url`, `author`, `preview`). `db.js` starts with `require('total4')`, so it needs Total.js 4 to run, as in the repository of the components of Flow ([the components repository](/en/flow/the-components-repository/)).\n\n## Your own widgets\n\nCopy the closest widget, change `exports.id` and `exports.name`, and adapt the style, the design and the settings. If the widget needs a library, list it in `exports.dependencies` and the CMS adds it to the head of the pages that use it."}],"knowledge":{"content_sha256":"4ad631d644c4203883e4d4e49114a1dac43eb6eb1e92f4c319e9d4c360676593","source_sha256":"209c74a850ef0a379142cf1726e322cdbbcdaaf32582318e55596bcfa8b81513","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[],"review":"not-recorded","symbols":["API","CMS","HTML","POST","Total.js"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"cms/the-admin-api","section":"cms","kind":"recipe","framework":"CMS","framework_version":"cms@8071e48","language":"en","url":"https://forge.totaljs.es/en/cms/the-admin-api/","markdown_url":"https://forge.totaljs.es/en/cms/the-admin-api.md","tags":["cms","api","admin","backup","restore","permissions"],"license":"CC-BY-4.0","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"The admin API of the Total.js CMS: manage a site from code, backup and restore","description":"The JSON API behind the CMS admin at /admin/: the call format, the actions of each plugin and their permissions, uploading files, and the three routes for backup, restore and clear, including what clear really does.","messages":[{"role":"user","content":"The admin API of the Total.js CMS: manage a site from code, backup and restore\n\nThe JSON API behind the CMS admin at /admin/: the call format, the actions of each plugin and their permissions, uploading files, and the three routes for backup, restore and clear, including what clear really does."},{"role":"assistant","content":"Everything the admin interface does goes through one endpoint: `POST /admin/` with the body `{ \"schema\": \"<name>\", \"data\": { ... } }` (the Total.js API routes, see [API routes](/en/total5/api-routes/)). The address comes from the line `$api : /admin/` of the file `config`. Authenticate with the session cookie (`POST /admin/` with `schema: \"login\"` and the `login` and `password`), with the header `x-token` or through OpenPlatform ([install and first start](/en/cms/install-and-first-start/)).\n\n## The actions\n\n| Plugin | Calls (`schema`) | Permission |\n|---|---|---|\n| Account | `login`, `logout`, `admin_read`, `admin_save` (name, login, password), `account` | the session |\n| Pages | `pages_list`, `pages_read`, `pages_save`, `pages_clone`, `pages_remove`, `pages_links`, `pages_html` (reads the HTML), `pages_save_html` (`id`, `html`, up to 5 MB), `pages_copy` (`from`, `to`) | `pages` |\n| Layouts | `layouts_list`, `layouts_read`, `layouts_save`, `layouts_clone`, `layouts_remove`, `layouts_html`, `layouts_save_html`, `layouts_import` | `layouts` |\n| Widgets | `widgets_list`, `widgets_read`, `widgets_save`, `widgets_remove`, `widgets_detail` | `widgets` |\n| Navigation | `nav_list`, `nav_read`, `nav_save`, `nav_remove`, `nav_editor` | `navigation` |\n| Redirects | `redirects_list`, `redirects_read`, `redirects_save` (`url`, `target`, `permanent`), `redirects_remove` | `redirects` |\n| Variables | `variables_read`, `variables_save` (`vars`) | `variables` |\n| Posts | `posts`, `posts_read`, `posts_create`, `posts_update`, `posts_remove`, `posts_clear`, `posts_categories` | `posts` |\n| Files | `files_list`, `files_rename`, `files_clear`, `files_remove`, and the uploads below | `files` |\n| Settings | `settings_read`, `settings_save`, `settings_test` (tries the SMTP settings), `chatgpt` | `settings` |\n| Dashboard | `dashboard_online`, `dashboard_referers`, `dashboard_browsers`, `dashboard_pages`, `dashboard_stats` | none |\n\nThe permission `admin` (and every user with the flag `sa`, including the *Bot* of the token) is allowed in all of them. A call without permission is answered with an error, and `login` is the only call that does not need a session.\n\nCreate a page and its content:\n\n```json\n{ \"schema\": \"pages_save\", \"data\": { \"name\": \"About\", \"layoutid\": \"<layout id>\", \"url\": \"/about/\", \"title\": \"About us\" } }\n{ \"schema\": \"pages_save_html\", \"data\": { \"id\": \"<page id>\", \"html\": \"<h1>About us</h1>\" } }\n```\n\n`pages_save` answers `{ \"success\": true }`; read the id with `pages_list`. For a layout, `layouts_save` answers the id of the layout in `value`. When a field is missing from an update, the field is cleared (a `title` that you do not send is saved empty), so send the whole record.\n\n## Uploading files\n\n`POST /admin/upload/` takes a multipart upload (up to 10 MB) and `POST /admin/upload/base64/` a JSON body with a data URI (png, jpeg or gif). Both answer a list of `{ id, name, type, ext, size, url }` where `url` is `/download/<id>.<ext>`.\n\n## Backup, restore and clear\n\nThree routes of `controllers/api.js` work on the whole site and need the permission `admin`:\n\n| Route | What it does |\n|---|---|\n| `GET /admin/backup/` | downloads the whole file storage (metadata, HTML of pages and layouts, files and posts) as one text file named after the site and the date |\n| `POST /admin/restore/` | uploads such a file (up to 100 MB) and loads it. |\n| `GET /admin/clear/` | **deletes the content of the site.** It drops the file storage and loads it again, so pages, layouts, widgets and the rest are removed |\n\nMake a backup before you try any of these, never open `/admin/clear/` in a browser out of curiosity, and keep the token private: with it, a single `GET` empties the site."}],"knowledge":{"content_sha256":"d0108c50f8c67f006e4deadc87b0c0812812b447c52bc468e4579ab8c2d503b5","source_sha256":"6ddcc7b4955e4acd55c342e00d897058a5f1479a43dfc3c44bd0826aa72b2683","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[],"review":"not-recorded","symbols":["API","GET","HTML","JSON","POST","SMTP","Total.js","URI"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"cms/settings-posts-and-files","section":"cms","kind":"recipe","framework":"CMS","framework_version":"cms@8071e48","language":"en","url":"https://forge.totaljs.es/en/cms/settings-posts-and-files/","markdown_url":"https://forge.totaljs.es/en/cms/settings-posts-and-files.md","tags":["cms","settings","posts","files","visitors","storage"],"license":"CC-BY-4.0","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"CMS settings, posts, files, visitors and where everything is stored","description":"The settings of the Total.js CMS, the posts manager, how uploaded files are served and resized, the visitor statistics and the dashboard, the OpenPlatform integration and the data model of the site.","messages":[{"role":"user","content":"CMS settings, posts, files, visitors and where everything is stored\n\nThe settings of the Total.js CMS, the posts manager, how uploaded files are served and resized, the visitor statistics and the dashboard, the OpenPlatform integration and the data model of the site."},{"role":"assistant","content":"## Settings\n\nThe plugin *Settings* (`settings_read`, `settings_save`; permission `settings`) edits the configuration that the CMS keeps inside the metadata of the site and loads into `CONF` at every start:\n\n| Key | Meaning |\n|---|---|\n| `name` | the name of the site, used in the `<title>` of every page except `/` and in the file name of the backup |\n| `url` | the public address of the site; the CMS also updates it from the host of the admin requests |\n| `smtp` | the SMTP settings as a JSON text (`server`, `from`, `user`, `password`, `port`); `settings_test` tries them |\n| `icon` | an icon for the site |\n| `$tms`, `secret_tms` | publish the events by TMS, and the secret of that connection |\n| `op_reqtoken`, `op_restoken` | the tokens of an [OpenPlatform](/en/openplatform/how-an-app-connects/); when both are set the admin sign-in is replaced by the one of OpenPlatform |\n| `totalapi` | a key for the Total.js API services |\n\nPlugins can add more keys, which appear in the same form. Values that live in the file `config` (`$api`, `cdn`, `salt` and, if you add it, `token`) are not edited here.\n\n## Posts\n\nThe plugin *Posts* is a small manager of articles, kept in its own NoSQL file inside the storage. A post has a `name`, `category`, `picture`, `summary`, `body`, `reference`, `icon`, `color`, `hidden` and `date`; the admin lists them newest first (100 per page) and `posts_categories` returns the categories in use. The posts are data for your own code: the public renderer of the CMS (`controllers/default.js`) does not print them, so a widget or a route of yours has to read them and draw them.\n\n## Files\n\nThe plugin *Files* stores uploads in the file storage and marks them as public. `GET /download/<id>.<ext>` serves a public file (only the public ones; the rest answer 404). For images, `?s=<percent>` returns a smaller JPEG (the value must be between 10 and 80, with quality 80), and `?download=1` forces a download. The upload routes and their limits are in [the admin API](/en/cms/the-admin-api/).\n\n## Visitors and the dashboard\n\nThe module `modules/visitors.js` counts the visits of the public pages through the script `/visitors.js` that every page includes: unique visitors, hits, browsers, referrers, search, social and direct traffic, mobile and desktop, hours of the day, and the people online (a visitor is online for 20 minutes without activity). The *Dashboard* plugin draws those numbers from `dashboard_stats`, `dashboard_online`, `dashboard_browsers`, `dashboard_referers` and `dashboard_pages`.\n\n## OpenPlatform\n\nWhen `op_reqtoken` and `op_restoken` are set, the CMS signs people in through [OpenPlatform](/en/openplatform/what-is-openplatform/) (module `modules/openplatform.js`) and each plugin contributes its permission to the list that OpenPlatform shows when you assign groups: `pages`, `layouts`, `widgets`, `navigation`, `posts`, `files`, `redirects`, `variables`, `settings` and `admin`. Remember the rule of OpenPlatform: an application is visible only to the groups that you assign to it.\n\n## The data model\n\n`MAIN.db` holds the metadata of the site (`pages`, `layouts`, `widgets`, `redirects`, `vars`, `nav`, `config` and `storage`) and it is saved as one JSON document called `meta` in the file storage. The HTML of every page and layout is a file of that storage whose name is the id of the page or layout. `FUNC.load()` reads the metadata at start, compiles the widgets (running their `install` function) and rebuilds the caches; `FUNC.save()` writes it back. That is why a backup of the storage is a backup of the site."}],"knowledge":{"content_sha256":"111717bf1e02afa7bbb01896ed4e92ee49e22fc18bc8590b127b73e3e0868ce6","source_sha256":"407e5d28a31df0b394073d104e5c3b4b9435d9fcf890cfbee2470c08d70d0cd4","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[],"review":"not-recorded","symbols":["API","CMS","CONF","FUNC","GET","HTML","JPEG","JSON","MAIN","SMTP","TMS","Total.js"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"todomator/what-is-todomator","section":"todomator","kind":"recipe","framework":"Todomator","framework_version":"todomator@1.7","language":"en","url":"https://forge.totaljs.es/en/todomator/what-is-todomator/","markdown_url":"https://forge.totaljs.es/en/todomator/what-is-todomator.md","tags":["todomator","tasks","tickets","overview"],"license":"CC-BY-4.0","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"What Todomator is: task management for small teams","description":"Todomator is the Total.js task manager: tickets with folders, tags, assignees, comments, attachments, time tracking and a REST API, free and open source under MIT.","messages":[{"role":"user","content":"What Todomator is: task management for small teams\n\nTodomator is the Total.js task manager: tickets with folders, tags, assignees, comments, attachments, time tracking and a REST API, free and open source under MIT."},{"role":"assistant","content":"Todomator is a **task manager for small teams**: the people of a team create tickets, assign them, comment on them, attach files, track the time they take and follow what is pending. It is made by Total.js, it is open source under the **MIT licence** and free to use, also in commercial projects, and its repository is [totaljs/todomator](https://github.com/totaljs/todomator) (version 1.7). The project recommends it for teams of up to 30 members, and you can run one instance per team.\n\nThese pages describe Todomator (revision `af448eb` of the repository).\n\n## What it gives you\n\n- **Tickets** with a name, a status, folders, tags, a deadline, a priority flag, a billable flag, subtasks and links between tickets, written in Markdown (see [tickets, folders and tags](/en/todomator/tickets-folders-and-tags/)).\n- **Who sees what.** A ticket is visible to its owner, to the people assigned to it, to its watchers, or to everybody when it is public.\n- **Time tracking** with a start/stop timer or manual entries, for billing and payouts (see [time tracking](/en/todomator/time-tracking/)).\n- **Comments, attachments and notifications**, with unread marks and live updates through a WebSocket.\n- **An open REST API** to create tickets from other systems, for example from an email or from the contact form of a website (see [create tickets with the API](/en/todomator/create-tickets-with-the-api/)).\n- **Two-factor authentication** (added in version 1.7), search, a calendar of tickets and bookmarks.\n- **Premium plugins** (reminder, reports, user workload, extensions) that are part of the [Total.js Enterprise service](https://www.totaljs.com/enterprise/); the core of Todomator does not need them.\n\n## How it is built\n\nTodomator is a Total.js 5 application that stores its data in PostgreSQL through `querybuilderpg`. The interface is built with jComponent and the Componentator components, and the API uses the Total.js API routes. Install it by following [install Todomator](/en/todomator/install-todomator/); the [settings and the database](/en/todomator/settings-and-database/) are described on their own page. The official documentation is at [docs.totaljs.com/todomator](https://docs.totaljs.com/todomator/)."}],"knowledge":{"content_sha256":"ebc81a61f6fd57548355627d025781f2ca171530c47456ec8a0d32c472af022b","source_sha256":"e0eee4ca4951d31f546353f74519f3545e797b0d023183256d946a6ee26d364c","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[],"review":"not-recorded","symbols":["API","MIT","REST","Total.js"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"todomator/install-todomator","section":"todomator","kind":"recipe","framework":"Todomator","framework_version":"todomator@1.7","language":"en","url":"https://forge.totaljs.es/en/todomator/install-todomator/","markdown_url":"https://forge.totaljs.es/en/todomator/install-todomator.md","tags":["todomator","install","docker","postgresql"],"license":"CC-BY-4.0","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"Install Todomator with Node.js and PostgreSQL, Docker or Docker Compose","description":"Start Todomator by hand, with the Docker Hub image or with Docker Compose, what the first start does, the default account and how to update an existing database.","messages":[{"role":"user","content":"Install Todomator with Node.js and PostgreSQL, Docker or Docker Compose\n\nStart Todomator by hand, with the Docker Hub image or with Docker Compose, what the first start does, the default account and how to update an existing database."},{"role":"assistant","content":"Todomator needs the latest **Node.js** and a **PostgreSQL** database. Do **not** run `database.sql` yourself: Todomator uses that script internally when it starts on an empty database.\n\n## By hand\n\n1. Install Node.js and PostgreSQL and create a database for Todomator.\n2. Download the [source code](https://github.com/totaljs/todomator).\n3. Install the dependencies in the root of the application:\n\n```bash\nnpm install\n```\n\n4. Set the connection in the file `config`. It reads the environment variable `DATABASE` (line `database (env) : DATABASE`) and has a line `database : postgresql://total:platform@127.0.0.1:5432/todomator` as a model. The same file has `$api : /api/` (the address of the API) and a `mail` line with the SMTP settings as a JSON object.\n5. Run it (the `start` script is `node index.js 8000`):\n\n```bash\nnode index.js\n```\n\n6. Open `http://127.0.0.1:8000`.\n\n## Docker Hub\n\n```bash\ndocker pull totalplatform/todomator\ndocker run --env DATABASE='postgresql://user:pass@hostname/database' -p 8000:8000 totalplatform/todomator\n```\n\n## Docker Compose\n\n```bash\ngit clone https://github.com/totaljs/todomator.git\ncd todomator\ndocker compose up\n```\n\n## The first start\n\nIf the table `tbl_ticket` does not exist in the schema `public`, Todomator runs `database.sql` with a generated identifier, a secret and the password hash of the first user, then loads the configuration and the tags. With the environment variable `DATABASE` (Docker) it waits three seconds first. The script creates the statuses, the first user and the configuration. The server stays paused (`PAUSESERVER('Database')`) until the database is ready.\n\n## The first sign-in\n\nThe README gives the default account:\n\n```text\nlogin : info@totaljs.com\npassword : admin\n```\n\nThe account is created with the name John Connor and administrator rights (`sa`). **Change the password** after signing in, and configure the [settings](/en/todomator/settings-and-database/).\n\n## Updating\n\nFor an existing installation the README points to the update scripts of the repository instead of `database.sql`: `database_1-3.sql`, `database_1-4.sql`, `database_1-5.sql`, `database_1-6.sql` and `database-20240119.sql`. Apply the ones that are newer than your version; the file `version` of the repository lists what each release changed (for example, version 1.7 added two-factor authentication, improved the search and the plugins of reports, bookmarks and users)."}],"knowledge":{"content_sha256":"300e289fdd80ff13b51f7fb675fbdb3de93238d77d41e733cd1392362638851d","source_sha256":"7ca0c7ff1897eb62f21222684997cbedded0b0f6177f786b9421734caa51f7d9","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[],"review":"not-recorded","symbols":["API","DATABASE","JSON","PAUSESERVER","README","SMTP"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"todomator/tickets-folders-and-tags","section":"todomator","kind":"recipe","framework":"Todomator","framework_version":"todomator@1.7","language":"en","url":"https://forge.totaljs.es/en/todomator/tickets-folders-and-tags/","markdown_url":"https://forge.totaljs.es/en/todomator/tickets-folders-and-tags.md","tags":["todomator","tickets","folders","tags","comments"],"license":"CC-BY-4.0","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"Tickets, folders, statuses and tags in Todomator","description":"The data model of Todomator: what a ticket holds, the six statuses, who can see a ticket, folders for customers and projects, tags, comments, links and attachments.","messages":[{"role":"user","content":"Tickets, folders, statuses and tags in Todomator\n\nThe data model of Todomator: what a ticket holds, the six statuses, who can see a ticket, folders for customers and projects, tags, comments, links and attachments."},{"role":"assistant","content":"The center of Todomator is the **ticket** (table `tbl_ticket`, schema `Tickets` in `schemas/tickets.js`). Everything else organizes tickets or hangs from them.\n\n## What a ticket holds\n\n| Field | Meaning |\n|---|---|\n| `name` | the title (required) |\n| `statusid` | the status; a new ticket is `pending` unless you say otherwise |\n| `folderid` | the folder (customer or project) it belongs to |\n| `ownerid`, `userid`, `watcherid` | the author, the people assigned and the people who only follow it |\n| `ispublic` | visible to everybody; a public ticket has no assigned people |\n| `ispriority`, `isbillable` | a priority mark and a billable flag |\n| `markdown`, `html` | the content (the interface uses Markdown) |\n| `tags` | a list of tags |\n| `date`, `deadline` | the day of the ticket (today by default) and an optional deadline |\n| `estimate`, `worked` | the estimate and the minutes already worked ([time tracking](/en/todomator/time-tracking/)) |\n| `reference`, `source` | free text to connect the ticket with something outside |\n| `parentid` | the parent ticket, for subtasks |\n| `attachments` | the files of the ticket |\n| `attrs` | free JSON data |\n| `callback` | an address that receives the ticket when it changes ([API](/en/todomator/create-tickets-with-the-api/)) |\n\n## The six statuses\n\nThe table `cl_status` is created with: `pending` (Pending), `open` (In progress), `review` (Review), `postponed` (Postponed), `note` (Note) and `closed` (Closed). The names are translated into the language of the user.\n\n## Who can see a ticket\n\nA ticket is visible to its owner, to the people in `userid`, to the people in `watcherid` and to everybody when `ispublic` is set. The list of folders that a user sees is limited the same way: a folder appears only if the user can see a ticket in it (users with `sa` see all). The list of tickets takes `type` (for example `pending`, `postponed`, `review`, `assigned`, `note`, `unread` or `all`), `q` (search), `folderid`, `date`, `skip` and `limit` (10 by default).\n\n## Folders and tags\n\nA **folder** has a name, an icon, a colour, a customer, an email and a phone, and flags: `isbillable` (new tickets inherit it), `isprivate`, `ispinned`, `isarchived` and `isdisabled`. Only people with the `admin` permission (or administrators) manage folders. A **tag** has a name, a colour and an icon, and can belong to a folder.\n\n## Around the ticket\n\n- **Comments** (`Comments|create`, `Comments|update`, `Comments|remove`): Markdown text attached to a ticket.\n- **Unread marks and bookmarks**: each user has their own unread state and bookmarks for a ticket.\n- **Links** between tickets, and **clone** of a ticket.\n- **History and logs** of the changes of a ticket.\n- **Attachments**: `POST /upload/` takes up to 5 MB per request. The address of a file is signed (`/download/<id>-<signature>.<ext>`): images, video and PDF open in the browser and the other types are downloaded.\n- **Live updates**: the WebSocket sends a `refresh` event only to the people who can see the ticket, and tells the others who is looking at a ticket right now."}],"knowledge":{"content_sha256":"34671bd5d1988af591d37c044efd7371cc9c6952df0f651981fc840762dd480a","source_sha256":"7cda1b5e35bb575715b9e776b132a149713e00a2ac5853e4a4359c1d5ab8d316","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[],"review":"not-recorded","symbols":["API","JSON","PDF","POST"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"todomator/time-tracking","section":"todomator","kind":"recipe","framework":"Todomator","framework_version":"todomator@1.7","language":"en","url":"https://forge.totaljs.es/en/todomator/time-tracking/","markdown_url":"https://forge.totaljs.es/en/todomator/time-tracking.md","tags":["todomator","time tracking","billing"],"license":"CC-BY-4.0","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"Time tracking in Todomator: timers, manual entries and the minimum log time","description":"How Todomator records the time spent on a ticket: a start/stop timer, manual entries, the minimum and maximum minutes, and how the total of a ticket is kept.","messages":[{"role":"user","content":"Time tracking in Todomator: timers, manual entries and the minimum log time\n\nHow Todomator records the time spent on a ticket: a start/stop timer, manual entries, the minimum and maximum minutes, and how the total of a ticket is kept."},{"role":"assistant","content":"Todomator records working time in the table `tbl_ticket_time` and keeps the sum in the field `worked` (minutes) of the ticket. The actions are in `schemas/tickets.js`.\n\n## The timer\n\n- **Start** (`logwork_start`): opens a timer on a ticket for the current user. If the ticket was `pending`, its status changes to `open` automatically.\n- **Stop** (`logwork_stop`): closes the timer and adds the elapsed minutes (rounded up) to the ticket. A single timer counts at most **8 hours**, and never less than the setting `minlogtime`.\n- **Open timers** (`logwork_open`): lists the timers that the user has running.\n\n## Manual entries\n\n`logwork_create` adds an entry with the ticket, the minutes, a date and an optional note; `logwork_update` and `logwork_remove` change or delete it, and after an update the total of the ticket is recalculated from all its entries. Entries are never smaller than `minlogtime`. You can also pass `worked` when you create a ticket (also through the [API](/en/todomator/create-tickets-with-the-api/)): Todomator then records an entry of that many minutes.\n\n## The setting\n\n`minlogtime` (minutes, 1 by default) is in the [settings](/en/todomator/settings-and-database/). It is the shortest entry that Todomator records, which avoids rows of zero minutes.\n\n## Notifications and billing\n\nEvery logged time notifies the other people of the ticket and refreshes it live for those who can see it. The flag `isbillable` of the ticket (inherited from the folder) marks the time that you bill, and the premium plugins of reports and user workload build on these records."}],"knowledge":{"content_sha256":"846740c7eea059a9159b38ff4b39d454fe32cbf6fbcc52d53c7ed2950844d9e5","source_sha256":"e838ff02509d8ec72823b5b49558fa1515f8a3210dd8f52a246c9d08b5c4df4b","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[],"review":"not-recorded","symbols":["API"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"todomator/create-tickets-with-the-api","section":"todomator","kind":"recipe","framework":"Todomator","framework_version":"todomator@1.7","language":"en","url":"https://forge.totaljs.es/en/todomator/create-tickets-with-the-api/","markdown_url":"https://forge.totaljs.es/en/todomator/create-tickets-with-the-api.md","tags":["todomator","api","token","tickets","integration"],"license":"CC-BY-4.0","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"Create tickets from other systems with the Todomator API","description":"Authenticate with the token of Todomator and create a ticket with one JSON call, from a script, an email or a website form, with folders, people, tags, attachments and a callback.","messages":[{"role":"user","content":"Create tickets from other systems with the Todomator API\n\nAuthenticate with the token of Todomator and create a ticket with one JSON call, from a script, an email or a website form, with folders, people, tags, attachments and a callback."},{"role":"assistant","content":"Todomator exposes its actions through one API endpoint: `POST /api/` with a JSON body `{ \"schema\": \"<name>\", \"data\": { ... } }` (the Total.js API routes; see the recipe [API routes](/en/total5/api-routes/)). The address `/api/` comes from the line `$api` of the `config` file.\n\n## Authenticate with the token\n\nBesides the session of a signed-in person, the API accepts a **token**: send it in the header `x-token` or in the query parameter `token`. If it equals the setting `token` (see [settings](/en/todomator/settings-and-database/)), the request runs as a built-in user called *Bot*, an administrator with the permission `bot`; any other token is refused. Use a long random token, keep it on your server and send it only over HTTPS.\n\n## Create a ticket\n\nThe name of the call is `tickets_create`. Only `name` is required; the rest is optional:\n\n| Field | Notes |\n|---|---|\n| `name` | the title |\n| `folder` or `folderid` | the folder, by **name** (Todomator looks it up) or by id; the ticket takes the billable flag of the folder |\n| `users` | the people to assign, by **name or email**; an unknown person is an error (`User \"...\" not found`) |\n| `watcherid` | the people who follow it, by **id** (unlike `users`, the schema of this version does not take them by name) |\n| `statusid` | `pending` (default), `open`, `review`, `postponed`, `note` or `closed` |\n| `markdown`, `html` | the content |\n| `tags`, `ispriority`, `isbillable`, `ispublic` | tags and flags |\n| `date`, `deadline`, `worked` | the day, a deadline and minutes already worked |\n| `attachments` | a list of `{ name, data }` where `data` is the file in Base64 |\n| `reference`, `source`, `attrs` | free fields to link the ticket with your system |\n| `callback` | an address that receives the ticket |\n\n```json\n{\n\t\"schema\": \"tickets_create\",\n\t\"data\": {\n\t\t\"name\": \"The invoice page fails with a 500\",\n\t\t\"folder\": \"Customer support\",\n\t\t\"users\": [\"jane@example.com\"],\n\t\t\"markdown\": \"Reported by the contact form.\\n\\nSteps: open the invoice and click *Print*.\",\n\t\t\"tags\": [\"bug\"],\n\t\t\"source\": \"website\",\n\t\t\"reference\": \"form-2041\"\n\t}\n}\n```\n\nThe answer is the id of the new ticket. The people assigned are notified and the open sessions of the people who may see it are refreshed.\n\n## The callback\n\nWhen `callback` is set, Todomator sends a `POST` to that address with the ticket data when the ticket is created, updated or cloned, and when a comment is added (on creation and update it also sends the list of the fields that were given, `keys`). Your system can use it to mirror the status of a ticket.\n\n## Other calls\n\nThe same endpoint serves the rest of the actions, with the names of `controllers/api.js`: `tickets` (list), `tickets_detail/{id}`, `tickets_update/{id}`, `tickets_clone/{id}`, `tickets_remove/{id}`, `logwork_create`, `folders`, `tags` and more. The official API documentation is at [docs.totaljs.com/todomator](https://docs.totaljs.com/todomator/)."}],"knowledge":{"content_sha256":"1ef7cc7f899dabeb5c0827e652b42f39755b3320dc1891cbd1a0b0579c7ba6cc","source_sha256":"a10f119b50698f7f3d41087f710edc038a1712ee0527d87adfc9574f8b728926","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[],"review":"not-recorded","symbols":["API","HTTPS","JSON","POST","Total.js"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"todomator/settings-and-database","section":"todomator","kind":"recipe","framework":"Todomator","framework_version":"todomator@1.7","language":"en","url":"https://forge.totaljs.es/en/todomator/settings-and-database/","markdown_url":"https://forge.totaljs.es/en/todomator/settings-and-database.md","tags":["todomator","settings","database","permissions"],"license":"CC-BY-4.0","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"Todomator settings and database tables","description":"The settings that Todomator keeps, the permissions that guard them and what each table of the public schema is for.","messages":[{"role":"user","content":"Todomator settings and database tables\n\nThe settings that Todomator keeps, the permissions that guard them and what each table of the public schema is for."},{"role":"assistant","content":"## The settings\n\nTodomator keeps its configuration in the table `cl_config` and reloads it every 20 minutes. The settings screen (`Settings/read`, `Settings/save` in `schemas/settings.js`) edits these keys, and needs the permission `settings`:\n\n| Key | Meaning |\n|---|---|\n| `name` | the name of the instance |\n| `color` | the main colour |\n| `token` | the token of the [API](/en/todomator/create-tickets-with-the-api/) |\n| `minlogtime` | the shortest time entry, in minutes (1 by default) ([time tracking](/en/todomator/time-tracking/)) |\n| `backup` | when on, each edit of the Markdown of a ticket also stores a copy of the text, with the user, the address and the browser (table `tbl_ticket_backup`) |\n| `folderid` | a default folder |\n\n## Permissions\n\nA person is an administrator when the flag `sa` is set or when the permission list contains `admin`. `admin` manages folders and the screens of administration; `settings` allows the settings; `bot` belongs to the built-in user of the API token. The sessions live in the table `tbl_session`, and expired ones are deleted once a day.\n\n## Two-factor authentication\n\nEach user has the flag `is2fa`, which the person turns on when changing the password (`Users|password`). Sign-in then has two steps (`plugins/users/index.js`): email and password first; if the account has `is2fa`, Todomator emails a six-digit code (template `templates/mail-2fa.html`) that is valid for 10 minutes, and the person sends it back to finish. Wrong attempts are blocked after 10 tries in 10 minutes. The `mail` line of the `config` file must be set for the code to be delivered.\n\n## The database\n\nEverything lives in the `public` schema of PostgreSQL:\n\n| Table | What it holds |\n|---|---|\n| `cl_config` | the configuration |\n| `cl_status` | the six statuses of a ticket |\n| `cl_notification` | the types of notification |\n| `tbl_user` | the users: profile, password hash, permissions and `is2fa` |\n| `tbl_folder` | the folders (customers and projects) |\n| `tbl_tag` | the tags |\n| `tbl_ticket` | the tickets |\n| `tbl_ticket_comment` | the comments of the tickets |\n| `tbl_ticket_time` | the time entries |\n| `tbl_ticket_bookmark` | the bookmarks of each user |\n| `tbl_ticket_unread` | the unread state per user and ticket |\n| `tbl_ticket_data` | free data of a ticket (`data_read` / `data_save`) |\n| `tbl_ticket_backup` | copies of the Markdown of a ticket (the `backup` setting) |\n| `tbl_notification` | the notifications of the users |\n| `tbl_file` | the files that were uploaded |\n| `tbl_session` | the sessions |\n\nBack this schema up like any other PostgreSQL database. For an update, apply the `database_*.sql` scripts of the repository that are newer than your version ([install Todomator](/en/todomator/install-todomator/))."}],"knowledge":{"content_sha256":"ebcc4e81d3b881dd5bf445b6b159386aeadd1789cc2a6d4c388975a513a54435","source_sha256":"edd20472785628233f4ef013d116e632fe55d52cb97fd08137ea9e15ea46a78b","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[],"review":"not-recorded","symbols":["API"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"uibuilder/what-is-uibuilder","section":"uibuilder","kind":"recipe","framework":"UI Builder","framework_version":"uibuilder@1","language":"en","url":"https://forge.totaljs.es/en/uibuilder/what-is-uibuilder/","markdown_url":"https://forge.totaljs.es/en/uibuilder/what-is-uibuilder.md","tags":["uibuilder","ui","designer","overview"],"license":"CC-BY-4.0","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"What UI Builder is: design interfaces as a JSON file","description":"UI Builder is the Total.js visual editor for user interfaces: web apps, dashboards, flowboards and parts, described by a JSON file and rendered by a web component, with no server code.","messages":[{"role":"user","content":"What UI Builder is: design interfaces as a JSON file\n\nUI Builder is the Total.js visual editor for user interfaces: web apps, dashboards, flowboards and parts, described by a JSON file and rendered by a web component, with no server code."},{"role":"assistant","content":"UI Builder is a **visual editor for user interfaces**. You drag components onto a canvas, configure them and connect them, and the result is not code but a **JSON file** that describes the interface. A small web component renders that file anywhere, so a UI Builder design needs no server-side implementation. It is open source under the **MIT licence**; the editor is the repository [totaljs/uibuilder](https://github.com/totaljs/uibuilder), and you can use the cloud version at [uibuilder.totaljs.com](https://uibuilder.totaljs.com).\n\nThese pages describe UI Builder (revision `620107a` of the repository).\n\n## What it is for\n\nThe templates of the editor show the kinds of interfaces it builds:\n\n| Template | Use |\n|---|---|\n| Web app | a classic web application |\n| Dashboard | a dashboard of data widgets |\n| Flowboard | a board connected to a flow of data (it works with [Total.js Flow](https://www.totaljs.com/flow/)) |\n| Part | a reusable piece of interface |\n\nThe editor also lists templates for mobile apps and HMI panels, which are switched off in this version. Total.js presents UI Builder for web forms, dashboards, intranets, IoT screens, ERP and CRM interfaces.\n\n## Why it fits Total.js\n\n- **The design is data.** A JSON file is easy to store in a database, to version, to generate and to give to another tool.\n- **It needs no special environment.** The renderer is a jComponent web component, so any page can show a design.\n- **Your own components.** A component is one HTML file with its style, its code and its settings form ([build a component](/en/uibuilder/build-a-component/)).\n- **It integrates** with [Flow](https://www.totaljs.com/flow/) and with [OpenPlatform](/en/openplatform/what-is-openplatform/), and it supports localization.\n\nContinue with [running the editor](/en/uibuilder/run-the-designer/), the [JSON format](/en/uibuilder/the-ui-json-format/) and [rendering a design in your page](/en/uibuilder/render-an-app-in-your-page/). The official documentation is at [docs.totaljs.com/uibuilder](https://docs.totaljs.com/uibuilder/)."}],"knowledge":{"content_sha256":"329c5a47f7d4881abacafeecf422ecddaa9d5e75234b09184f4d79b0f5af9be7","source_sha256":"819a8d52756f5171c6f0ae5a07c9696deba10d6554b3ebbd8bee7daf8e324115","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[],"review":"not-recorded","symbols":["CRM","ERP","HMI","HTML","JSON","MIT","Total.js"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"uibuilder/run-the-designer","section":"uibuilder","kind":"recipe","framework":"UI Builder","framework_version":"uibuilder@1","language":"en","url":"https://forge.totaljs.es/en/uibuilder/run-the-designer/","markdown_url":"https://forge.totaljs.es/en/uibuilder/run-the-designer.md","tags":["uibuilder","install","components"],"license":"CC-BY-4.0","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"Use or run the UI Builder editor, and bring your own components","description":"Open the cloud editor or run it yourself with Node.js, what the project contains and how to load your own list of components with the components parameter.","messages":[{"role":"user","content":"Use or run the UI Builder editor, and bring your own components\n\nOpen the cloud editor or run it yourself with Node.js, what the project contains and how to load your own list of components with the components parameter."},{"role":"assistant","content":"## The cloud editor\n\nThe quickest way is the cloud version of the editor, [uibuilder.totaljs.com](https://uibuilder.totaljs.com).\n\n## Run it yourself\n\nThe editor is a small Total.js 5 application that serves static files. The README gives three steps:\n\n```bash\nnpm install\nnode index.js\n```\n\nDownload the source code first, from [github.com/totaljs/uibuilder](https://github.com/totaljs/uibuilder). The application has one route that serves its files (`ROUTE('GET /*')`) and the `config` file sets the name and the CDN of the interface (`cdn : //cdn.componentator.com`). The editor needs no database.\n\nWhat the repository contains:\n\n| Folder or file | What it holds |\n|---|---|\n| `public/` | the editor: `ui.js` and `func.js`, the forms of the editor (`forms/*.html`: settings, components, schema, import, code, paths...) and the images of the templates |\n| `public/templates/` | the starting designs: `app.json`, `dashboard.json`, `flowboard.json`, `part.json` and `db.json` |\n| `public/templates.json` | the list of templates that the editor offers (a mobile app and an HMI panel exist but are disabled) |\n| `public/template.txt` | the skeleton of a new component ([build a component](/en/uibuilder/build-a-component/)) |\n| `public/render.txt` | a page that renders a design with jComponent 20 |\n| `cdn/uibuilder.min@1.js` | the runtime that renders a design (the `UIBuilder` object of the page) |\n\n## Use your own list of components\n\nThe editor reads its components from a list. You can point it to your own list by adding the parameter `components` to the address, with the address of a JSON file as its value:\n\n```text\nhttps://uibuilder.totaljs.com?components=URL_TO_YOUR_JSON_DB\n```\n\nThis is how a company ships its own component library without touching the editor.\n\n## Where the pieces come from\n\nThe design saved by the editor carries, in its `components` field, the address of the library that holds each component, so a design can be rendered later by anyone who can reach that address ([the JSON format](/en/uibuilder/the-ui-json-format/))."}],"knowledge":{"content_sha256":"ddf1253f6ea8cd393a6083546cbd69e30aaac72f775c2905b253c4328101aa8a","source_sha256":"949e96b69b4eb5f0aa1a1db23b76823cf9eef1d3b408d6d92424a62d7bf73313","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[],"review":"not-recorded","symbols":["CDN","GET","HMI","JSON","README","ROUTE","Total.js","UIB","URL_TO_YOUR_JSON_DB"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"uibuilder/the-ui-json-format","section":"uibuilder","kind":"recipe","framework":"UI Builder","framework_version":"uibuilder@1","language":"en","url":"https://forge.totaljs.es/en/uibuilder/the-ui-json-format/","markdown_url":"https://forge.totaljs.es/en/uibuilder/the-ui-json-format.md","tags":["uibuilder","json","schema"],"license":"CC-BY-4.0","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"The JSON format of a UI Builder design","description":"The fields of the JSON file that UI Builder saves: metadata, the CSS with the CLASS placeholder, the component tree, inputs, outputs and arguments, with the starting templates as examples.","messages":[{"role":"user","content":"The JSON format of a UI Builder design\n\nThe fields of the JSON file that UI Builder saves: metadata, the CSS with the CLASS placeholder, the component tree, inputs, outputs and arguments, with the starting templates as examples."},{"role":"assistant","content":"A UI Builder design is one JSON object. The starting templates of the repository (`public/templates/*.json`) show its fields.\n\n| Field | Meaning |\n|---|---|\n| `name`, `description`, `icon`, `version` | the metadata of the design (the icon is a class of the icon font, like `ti ti-totaljs`) |\n| `type` | the kind of design: `app`, `dashboard`, `flowboard` or `part` |\n| `css` | the style of the design. The placeholder `CLASS` is replaced by a unique class of the design, so its rules never leak (for example `.CLASS:root{--radius:5px}`) |\n| `csspublish` | the style of a `part` that is published for others to use |\n| `editor` | options of the editor: `download` (a flag) and the text fields `paths`, `codes` and `upload` (the templates start with them empty; see the official documentation for their use) |\n| `components` | where the components come from: an object with `cdn`, the address of the library (`https://cdn.componentator.com/uibuilder/app.json` in the app template), and in a `part` an object that maps the id of each component to the address of its editor form |\n| `children` | the tree of components of the design |\n| `inputs`, `outputs` | the connections of the design with the outside; the templates start with empty lists |\n| `args` | arguments of the design |\n\n## The tree of components\n\n`children` is a list of nodes. Each node has:\n\n```json\n{\n\t\"id\": \"root\",\n\t\"component\": \"container\",\n\t\"config\": { \"name\": \"Container\" },\n\t\"children\": []\n}\n```\n\n- `id`: the identifier of the instance inside the design.\n- `component`: the id of the component that renders it (the starting designs begin with a `container` called `root`).\n- `config`: the configuration that the settings form of the component edits.\n- `children`: the nodes inside it, with the same shape.\n\nA component can follow another one: when its `config.path` starts with `@`, it reads and writes the value of the component whose id comes after the `@` ([build a component](/en/uibuilder/build-a-component/)).\n\n## Why this matters\n\nThe design is plain data: you can store it in a database, compare two versions, generate it from your own tool, or let your application choose a design per customer. To show one, read [render a design in your page](/en/uibuilder/render-an-app-in-your-page/)."}],"knowledge":{"content_sha256":"e85f068a6d27504ab70f8fc202d298d94731a0f11e40603e0fb11bfcf4fe86e1","source_sha256":"7bb1e7442617a8b57d9557b02f99f6aee7013fec7bee80c3ea730668de6b508a","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[],"review":"not-recorded","symbols":["CLASS","JSON"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"uibuilder/build-a-component","section":"uibuilder","kind":"recipe","framework":"UI Builder","framework_version":"uibuilder@1","language":"en","url":"https://forge.totaljs.es/en/uibuilder/build-a-component/","markdown_url":"https://forge.totaljs.es/en/uibuilder/build-a-component.md","tags":["uibuilder","components","extensions"],"license":"CC-BY-4.0","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"Build a component for UI Builder","description":"The format of a UI Builder component: one HTML file with its style, a script that exports its metadata and a make function, a settings form and a readme, plus the flags, methods and events of an instance.","messages":[{"role":"user","content":"Build a component for UI Builder\n\nThe format of a UI Builder component: one HTML file with its style, a script that exports its metadata and a make function, a settings form and a readme, plus the flags, methods and events of an instance."},{"role":"assistant","content":"A UI Builder component is **one HTML file** with up to four parts. The skeleton that the editor offers for a new component (`public/template.txt`) is a clock:\n\n```html\n<style>\n\t.CLASS { font-size: 14px; }\n</style>\n\n<script>\n\texports.id = '';\n\texports.name = 'Clock';\n\texports.group = 'Controls';\n\texports.author = 'Total.js';\n\texports.icon = 'ti ti-clock';\n\texports.config = { bold: false, flash: true, seconds: true };\n\texports.version = '1';\n\texports.follow = true;\n\n\texports.make = function(instance, config, element, cls, iseditor) {\n\t\t// build the component inside element\n\t};\n</script>\n\n<readme>\nThe component renders the current time and the model contains a `Date` object.\n</readme>\n\n<settings>\n\t<div class=\"padding\">\n\t\t<ui-component name=\"input\" path=\"?.bold\" config=\"type:checkbox\">Apply bold font</ui-component>\n\t</div>\n</settings>\n```\n\n| Part | What it does |\n|---|---|\n| `<style>` | the CSS of the component; `CLASS` is replaced by a class that belongs to the component |\n| `<script>` | exports the metadata (`id`, `name`, `group`, `author`, `icon`, `version`, `config` with the defaults) and the function `make` |\n| `<readme>` | the help text shown in the editor (Markdown) |\n| `<settings>` | the form that edits `config`: jComponent inputs whose `path` starts with `?.` followed by the key of the config |\n\n## The function make\n\n`make(instance, config, element, cls, iseditor)` runs when the component is created:\n\n- `instance` is the instance of the component (`set`, `on`, `emit`, `state`...).\n- `config` is its configuration and `element` the jQuery element to fill.\n- `cls` is the class that replaced `CLASS` and `iseditor` tells whether it is drawn inside the editor or in a rendered design (so, for example, a timer does not need to run while you design).\n\nInside it you listen to events (`instance.on('configure', ...)` when the settings change and `instance.on('destroy', ...)` to clean timers), and write the value with `instance.set('value', date)`.\n\n## Metadata, flags, methods and events\n\nFrom the README of the editor:\n\n- **Metadata:** `exports.follow = true` shows the component in the settings form *Path for reading/writing* (for the designer only) and `exports.hidden = true` hides it from the list of components.\n- **Flags of an instance:** `instance.state.bind = true` binds the component automatically inside a form, for the ones that read and write data; `instance.state.notify = true` emits only the `notify` event when the value changes, instead of setting the value of the component directly (the Counter component is the example).\n- **Method:** `instance.datasource(CLID_OR_@INSTANCEID__OR__#VIEWID, function(value) {})` reads a source of data and gives an array of `[id, name]`.\n- **Event:** `instance.on('refresh', function(meta) {})` fires when some objects changed: `meta.type` is `remove` or `configure`, with `meta.items` or `meta.item`.\n- **Properties of the editor** that a component can read: `DEF.cl.datasource` (code lists and components with `follow`), `DEF.cl.paths`, `DEF.cl.list` (all the instances), `DEF.cl.inputs`, `DEF.cl.outputs`, `DEF.cl.views` and `DEF.cl.apps` (sibling designs).\n- **Following:** a `config.path` that starts with `@` means the component follows another one.\n\nPublish the file in your own list of components ([run the editor](/en/uibuilder/run-the-designer/)) and your team can use it from the editor."}],"knowledge":{"content_sha256":"bd9a9a5904c5130dd20eb775e95489977b91a56df92a270acb0fba296ed79009","source_sha256":"ab1702bc62f098dec4f4adadc781842cc789387f8cf7ceb3bee6f69195ea7da0","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[],"review":"not-recorded","symbols":["CLASS","CLID_OR_","CSS","DEF","HTML","INSTANCEID__OR__","README","Total.js","VIEWID"],"language":"en"},"files":[],"test":null,"test_kind":null}
{"id":"uibuilder/render-an-app-in-your-page","section":"uibuilder","kind":"recipe","framework":"UI Builder","framework_version":"uibuilder@1","language":"en","url":"https://forge.totaljs.es/en/uibuilder/render-an-app-in-your-page/","markdown_url":"https://forge.totaljs.es/en/uibuilder/render-an-app-in-your-page.md","tags":["uibuilder","render","web component"],"license":"CC-BY-4.0","verified":false,"verification":"documentation; no executable test","verification_status":"documentation","verification_evidence":null,"title":"Render a UI Builder design in your own page","description":"Show a saved design anywhere with the j-uibuilder web component and the address of its JSON file, with jComponent 20 as the only requirement.","messages":[{"role":"user","content":"Render a UI Builder design in your own page\n\nShow a saved design anywhere with the j-uibuilder web component and the address of its JSON file, with jComponent 20 as the only requirement."},{"role":"assistant","content":"A design is a JSON file ([the format](/en/uibuilder/the-ui-json-format/)), and rendering it is one component. The README of the editor shows it with the web component [j-uibuilder](https://componentator.com/components/j-uibuilder/):\n\n```html\n<ui-component name=\"uibuilder\" config=\"url:https://uibuilder.totaljs.com/render.json\"></ui-component>\n```\n\n`config=\"url:...\"` is the address of the JSON file of the design. The page needs jComponent 20 and the component `uibuilder`; the editor itself serves a ready page for this (`public/render.txt`) that loads jComponent from the CDN:\n\n```html\n<link href=\"https://cdn.componentator.com/spa.min@20.css\" rel=\"stylesheet\" type=\"text/css\" />\n<script src=\"https://cdn.componentator.com/spa.min@20.js\"></script>\n```\n\nThen the design appears where you put the tag: a form, a dashboard, a board of a flow. Because the design is data, your application can decide which file to show per user or per customer.\n\n## What the page of Forge already gives you\n\n- The catalogue of components of Forge has the page [uibuilder](/en/components/uibuilder/), generated from the repository of the components, with its readme and example.\n- In the editor, the component list that a design uses is the one in its `components` field; a design can only be rendered where those components can be reached.\n\n## Good to know\n\n- You do not need the editor to render: any page with jComponent 20 can do it.\n- The runtime that renders a design is `cdn/uibuilder.min@1.js` in the repository of the editor.\n- UI Studio, listed among the app templates of Total.js, builds on the same idea ([Total.js apps](https://www.totaljs.com/apps/))."}],"knowledge":{"content_sha256":"dd8955b5647235d035f7e4e657dfb7fb1aa72fb4a5f80311b92f060b7daec88e","source_sha256":"516b8658639947da0a30707b3b126d9dff558aee753b648ead3d7edea5a05703","snapshot":"c0700e321579725c5cf4f5ace1ad7d2866d1b4f93c24a63c0b41b6b7998e976c","sources":[],"review":"not-recorded","symbols":["CDN","JSON","README","Total.js"],"language":"en"},"files":[],"test":null,"test_kind":null}
