Fetch, cache, and manage JSON schemas from the OpenAPI specification.
Funky.Schema provides centralized access to JSON schemas defined in the OpenAPI spec (/api.json). It eliminates schema duplication across templates by fetching schemas once, caching them, and providing utilities for resolution, cloning, and runtime enhancement.
File: public/assets/js/components/schema.js
Registered as Funky.Schema via the component registry.
Auto-initialized on app load in layouts/app.html.ep.
Fetch and cache the OpenAPI spec.
Parameters:
| Name | Type | Default | Description |
|---|---|---|---|
| url | string | '/api.json' | Optional custom URL for the spec |
Returns: Promise that resolves when spec is loaded
Example:
// Usually called automatically in app.html.ep
Funky.Schema.init();
// Custom URL
Funky.Schema.init('/custom/api-spec.json');Wait for schema to be fully loaded.
Returns: Promise that resolves with Funky.Schema when ready
Example:
Funky.Schema.ready().then(function() {
var schema = Funky.Schema.get('CreateClient');
// Use schema...
});
// Or with async/await
await Funky.Schema.ready();
var schema = Funky.Schema.get('CreateClient');Check if schemas are loaded.
Returns: boolean
Get a schema by name or path. Returns a reference - do not modify!
Parameters:
| Name | Type | Description |
|---|---|---|
| path | string | Schema name or path |
Path Formats Accepted:
'CreateClient'- Schema name only'components/schemas/CreateClient'- Path without hash'#/components/schemas/CreateClient'- Full $ref path
Returns: Schema object (reference) or null
Example:
// All equivalent:
var schema = Funky.Schema.get('CreateClient');
var schema = Funky.Schema.get('components/schemas/CreateClient');
var schema = Funky.Schema.get('#/components/schemas/CreateClient');Get a deep copy of a schema (safe to modify).
Parameters:
| Name | Type | Description |
|---|---|---|
| path | string | Schema name or path |
Returns: Deep-cloned schema object or null
Example:
var schema = Funky.Schema.clone('CreateClient');
schema.properties.custom_field = { type: 'string', title: 'Custom' };
// Original schema unchangedResolve all $ref values in a schema recursively.
Parameters:
| Name | Type | Description |
|---|---|---|
| schemaOrPath | string/Object | Schema name or schema object |
Returns: Fully resolved schema (cloned) with all $refs inlined
Example:
// Schema with $ref:
// { "allOf": [{ "$ref": "#/components/schemas/BaseEntity" }, { ... }] }
var resolved = Funky.Schema.resolve('CreateTrade');
// Returns schema with BaseEntity properties inlinedClone and enhance a schema with runtime data.
Parameters:
| Name | Type | Description |
|---|---|---|
| schemaOrPath | string/Object | Schema name or schema object |
| enhancements | Object | Properties to merge/add |
Enhancement Options:
| Property | Description |
|---|---|
properties |
Object with property enhancements (deep merged) |
required |
Override required array |
| Other keys | Merged at top level |
Returns: Enhanced schema copy
Example:
var clients = [{ id: 1, name: 'Acme' }, { id: 2, name: 'Beta' }];
var enhanced = Funky.Schema.enhance('CreateTrade', {
properties: {
client_id: {
enum: clients.map(function(c) { return c.id; }),
options: {
enum_titles: clients.map(function(c) { return c.name; })
}
}
},
required: ['client_id', 'quantity'] // Override required fields
});Convenience method: clone, resolve $refs, and enhance in one call.
Parameters:
| Name | Type | Description |
|---|---|---|
| path | string | Schema name or path |
| enhancements | Object | Optional enhancements |
Returns: Fully processed schema
Example:
// Get fully resolved and enhanced schema
var schema = Funky.Schema.getResolved('CreateTrade', {
properties: {
client_id: { enum: clientIds }
}
});Get all available schema names.
Returns: Array of schema name strings
Example:
var names = Funky.Schema.list();
// ['Client', 'CreateClient', 'Trade', 'CreateTrade', ...]Search for schemas matching a pattern.
Parameters:
| Name | Type | Description |
|---|---|---|
| pattern | string/RegExp | Pattern to match |
Returns: Array of matching schema names
Example:
Funky.Schema.search('Client'); // ['Client', 'CreateClient', 'UpdateClient']
Funky.Schema.search(/^Create/); // ['CreateClient', 'CreateTrade', ...]Get the raw OpenAPI specification object.
Returns: Full OpenAPI spec or null
Get API paths from the spec.
Parameters:
| Name | Type | Description |
|---|---|---|
| pathPattern | string | Optional regex pattern to filter |
Returns: Object with matching paths
Example:
var clientPaths = Funky.Schema.getPaths('/api/clients');Clear the resolved schema cache.
Re-fetch the OpenAPI spec.
Returns: Promise
// Wait for ready, then get schema
Funky.Schema.ready().then(function() {
var schema = Funky.Schema.get('CreateClient');
var editor = new JSONEditor(container, {
schema: schema
});
});// Using schemaPath - FormModal handles schema loading
Funky.FormModal.init({
modalId: 'clientModal',
entity: 'client',
entityLabel: 'Client',
schemaPath: 'CreateClient', // Uses Funky.Schema
apiUrl: '/api/clients',
onSave: function() { table.ajax.reload(); }
});
// Or with enhancement callback
Funky.FormModal.init({
modalId: 'tradeModal',
entity: 'trade',
entityLabel: 'Trade',
schemaPath: 'CreateTrade',
apiUrl: '/api/trades',
enhanceSchema: function(schema, data) {
return Funky.Schema.enhance(schema, {
properties: {
client_id: {
enum: activeClients.map(function(c) { return c.id; }),
options: { enum_titles: activeClients.map(function(c) { return c.name; }) }
}
}
});
}
});// Fetch options and enhance schema
Promise.all([
Funky.Schema.ready(),
fetch('/api/clients?active=true').then(function(r) { return r.json(); })
]).then(function(results) {
var clients = results[1];
var schema = Funky.Schema.enhance('CreateTrade', {
properties: {
client_id: {
type: 'integer',
title: 'Client',
enum: clients.map(function(c) { return c.id; }),
options: {
enum_titles: clients.map(function(c) { return c.name; })
}
}
}
});
// Use enhanced schema
});// Original schema in api.json:
// {
// "CreateTrade": {
// "allOf": [
// { "$ref": "#/components/schemas/TradeBase" },
// { "properties": { "special_field": { ... } } }
// ]
// }
// }
// Resolve flattens $refs
var resolved = Funky.Schema.resolve('CreateTrade');
// Now has all TradeBase properties directly embedded// Schema duplicated in every template file
var schema = {
type: 'object',
title: 'New Client',
required: ['name', 'code'],
properties: {
name: { type: 'string', title: 'Name', minLength: 1 },
code: { type: 'string', title: 'Code', pattern: '^[A-Z0-9]+$' },
is_active: { type: 'boolean', title: 'Active', default: true },
parent_id: { type: 'integer', title: 'Parent' }
}
};
// 20+ lines repeated in every template// Schema from OpenAPI spec
var schema = Funky.Schema.enhance('CreateClient', {
properties: {
parent_id: {
enum: parentClients.map(function(c) { return c.id; }),
options: { enum_titles: parentClients.map(function(c) { return c.name; }) }
}
}
});
// 5 lines, DRY, consistent with API validationSchema is initialized early in app load:
<!-- In app.html.ep after registry.js -->
<script src="/assets/js/components/schema.js"></script>
<script>
if (Funky.Schema) { Funky.Schema.init(); }
</script>By the time page scripts run, schemas are typically loaded. Use Schema.ready() for guaranteed availability:
$(document).ready(function() {
Funky.Schema.ready().then(function() {
// Safe to use Schema.get(), etc.
});
});Funky.Schema.init()
.then(function() {
console.log('Schemas loaded');
})
.catch(function(err) {
console.error('Failed to load schemas:', err);
// Show fallback UI or retry
});Funky.register(registry.js)/api.json(OpenAPI specification)
- form-modal.md - Form modal with schemaPath support
- crud.md - CRUD controller
- API Documentation - OpenAPI spec viewer