json-object-editor 0.10.622 → 0.10.623

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/readme.md CHANGED
@@ -1,663 +1,663 @@
1
- <img src="http://joe.craydent.com/JsonObjectEditor/img/svgs/joe_banner_o.svg"/>
2
-
3
- # Json Object Editor
4
- JOE is software that allows you to manage data models via JSON objects. There are two flavors, the client-side version and nodejs server platform.
5
-
6
-
7
-
8
- ## Architecture & Mental Model (Server)
9
-
10
- - Global JOE
11
- - Created in `server/init.js`; exposes subsystems like `JOE.Utils`, `JOE.Schemas`, `JOE.Storage`, `JOE.Mongo`/`JOE.MySQL`, `JOE.Cache`, `JOE.Apps`, `JOE.Server` (Express), `JOE.Sites`, `JOE.io` (Socket), and `JOE.auth`.
12
- - Init pattern
13
- - Modules can optionally export `init()`; `init.js` loads/watches modules and calls `init()` after `JOE.Server` is ready. This enables hot-reload and keeps `Server.js` thin.
14
- - Schemas & events
15
- - `JOE.Schemas` loads from `server/schemas/` and app schema dir into `JOE.Schemas.schema` with names in `JOE.Schemas.schemaList`. Raw copies live in `JOE.Schemas.raw_schemas` for event hooks.
16
- - `JOE.Storage.save()` triggers schema events (`create`, `save`, `status`, `delete`) via `JOE.Schemas.events()` and writes history documents to `_history`.
17
- - Storage & cache
18
- - `JOE.Storage.load(collection, query, cb)` chooses backend per schema `storage.type` (`mongo`, `mysql`, `file`, `api`), with Mongo/file fallback.
19
- - `JOE.Storage.save(item, collection, cb, { user, history })` emits `item_updated` to sockets, records history, and fires events.
20
- - `JOE.Cache.update(cb, collections)` populates `JOE.Data`, flattens a `list`, and builds `lookup`. Use `JOE.Cache.findByID(collection, idOrCsv)` for fast lookups; `JOE.Cache.search(query)` for in-memory filtering.
21
- - Auth
22
- - `JOE.auth` middleware checks cookie token or Basic Auth; many API routes (and `/mcp`) are protected.
23
- - Shorthand `$J`
24
- - Server and client convenience: `$J.get(_id)`, `$J.search(query)`, `$J.schema(name)`.
25
- - On server, provided by `server/modules/UniversalShorthand.js` and assigned to `global.$J` in `init.js`.
26
- - MCP overview
27
- - Manifest: `/.well-known/mcp/manifest.json`; JSON-RPC: `POST /mcp` (auth-protected). Tools map to real JOE APIs (`Schemas`, `Storage`, `Cache`) and sanitize sensitive fields.
28
-
29
- ## MCP routes and local testing
30
-
31
- - Endpoints
32
- - Manifest (public): `GET /.well-known/mcp/manifest.json`
33
- - JSON-RPC (auth): `POST /mcp`
34
- - Privacy (public): `/privacy` (uses Setting `PRIVACY_CONTACT` for contact email)
35
- - Terms (public): `/terms`
36
-
37
- - Auth
38
- - If users exist, `POST /mcp` requires cookie or Basic Auth (same as other APIs). If no users configured, it is effectively open.
39
-
40
- - Test page
41
- - JOE ships a simple tester at `/_www/mcp-test.html` inside the package.
42
- - Access via JOE path: `http://localhost:<PORT>/JsonObjectEditor/_www/mcp-test.html`
43
- - If your host app serves its own `_www`, the tester can also be available at the root (fallback) if running with the updated server that mounts JOE’s `_www` as a secondary static directory. Then: `http://localhost:<PORT>/mcp-test.html`
44
-
45
- - Tools
46
- - `listSchemas(name?)`, `getSchema(name)`
47
- - `getObject(_id, schema?)` (supports optional `flatten` and `depth`)
48
- - `search` (exact): unified tool for cache and storage
49
- - Params: `{ schema?, query?, ids?, source?: 'cache'|'storage', limit?, flatten?, depth? }`
50
- - Defaults to cache across all collections; add `schema` to filter; set `source:"storage"` to query a specific schema in the DB. Use `fuzzySearch` for typo-tolerant free text.
51
- - `fuzzySearch` (typo-tolerant free text across weighted fields)
52
- - Params: `{ schema?, q, filters?, fields?, threshold?, limit?, offset?, highlight?, minQueryLength? }`
53
- - Defaults: `fields` resolved from schema `searchables` (plural) if present; otherwise weights `name:0.6, info:0.3, description:0.1`. `threshold:0.5`, `limit:50`, `minQueryLength:2`.
54
- - Returns: `{ items, count }`. Each item may include `_score` (0..1) and `_matches` when `highlight` is true.
55
- - `saveObject({ object })`
56
- - `saveObjects({ objects, stopOnError?, concurrency? })`
57
- - Batch save with per-item history/events. Defaults: `stopOnError=false`, `concurrency=5`.
58
- - Each object must include `itemtype`; `_id` and `joeUpdated` are set when omitted.
59
- - Return shape: `{ results, errors, saved, failed }`.
60
-
61
- - Quick tests (PowerShell)
62
- - Prepare headers if using Basic Auth:
63
- ```powershell
64
- $pair = "user:pass"; $b64 = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes($pair))
65
- $h = @{ Authorization = "Basic $b64"; "Content-Type" = "application/json" }
66
- $base = "http://localhost:<PORT>"
67
- ```
68
- - Manifest:
69
- ```powershell
70
- Invoke-RestMethod "$base/.well-known/mcp/manifest.json"
71
- ```
72
- - listSchemas:
73
- ```powershell
74
- $body = @{ jsonrpc="2.0"; id="1"; method="listSchemas"; params=@{} } | ConvertTo-Json -Depth 6
75
- Invoke-RestMethod -Method Post -Uri "$base/mcp" -Headers $h -Body $body
76
- ```
77
- - getSchema:
78
- ```powershell
79
- $body = @{ jsonrpc="2.0"; id="2"; method="getSchema"; params=@{ name="<schemaName>" } } | ConvertTo-Json -Depth 6
80
- Invoke-RestMethod -Method Post -Uri "$base/mcp" -Headers $h -Body $body
81
- ```
82
- - search (cache default):
83
- ```powershell
84
- $body = @{ jsonrpc="2.0"; id="3"; method="search"; params=@{ query=@{ itemtype="<schemaName>" }; limit=10 } } | ConvertTo-Json -Depth 10
85
- Invoke-RestMethod -Method Post -Uri "$base/mcp" -Headers $h -Body $body
86
- ```
87
- - fuzzySearch (cache):
88
- ```powershell
89
- $body = @{ jsonrpc="2.0"; id="6"; method="fuzzySearch"; params=@{ schema="<schemaName>"; q="st paal"; threshold=0.35; limit=10 } } | ConvertTo-Json -Depth 10
90
- Invoke-RestMethod -Method Post -Uri "$base/mcp" -Headers $h -Body $body
91
- ```
92
- - search (storage):
93
- ```powershell
94
- $body = @{ jsonrpc="2.0"; id="4"; method="search"; params=@{ schema="<schemaName>"; source="storage"; query=@{ }; limit=10 } } | ConvertTo-Json -Depth 10
95
- Invoke-RestMethod -Method Post -Uri "$base/mcp" -Headers $h -Body $body
96
- ```
97
- - search (ids + flatten):
98
- ```powershell
99
- $body = @{ jsonrpc="2.0"; id="5"; method="search"; params=@{ schema="<schemaName>"; ids=@("<id1>","<id2>"); flatten=$true; depth=2 } } | ConvertTo-Json -Depth 10
100
- Invoke-RestMethod -Method Post -Uri "$base/mcp" -Headers $h -Body $body
101
- ```
102
- - saveObject:
103
- ```powershell
104
- $object = @{ itemtype="<schemaName>"; name="Test via MCP" }
105
- $body = @{ jsonrpc="2.0"; id="4"; method="saveObject"; params=@{ object=$object } } | ConvertTo-Json -Depth 10
106
- Invoke-RestMethod -Method Post -Uri "$base/mcp" -Headers $h -Body $body
107
- ```
108
- - saveObjects (batch):
109
- ```powershell
110
- $objs = @(
111
- @{ itemtype="<schemaName>"; name="Batch A" },
112
- @{ itemtype="<schemaName>"; name="Batch B" }
113
- )
114
- $body = @{ jsonrpc="2.0"; id="7"; method="saveObjects"; params=@{ objects=$objs; stopOnError=$false; concurrency=5 } } | ConvertTo-Json -Depth 10
115
- Invoke-RestMethod -Method Post -Uri "$base/mcp" -Headers $h -Body $body
116
- ```
117
-
118
- - Quick tests (curl)
119
- - Manifest:
120
- ```bash
121
- curl -s http://localhost:<PORT>/.well-known/mcp/manifest.json | jq
122
- ```
123
- - listSchemas:
124
- - search (cache):
125
- ```bash
126
- curl -s -X POST http://localhost:<PORT>/mcp \
127
- -H 'Content-Type: application/json' \
128
- -d '{"jsonrpc":"2.0","id":"3","method":"search","params":{"query":{"itemtype":"<schemaName>"},"limit":10}}' | jq
129
- ```
130
- - fuzzySearch:
131
- ```bash
132
- curl -s -X POST http://localhost:<PORT>/mcp \
133
- -H 'Content-Type: application/json' \
134
- -d '{"jsonrpc":"2.0","id":"6","method":"fuzzySearch","params":{"schema":"<schemaName>","q":"st paal","threshold":0.35,"limit":10}}' | jq
135
- ```
136
- - search (storage):
137
- ```bash
138
- curl -s -X POST http://localhost:<PORT>/mcp \
139
- -H 'Content-Type: application/json' \
140
- -d '{"jsonrpc":"2.0","id":"4","method":"search","params":{"schema":"<schemaName>","source":"storage","query":{},"limit":10}}' | jq
141
- ```
142
- ```bash
143
- curl -s -X POST http://localhost:<PORT>/mcp \
144
- -H 'Content-Type: application/json' \
145
- -d '{"jsonrpc":"2.0","id":"1","method":"listSchemas","params":{}}' | jq
146
- ```
147
- - saveObjects (batch):
148
- ```bash
149
- curl -s -X POST http://localhost:<PORT>/mcp \
150
- -H 'Content-Type: application/json' \
151
- -d '{"jsonrpc":"2.0","id":"7","method":"saveObjects","params":{"objects":[{"itemtype":"<schemaName>","name":"Batch A"}],"stopOnError":false,"concurrency":5}}' | jq
152
- ```
153
-
154
- - Troubleshooting
155
- - If you see a payload like `{ originalURL: "/...", site: "no site found" }`, the request hit the Sites catch-all. Ensure MCP routes are initialized before Sites (handled by default in `server/init.js` via `MCP.init()`), and use the correct URL: `/.well-known/mcp/manifest.json` or `/mcp`.
156
- - To update contact email on /privacy and /terms, set Setting `PRIVACY_CONTACT`.
157
-
158
- ## File uploads (S3)
159
- - Uploader field options:
160
- - `allowmultiple: true|false` — allow selecting multiple files.
161
- - `url_field: 'image_url'` — on success, sets this property to the remote URL and rerenders that field.
162
- - `ACL: 'public-read'` — optional per-field ACL. When omitted, server currently defaults to `public-read` (temporary during migration).
163
- - Flow:
164
- - Client posts `{ Key, base64, contentType, ACL? }` to `/API/plugin/awsConnect`.
165
- - Server uploads with AWS SDK v3 and returns `{ url, Key, bucket, etag }` (HTTP 200).
166
- - Client uses `response.url`; if `url_field` is set, it assigns and rerenders that field.
167
- - Errors:
168
- - If bucket or region config is missing, server returns 400 with a clear message.
169
- - If the bucket has ACLs disabled, server returns 400: “Bucket has ACLs disabled… remove ACL or switch to presigned/proxy access.”
170
-
171
- ## SERVER/PLATFORM mode
172
- check port 2099
173
- /JOE/
174
- /JsonObjectEditor/docs.html
175
- *Should auto-open on start
176
-
177
- Json Object Editor (Universal-esque software)
178
- (requires connection to a mongo server for full functionality)
179
-
180
-
181
-
182
- ### JOE server instantiation (add to entry point js file)
183
- var joe = require('json-object-editor');
184
- or here's a custom example
185
- var joe = require('json-object-editor')({
186
- name:'name_your_joe' (JOE),
187
- joedb:'local_custom_database_name' // {{mongoURL}}' if using mongolab or remote mongodb,
188
- port:'4099', (2099)
189
- socketPort:'4098', (2098)
190
- sitesPort:'4100' (2100),
191
- clusters:1,
192
- hostname:'server name if not localhost'(localhost)
193
- });
194
-
195
- ## $J (universal) Shorthand JOE
196
- $J to access helper funtions on client and server. (callbacks are optional)
197
- $J.get(itemId,[callback])
198
- $J.schema(schemaname,[callback])
199
-
200
- # Client-Side (front end only)
201
-
202
- ## js client only instantiation
203
- var specs = {
204
- fields:{
205
- species:{label:'Species',type:'select', values:['cat','dog','rat','thing'], onchange:adjustSchema},
206
- gender:{type:'select', values:['male','female']},
207
- legs:{label:'# of Legs',type:'int', onblur:logit},
208
- weight:{label:' Weight (lbs)',type:'number', onblur:logit},
209
- name:{label:' pet Name', onkeyup:logValue},
210
- //id:{label:'ID',type:'text', locked:true},
211
- id:{label:'ID',type:'guid'},
212
-
213
- //example of select that takes function (function is passed item)
214
- animalLink:{label:'Link to other animal',type:'select', values:getAnimals},
215
- hiddenizer:{hidden:true}
216
- },
217
- schemas:{
218
- animal:animalschema,
219
- thing:thingschema
220
- },
221
- container:string ('body'),
222
- compact:false,
223
- useBackButton:true,
224
- autosave:2000,
225
- listSubMenu:{filters:{}},
226
- useHashlink:true,
227
- title:'${itemtype} | ${display}'
228
- }
229
- var JOE = new JsonObjectEditor(specs);
230
- JOE.init();
231
-
232
- ##JOE CONFIG
233
- ##specs
234
- - useBackButton:[false] if true, back button moves through joe panels when joe has history to go to (is open).
235
- - useHashlink:[false], true or a template for hashlinks.
236
- default template is '${schema_name}_${_id}'
237
- default server tempalte is '${schema_name}/${_id}'
238
- ##SCHEMA CONFIG
239
- ###fields
240
- Properties for all Fields
241
-
242
- - `label / display`: what the field should display as
243
- *If the field type is boolean, label controls checkbox/boolean label
244
- - `value`: default value if not one in object
245
- - `default`: default value for field || function(object)
246
- - `type`: what type of field should JOE show
247
- - `hidden`: boolean / function, value will be added (but unsees by user)
248
- - `locked`: boolean
249
- - `condition`: boolean
250
- - `width`: used for layout control.
251
- - can use pixels or percentages (as string)
252
- - `comment`: a commentthat shows up at the beginning of the field
253
- - `tooltip`: hover/clickable tooltip that shows up next to name
254
-
255
- **field types:**
256
-
257
- - `rendering`: for css html and js
258
- - `text`: default single line text.
259
- - autocomplete: boolean // obj of specs (template, idprop)
260
- - values:array of possibilities
261
- -maxlength:string
262
- - `int`: integer field
263
- - `number`: number (float) field
264
- - `select`: select list.
265
- - multiple(bool)
266
- - values(array of objects, [{value:"",name/display:""]), can be a function
267
- - disabled:boolean(func acceptable)
268
- - idprop: string of prop name
269
- - `geo`: shows a map
270
- - *takes a string array "[lat,lon]"*
271
- - center:[lat,lon], center of map
272
- - zoom: zoom level (higher zooms in more)
273
- - returns "[lat,lon]"
274
- -`image` : shows an image and HxW as th image url is typed in.
275
- - `multisorter` : allows arrays of objects to be selected and sorted in right bin.
276
- - values(array of objects, [{value:"",name/display:""]), can be a function
277
- - `content` : show content on in the editor
278
- - run: function to be run(current_object,field_properties)
279
- - template: html template for fillTemplate(template,current_object);
280
- - `objectlist` : a table of objects with editable properties
281
- - properties: array of objects|strings for the object property names
282
- -name: value in object
283
- -display: header in objectList
284
- - max: integer, number or items that can be added. use zero for infinite.
285
- - hideHeadings: don't show table headings
286
- - `objectReference` : a list of object ids
287
- - template
288
- - autocomplete_template
289
- - idprop
290
- - values
291
- - max(0 unlimited)
292
- - sortable(true)
293
- - `code` :
294
- - language
295
-
296
- - `boolean`:
297
- - label:controls checkbox label
298
- - `preview` :
299
- -content: string or function(current joe object) to replace everything on page (template).
300
- -bodycontent: same as content, only replaces body content.
301
- -url: preview page if not the default one.
302
- - encoded: boolean, if pre uriencoded
303
- **labels:**
304
-
305
- - pass an object instead of a string to the fields array.
306
-
307
-
308
- {label:'Name of the following properties section'}
309
-
310
- ##page sections
311
- {section_start: 'SectionName',
312
- section_label:'Section Name with Labels',
313
- condition:function(item){
314
- return item.show;}
315
- },
316
- {section_end: 'CreativeBrief'}
317
-
318
- - pass an object instead of a string to the fields array. these show up on the details view as anchors.
319
-
320
-
321
- - Object Properties
322
- - `section_start`: name/id of section
323
- - `'section_label`:use instead of section_start for display name
324
- - `section_end`: name/id of section(str)
325
- - template: html template for fillTemplate(template,current_object);
326
- - Note: for fields, `condition` removes the field from the DOM while `hidden` only toggles visibility; sections use `condition` (and sidebar sections use `hidden`) differently than fields.
327
-
328
- ##page sidebar
329
- {sidebar_start: 'SectionName',
330
- sidebar_label:'Section Name with Labels',
331
- condition:function(item){
332
- return item.show;}
333
- },
334
- {sidebar_end: 'CreativeBrief'}
335
-
336
- - pass an object instead of a string to the fields array. these show up on the details view as anchors.
337
-
338
-
339
- - Object Properties
340
- - `sidebar_start`: name/id of sidebar
341
- - `sidebar_label`:use instead of sidebar_start for display name
342
- - `sidebar_end`: name/id of sidebar(str)
343
- - template: html template for fillTemplate(template,current_object);
344
-
345
- ###defaultProfile
346
- overwrites the default profile
347
-
348
- #schemas
349
-
350
- a list of schema objects that can configure the editor fields, these can be given properties that are delegated to all the corresponding fields.
351
-
352
- var animalschema =
353
- {
354
- title:'Animal', *what shows as the panel header*
355
- fields:['id','name','legs','species','weight','color','gender','animalLink'], *list of visible fields*
356
- _listID:'id', *the id for finding the object*
357
- _listTitle:'${name} ${species}', *how to display items in the list*
358
- menu:[array of menu buttons],
359
- listMenuTitle: (string) template forjoe window title in list view,
360
- listmenu:[array of menu buttons] (multi-edit and select all always show),
361
- /*callback:function(obj){
362
- alert(obj.name);
363
- },*/
364
- onblur:logit,
365
-
366
- hideNumbers:boolean *toggle list numbers*
367
- multipleCallback:function to be called after a multi-edit. passed list of edited items.
368
- onUpdate: callback for after update. passed single edited items.
369
- onMultipleUpdate:callback for after multi update.passed list of edited items.
370
- filters: array of objects
371
- checkChanges:(bool) whether or not to check for changes via interval and on leave
372
- }
373
- ##Table View
374
- - add tableView object to a schema;
375
- -cols = [strings||objects]
376
- -string is the name and value
377
- -display/header is the column title
378
- -property/name = object property
379
- ###Pre-formating
380
- you can preformat at the joe call or schema level. The data item will be affected by the passed function (which should return the preformated item).
381
-
382
- ##menu##
383
- an array of menu buttons
384
-
385
- //the default save button
386
- //this is the dom object,
387
- //use _joe.current.object for current object
388
- condition:function(field,object) to call
389
- self = Joe object
390
- var __saveBtn__ = {name:'save',label:'Save', action:'_joe.updateObject(this);', css:'joe-save-button'};
391
-
392
- ##itemMenu##
393
- as array of buttons for each item in list views
394
- - name
395
- - action (action string)
396
- - url (instead of js action)
397
- - condition
398
-
399
- ##itemExpander##
400
- template or run for content to be shown under the main list item block.
401
-
402
-
403
- ###Addition properties
404
- **Changing the schema on the fly?**
405
-
406
- _joe.resetSchema(new schema name);
407
-
408
-
409
-
410
- **css (included) options**
411
-
412
- - joe-left-button
413
- - joe-right-button
414
-
415
- ##FIELDS
416
-
417
- {extend:'name',specs:{display:'Request Title'}},//extends the field 'name' with the specs provided.
418
- ##usage
419
- ### a | adding a new object
420
-
421
- _joe.show({},{schema:'animal',callback:addAnimal);
422
- //or goJoe(object,specs)
423
-
424
- ...
425
- function addAnimal(obj){
426
- animals.push(obj);
427
- }
428
-
429
- ### b | viewing a list of objects
430
-
431
- goJoe([array of objects],specs:{schema,subsets,subset})
432
- goJoe.show(animals,{schema:'animal',subsets:[{name:'Two-Legged',filter:{legs:2}}]});
433
- //use the specs property subset to pre-select a subset by name
434
-
435
- **properties**
436
-
437
- - _listWindowTitle: the title of the window (can be passed in with the schema);
438
- - _listCount: added to the current object and can be used in the title.
439
- - _listTitle:'${name} ${species}', *how to display items in the list*
440
- - _icon: [str] template for a list item icon (standard min 50x50), 'http://www.icons.com/${itemname}', can be obj with width, height, url
441
- - listSubMenu:a function or object that represents the list submenu
442
- - stripeColor:string or function that returns valid css color descriptor.
443
- - bgColor:string or function that returns valid css color descriptor.
444
- - subsets: name:string, filter:object
445
- - subMenu:a function or object that represents the single item submenu
446
-
447
- - _listTemplate: html template that uses ${var} to write out the item properties for the list item.
448
- - standard css class `joe-panel-content-option`
449
-
450
- ### Helper shortcuts: subsets and stripes
451
-
452
- Use these helpers to quickly generate subset/filter options and add per-item stripe colors.
453
-
454
- ```javascript
455
- // In a schema definition
456
- {
457
- // 1) Subsets from statuses of the current schema (auto colors)
458
- subsets: function () {
459
- return _joe.Filter.Options.status({
460
- group: 'status', // optional grouping label
461
- collapsed: true, // start group collapsed
462
- none: true // include a "no status" option
463
- // color: 'stripecolor' // uncomment to render colored stripes in the menu
464
- });
465
- },
466
-
467
- // 2) Subsets for all distinct values of a property
468
- // Example: recommendation_domain on 'recommendation' items
469
- // (pass values to control order/allowlist)
470
- // subsets: () => _joe.Filter.Options.getDatasetPropertyValues(
471
- // 'recommendation', 'recommendation_domain', { group: 'domain', values: ['product','dietary','activity'] }
472
- // ),
473
-
474
- // 3) Subsets from another dataset (reference values)
475
- // subsets: () => _joe.Filter.Options.datasetProperty('user','members',{ group: 'assignees', collapsed: true }),
476
-
477
- // 4) Row stripe color by status (string or { color, title })
478
- stripeColor: function (item) {
479
- if (!item.status) return '';
480
- const s = _joe.getDataItem(item.status, 'status');
481
- return s && { color: s.color, title: s.name };
482
- }
483
- }
484
- ```
485
-
486
-
487
-
488
- ###c | Conditional select that changes the item schema
489
-
490
- fields:{
491
- species:{label:'Species',type:'select', values:['cat','dog','rat','thing'], onchange:adjustSchema},
492
- [field_id]:{
493
-
494
- +label : STR
495
- +type : STR
496
- value : STR (default value)
497
- +values : ARRAY/FUNC (for select)
498
-
499
- //modifiers
500
- +hidden:BOOL/STRING(name of field that toggles this) //don't show, but value is passed
501
- +locked:BOOL // show, but uneditable
502
- //events
503
- +onchange : FUNC
504
- +onblur : FUNC
505
- +onkeypress : FUNC
506
- +rerender : STRING // name of field to rerender
507
- }
508
- }
509
-
510
- function adjustSchema(dom){
511
- var species = $(dom).val();
512
- if(species == "thing"){
513
- JOE.resetSchema('thing')
514
- }
515
- else{
516
- JOE.resetSchema('animal')
517
-
518
- }
519
- }
520
-
521
- ###d | duplicating an item
522
-
523
- //duplicates the currently active object (being edited)
524
- _joe.duplicateObject(specs);
525
-
526
- **specs**
527
-
528
- - `deletes`:array of properties to clear for new item
529
- - note that you will need to delete guid/id fields or the id will be the same.
530
-
531
-
532
-
533
- ### e | exporting an object in pretty format json (or minified)
534
- JOE.exportJSON = function(object,objvarname,minify)
535
-
536
- ##Useful Functions
537
- _joe.reload(hideMessage,specs)
538
- - use specs.overwreite object to extend reloaded object.
539
-
540
- _joe.constructObjectFromFields()
541
-
542
- ## AI / Chat Integrations (OpenAI Responses + Assistants)
543
-
544
- JOE ships a few plugins and schemas to help you connect OpenAI assistants and the new Responses API to your data model.
545
-
546
- - **`chatgpt` plugin**
547
- - Central entry point for OpenAI calls (uses the `openai` Node SDK and the Responses API).
548
- - Looks up the OpenAI API key from the `setting` schema:
549
- - `OPENAI_API_KEY`: your secret key (can be a service‑account key, e.g. `sk-svcacct-...`).
550
- - Key methods:
551
- - **`autofill`** (`POST /API/plugin/chatgpt/autofill`):
552
- - Given `{ object_id, schema, fields, prompt?, assistant_id?, model? }`, calls `openai.responses.create(...)` and returns a JSON patch for the requested fields only.
553
- - Used by `_joe.Ai.populateField(...)` for schema‑driven “AI fill” buttons.
554
- - **`executeJOEAiPrompt`**:
555
- - Drives the `ai_prompt` → `ai_response` workflow using Responses API + optional helper functions.
556
- - **Widget‑focused methods**:
557
- - `widgetStart`, `widgetMessage`, `widgetHistory` for the embeddable AI widget (see below).
558
-
559
- - **`chatgpt-assistants` plugin (legacy Assistants API)**
560
- - Manages the older `beta.assistants` / `beta.threads` API for JOE’s rich in‑app chat (`<joe-ai-chatbox>`).
561
- - Still used by the `ai_conversation` flow; new integrations should prefer the Responses‑based `chatgpt` plugin.
562
-
563
- - **`chatgpt-tools` plugin**
564
- - Exposes JOE “AI tools” configured via the `ai_tool` schema.
565
- - Useful when you want OpenAI tools that call back into JOE (e.g. search, profile, etc.).
566
-
567
- ### AI Assistant schemas
568
-
569
- - **`ai_assistant` schema**
570
- - Represents a JOE‑side “assistant config” that can be synced to an OpenAI assistant.
571
- - Key fields:
572
- - `ai_model`: default model (e.g. `gpt-4.1-mini`, `gpt-4o`).
573
- - `instructions`: system prompt for the assistant.
574
- - `assistant_id`: OpenAI assistant ID (`asst_...`) set by the sync button.
575
- - `file_search_enabled`, `code_interpreter_enabled`: toggle built‑in tools.
576
- - `tools`: JSON definition array for custom function tools.
577
- - `assistant_thinking_text`: what the UI shows while the assistant is “thinking”.
578
- - UI highlights the **default assistant**:
579
- - The assistant whose `_id` matches the `DEFAULT_AI_ASSISTANT` setting is given a golden stripe in list view.
580
- - Default assistant resolution:
581
- - The client helper `Ai.getDefaultAssistant()` in `joe-ai.js` looks up `_joe.Data.setting.where({ name: 'DEFAULT_AI_ASSISTANT' })[0]` and caches it.
582
-
583
- - **`ai_widget_conversation` schema**
584
- - Lightweight conversation log used by the new embeddable widget (`<joe-ai-widget>`).
585
- - Fields:
586
- - `model`: model name used for the conversation (or inherited from `ai_assistant.ai_model`).
587
- - `assistant`: optional reference to the JOE `ai_assistant` used.
588
- - `assistant_id`: OpenAI assistant ID (`asst_...`) used by the Responses API.
589
- - `system`: effective system prompt.
590
- - `messages`: JSON array of `{ role, content, created_at }` for the widget chat.
591
- - `last_message_at`, `source`, `tags`, standard system fields.
592
- - This schema is what the widget endpoints in `chatgpt` read/write.
593
-
594
- ### Embeddable AI Widget (`<joe-ai-widget>`)
595
-
596
- JOE includes a small web component, defined in `js/joe-ai.js`, that can be dropped into any web page and talks to your JOE instance via the `chatgpt` plugin.
597
-
598
- - **Custom element**: `<joe-ai-widget>`
599
- - Attributes:
600
- - `endpoint` (optional): Base URL to the JOE instance. Defaults to same origin.
601
- Example: `endpoint="https://my-joe.example.com"`.
602
- - `ai_assistant_id` (optional): `_id` of an `ai_assistant` document in JOE. If present, the server will load this assistant and use its `ai_model`, `instructions`, and `assistant_id` when starting the conversation.
603
- - `assistant_id` (optional): Direct OpenAI assistant ID (`asst_...`). If provided, the widget uses Responses API with `assistant_id` instead of a bare `model`.
604
- - `model` (optional): Model name to use when no assistant is supplied (e.g. `gpt-4.1-mini`).
605
- - `title` (optional): Header title text shown in the widget panel.
606
- - `placeholder` (optional): Input placeholder text.
607
- - `source` (optional): Arbitrary string stored on the conversation (`ai_widget_conversation.source`).
608
- - `conversation_id` (optional): Existing `ai_widget_conversation._id` to resume a saved chat.
609
- - Behavior:
610
- - On first attach:
611
- - If `conversation_id` is set → calls `GET /API/plugin/chatgpt/widgetHistory?conversation_id=...` and renders messages.
612
- - Otherwise → calls `POST /API/plugin/chatgpt/widgetStart` to create a new `ai_widget_conversation` and stores the returned `conversation_id` (and any `assistant_id`/`model`) as attributes.
613
- - On send:
614
- - Optimistically appends a `{ role:'user', content }` message in the UI.
615
- - Calls `POST /API/plugin/chatgpt/widgetMessage` with `{ conversation_id, content, role:'user', assistant_id?, model? }`.
616
- - Renders the updated `messages` and assistant reply from the response.
617
-
618
- - **Server endpoints (`chatgpt` plugin)**:
619
- - `POST /API/plugin/chatgpt/widgetStart` → `chatgpt.widgetStart(data)`
620
- - Input: `{ model?, ai_assistant_id?, source? }`.
621
- - Behavior:
622
- - If `ai_assistant_id` is provided, loads that `ai_assistant` and seeds `model`, `assistant_id`, `system` from it.
623
- - Creates `ai_widget_conversation` with empty `messages` and timestamps.
624
- - Returns `{ success, conversation_id, model, assistant_id }`.
625
- - `GET /API/plugin/chatgpt/widgetHistory?conversation_id=...` → `chatgpt.widgetHistory(data)`
626
- - Input: `{ conversation_id }`.
627
- - Returns `{ success, conversation_id, model, assistant_id, messages }`.
628
- - `POST /API/plugin/chatgpt/widgetMessage` → `chatgpt.widgetMessage(data)`
629
- - Input: `{ conversation_id, content, role='user', assistant_id?, model? }`.
630
- - Behavior:
631
- - Loads `ai_widget_conversation`, appends a user message.
632
- - Calls `openai.responses.create`:
633
- - With `assistant_id` if available (preferred), or
634
- - With `model` and `instructions` (from `system`) otherwise.
635
- - Appends an assistant message, updates `last_message_at`, saves the conversation.
636
- - Returns `{ success, conversation_id, model, assistant_id, messages, last_message, usage }`.
637
-
638
- ### AI Widget Test Page
639
-
640
- To help you develop and debug the widget + plugin in your instance, JOE exposes an auth‑protected test page, similar to the MCP test pages.
641
-
642
- - **Routes** (both require standard JOE `auth`):
643
- - `/ai-widget-test.html`
644
- - `${JOE.webconfig.joepath}ai-widget-test.html` (e.g. `/JsonObjectEditor/ai-widget-test.html`).
645
-
646
- - **Behavior** (`server/modules/Server.js`):
647
- - The route handler:
648
- - Reads `assistant_id` or `assistant` from the query string.
649
- - If not present, tries to resolve the default assistant via:
650
- - `JOE.Utils.Settings('DEFAULT_AI_ASSISTANT', { object: true })` and uses its `.value` as `ai_assistant_id`.
651
- - Renders a small HTML page that:
652
- - Includes the MCP nav (`_www/mcp-nav.js`) so you can jump between MCP and widget tests.
653
- - Mounts `<joe-ai-widget title="JOE AI Assistant" ai_assistant_id="...">` in a centered card.
654
- - Loads `js/joe-ai.js`, which defines both `<joe-ai-chatbox>` (for in‑app use) and `<joe-ai-widget>` (for embedding).
655
-
656
- - **Shared nav link**:
657
- - The shared dev nav (`_www/mcp-nav.js`) includes an “AI Widget” link:
658
-
659
- ```html
660
- <a href="/ai-widget-test.html" target="ai_widget_test_win" rel="noopener">AI Widget</a>
661
- ```
662
-
1
+ <img src="http://joe.craydent.com/JsonObjectEditor/img/svgs/joe_banner_o.svg"/>
2
+
3
+ # Json Object Editor
4
+ JOE is software that allows you to manage data models via JSON objects. There are two flavors, the client-side version and nodejs server platform.
5
+
6
+
7
+
8
+ ## Architecture & Mental Model (Server)
9
+
10
+ - Global JOE
11
+ - Created in `server/init.js`; exposes subsystems like `JOE.Utils`, `JOE.Schemas`, `JOE.Storage`, `JOE.Mongo`/`JOE.MySQL`, `JOE.Cache`, `JOE.Apps`, `JOE.Server` (Express), `JOE.Sites`, `JOE.io` (Socket), and `JOE.auth`.
12
+ - Init pattern
13
+ - Modules can optionally export `init()`; `init.js` loads/watches modules and calls `init()` after `JOE.Server` is ready. This enables hot-reload and keeps `Server.js` thin.
14
+ - Schemas & events
15
+ - `JOE.Schemas` loads from `server/schemas/` and app schema dir into `JOE.Schemas.schema` with names in `JOE.Schemas.schemaList`. Raw copies live in `JOE.Schemas.raw_schemas` for event hooks.
16
+ - `JOE.Storage.save()` triggers schema events (`create`, `save`, `status`, `delete`) via `JOE.Schemas.events()` and writes history documents to `_history`.
17
+ - Storage & cache
18
+ - `JOE.Storage.load(collection, query, cb)` chooses backend per schema `storage.type` (`mongo`, `mysql`, `file`, `api`), with Mongo/file fallback.
19
+ - `JOE.Storage.save(item, collection, cb, { user, history })` emits `item_updated` to sockets, records history, and fires events.
20
+ - `JOE.Cache.update(cb, collections)` populates `JOE.Data`, flattens a `list`, and builds `lookup`. Use `JOE.Cache.findByID(collection, idOrCsv)` for fast lookups; `JOE.Cache.search(query)` for in-memory filtering.
21
+ - Auth
22
+ - `JOE.auth` middleware checks cookie token or Basic Auth; many API routes (and `/mcp`) are protected.
23
+ - Shorthand `$J`
24
+ - Server and client convenience: `$J.get(_id)`, `$J.search(query)`, `$J.schema(name)`.
25
+ - On server, provided by `server/modules/UniversalShorthand.js` and assigned to `global.$J` in `init.js`.
26
+ - MCP overview
27
+ - Manifest: `/.well-known/mcp/manifest.json`; JSON-RPC: `POST /mcp` (auth-protected). Tools map to real JOE APIs (`Schemas`, `Storage`, `Cache`) and sanitize sensitive fields.
28
+
29
+ ## MCP routes and local testing
30
+
31
+ - Endpoints
32
+ - Manifest (public): `GET /.well-known/mcp/manifest.json`
33
+ - JSON-RPC (auth): `POST /mcp`
34
+ - Privacy (public): `/privacy` (uses Setting `PRIVACY_CONTACT` for contact email)
35
+ - Terms (public): `/terms`
36
+
37
+ - Auth
38
+ - If users exist, `POST /mcp` requires cookie or Basic Auth (same as other APIs). If no users configured, it is effectively open.
39
+
40
+ - Test page
41
+ - JOE ships a simple tester at `/_www/mcp-test.html` inside the package.
42
+ - Access via JOE path: `http://localhost:<PORT>/JsonObjectEditor/_www/mcp-test.html`
43
+ - If your host app serves its own `_www`, the tester can also be available at the root (fallback) if running with the updated server that mounts JOE’s `_www` as a secondary static directory. Then: `http://localhost:<PORT>/mcp-test.html`
44
+
45
+ - Tools
46
+ - `listSchemas(name?)`, `getSchema(name)`
47
+ - `getObject(_id, schema?)` (supports optional `flatten` and `depth`)
48
+ - `search` (exact): unified tool for cache and storage
49
+ - Params: `{ schema?, query?, ids?, source?: 'cache'|'storage', limit?, flatten?, depth? }`
50
+ - Defaults to cache across all collections; add `schema` to filter; set `source:"storage"` to query a specific schema in the DB. Use `fuzzySearch` for typo-tolerant free text.
51
+ - `fuzzySearch` (typo-tolerant free text across weighted fields)
52
+ - Params: `{ schema?, q, filters?, fields?, threshold?, limit?, offset?, highlight?, minQueryLength? }`
53
+ - Defaults: `fields` resolved from schema `searchables` (plural) if present; otherwise weights `name:0.6, info:0.3, description:0.1`. `threshold:0.5`, `limit:50`, `minQueryLength:2`.
54
+ - Returns: `{ items, count }`. Each item may include `_score` (0..1) and `_matches` when `highlight` is true.
55
+ - `saveObject({ object })`
56
+ - `saveObjects({ objects, stopOnError?, concurrency? })`
57
+ - Batch save with per-item history/events. Defaults: `stopOnError=false`, `concurrency=5`.
58
+ - Each object must include `itemtype`; `_id` and `joeUpdated` are set when omitted.
59
+ - Return shape: `{ results, errors, saved, failed }`.
60
+
61
+ - Quick tests (PowerShell)
62
+ - Prepare headers if using Basic Auth:
63
+ ```powershell
64
+ $pair = "user:pass"; $b64 = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes($pair))
65
+ $h = @{ Authorization = "Basic $b64"; "Content-Type" = "application/json" }
66
+ $base = "http://localhost:<PORT>"
67
+ ```
68
+ - Manifest:
69
+ ```powershell
70
+ Invoke-RestMethod "$base/.well-known/mcp/manifest.json"
71
+ ```
72
+ - listSchemas:
73
+ ```powershell
74
+ $body = @{ jsonrpc="2.0"; id="1"; method="listSchemas"; params=@{} } | ConvertTo-Json -Depth 6
75
+ Invoke-RestMethod -Method Post -Uri "$base/mcp" -Headers $h -Body $body
76
+ ```
77
+ - getSchema:
78
+ ```powershell
79
+ $body = @{ jsonrpc="2.0"; id="2"; method="getSchema"; params=@{ name="<schemaName>" } } | ConvertTo-Json -Depth 6
80
+ Invoke-RestMethod -Method Post -Uri "$base/mcp" -Headers $h -Body $body
81
+ ```
82
+ - search (cache default):
83
+ ```powershell
84
+ $body = @{ jsonrpc="2.0"; id="3"; method="search"; params=@{ query=@{ itemtype="<schemaName>" }; limit=10 } } | ConvertTo-Json -Depth 10
85
+ Invoke-RestMethod -Method Post -Uri "$base/mcp" -Headers $h -Body $body
86
+ ```
87
+ - fuzzySearch (cache):
88
+ ```powershell
89
+ $body = @{ jsonrpc="2.0"; id="6"; method="fuzzySearch"; params=@{ schema="<schemaName>"; q="st paal"; threshold=0.35; limit=10 } } | ConvertTo-Json -Depth 10
90
+ Invoke-RestMethod -Method Post -Uri "$base/mcp" -Headers $h -Body $body
91
+ ```
92
+ - search (storage):
93
+ ```powershell
94
+ $body = @{ jsonrpc="2.0"; id="4"; method="search"; params=@{ schema="<schemaName>"; source="storage"; query=@{ }; limit=10 } } | ConvertTo-Json -Depth 10
95
+ Invoke-RestMethod -Method Post -Uri "$base/mcp" -Headers $h -Body $body
96
+ ```
97
+ - search (ids + flatten):
98
+ ```powershell
99
+ $body = @{ jsonrpc="2.0"; id="5"; method="search"; params=@{ schema="<schemaName>"; ids=@("<id1>","<id2>"); flatten=$true; depth=2 } } | ConvertTo-Json -Depth 10
100
+ Invoke-RestMethod -Method Post -Uri "$base/mcp" -Headers $h -Body $body
101
+ ```
102
+ - saveObject:
103
+ ```powershell
104
+ $object = @{ itemtype="<schemaName>"; name="Test via MCP" }
105
+ $body = @{ jsonrpc="2.0"; id="4"; method="saveObject"; params=@{ object=$object } } | ConvertTo-Json -Depth 10
106
+ Invoke-RestMethod -Method Post -Uri "$base/mcp" -Headers $h -Body $body
107
+ ```
108
+ - saveObjects (batch):
109
+ ```powershell
110
+ $objs = @(
111
+ @{ itemtype="<schemaName>"; name="Batch A" },
112
+ @{ itemtype="<schemaName>"; name="Batch B" }
113
+ )
114
+ $body = @{ jsonrpc="2.0"; id="7"; method="saveObjects"; params=@{ objects=$objs; stopOnError=$false; concurrency=5 } } | ConvertTo-Json -Depth 10
115
+ Invoke-RestMethod -Method Post -Uri "$base/mcp" -Headers $h -Body $body
116
+ ```
117
+
118
+ - Quick tests (curl)
119
+ - Manifest:
120
+ ```bash
121
+ curl -s http://localhost:<PORT>/.well-known/mcp/manifest.json | jq
122
+ ```
123
+ - listSchemas:
124
+ - search (cache):
125
+ ```bash
126
+ curl -s -X POST http://localhost:<PORT>/mcp \
127
+ -H 'Content-Type: application/json' \
128
+ -d '{"jsonrpc":"2.0","id":"3","method":"search","params":{"query":{"itemtype":"<schemaName>"},"limit":10}}' | jq
129
+ ```
130
+ - fuzzySearch:
131
+ ```bash
132
+ curl -s -X POST http://localhost:<PORT>/mcp \
133
+ -H 'Content-Type: application/json' \
134
+ -d '{"jsonrpc":"2.0","id":"6","method":"fuzzySearch","params":{"schema":"<schemaName>","q":"st paal","threshold":0.35,"limit":10}}' | jq
135
+ ```
136
+ - search (storage):
137
+ ```bash
138
+ curl -s -X POST http://localhost:<PORT>/mcp \
139
+ -H 'Content-Type: application/json' \
140
+ -d '{"jsonrpc":"2.0","id":"4","method":"search","params":{"schema":"<schemaName>","source":"storage","query":{},"limit":10}}' | jq
141
+ ```
142
+ ```bash
143
+ curl -s -X POST http://localhost:<PORT>/mcp \
144
+ -H 'Content-Type: application/json' \
145
+ -d '{"jsonrpc":"2.0","id":"1","method":"listSchemas","params":{}}' | jq
146
+ ```
147
+ - saveObjects (batch):
148
+ ```bash
149
+ curl -s -X POST http://localhost:<PORT>/mcp \
150
+ -H 'Content-Type: application/json' \
151
+ -d '{"jsonrpc":"2.0","id":"7","method":"saveObjects","params":{"objects":[{"itemtype":"<schemaName>","name":"Batch A"}],"stopOnError":false,"concurrency":5}}' | jq
152
+ ```
153
+
154
+ - Troubleshooting
155
+ - If you see a payload like `{ originalURL: "/...", site: "no site found" }`, the request hit the Sites catch-all. Ensure MCP routes are initialized before Sites (handled by default in `server/init.js` via `MCP.init()`), and use the correct URL: `/.well-known/mcp/manifest.json` or `/mcp`.
156
+ - To update contact email on /privacy and /terms, set Setting `PRIVACY_CONTACT`.
157
+
158
+ ## File uploads (S3)
159
+ - Uploader field options:
160
+ - `allowmultiple: true|false` — allow selecting multiple files.
161
+ - `url_field: 'image_url'` — on success, sets this property to the remote URL and rerenders that field.
162
+ - `ACL: 'public-read'` — optional per-field ACL. When omitted, server currently defaults to `public-read` (temporary during migration).
163
+ - Flow:
164
+ - Client posts `{ Key, base64, contentType, ACL? }` to `/API/plugin/awsConnect`.
165
+ - Server uploads with AWS SDK v3 and returns `{ url, Key, bucket, etag }` (HTTP 200).
166
+ - Client uses `response.url`; if `url_field` is set, it assigns and rerenders that field.
167
+ - Errors:
168
+ - If bucket or region config is missing, server returns 400 with a clear message.
169
+ - If the bucket has ACLs disabled, server returns 400: “Bucket has ACLs disabled… remove ACL or switch to presigned/proxy access.”
170
+
171
+ ## SERVER/PLATFORM mode
172
+ check port 2099
173
+ /JOE/
174
+ /JsonObjectEditor/docs.html
175
+ *Should auto-open on start
176
+
177
+ Json Object Editor (Universal-esque software)
178
+ (requires connection to a mongo server for full functionality)
179
+
180
+
181
+
182
+ ### JOE server instantiation (add to entry point js file)
183
+ var joe = require('json-object-editor');
184
+ or here's a custom example
185
+ var joe = require('json-object-editor')({
186
+ name:'name_your_joe' (JOE),
187
+ joedb:'local_custom_database_name' // {{mongoURL}}' if using mongolab or remote mongodb,
188
+ port:'4099', (2099)
189
+ socketPort:'4098', (2098)
190
+ sitesPort:'4100' (2100),
191
+ clusters:1,
192
+ hostname:'server name if not localhost'(localhost)
193
+ });
194
+
195
+ ## $J (universal) Shorthand JOE
196
+ $J to access helper funtions on client and server. (callbacks are optional)
197
+ $J.get(itemId,[callback])
198
+ $J.schema(schemaname,[callback])
199
+
200
+ # Client-Side (front end only)
201
+
202
+ ## js client only instantiation
203
+ var specs = {
204
+ fields:{
205
+ species:{label:'Species',type:'select', values:['cat','dog','rat','thing'], onchange:adjustSchema},
206
+ gender:{type:'select', values:['male','female']},
207
+ legs:{label:'# of Legs',type:'int', onblur:logit},
208
+ weight:{label:' Weight (lbs)',type:'number', onblur:logit},
209
+ name:{label:' pet Name', onkeyup:logValue},
210
+ //id:{label:'ID',type:'text', locked:true},
211
+ id:{label:'ID',type:'guid'},
212
+
213
+ //example of select that takes function (function is passed item)
214
+ animalLink:{label:'Link to other animal',type:'select', values:getAnimals},
215
+ hiddenizer:{hidden:true}
216
+ },
217
+ schemas:{
218
+ animal:animalschema,
219
+ thing:thingschema
220
+ },
221
+ container:string ('body'),
222
+ compact:false,
223
+ useBackButton:true,
224
+ autosave:2000,
225
+ listSubMenu:{filters:{}},
226
+ useHashlink:true,
227
+ title:'${itemtype} | ${display}'
228
+ }
229
+ var JOE = new JsonObjectEditor(specs);
230
+ JOE.init();
231
+
232
+ ##JOE CONFIG
233
+ ##specs
234
+ - useBackButton:[false] if true, back button moves through joe panels when joe has history to go to (is open).
235
+ - useHashlink:[false], true or a template for hashlinks.
236
+ default template is '${schema_name}_${_id}'
237
+ default server tempalte is '${schema_name}/${_id}'
238
+ ##SCHEMA CONFIG
239
+ ###fields
240
+ Properties for all Fields
241
+
242
+ - `label / display`: what the field should display as
243
+ *If the field type is boolean, label controls checkbox/boolean label
244
+ - `value`: default value if not one in object
245
+ - `default`: default value for field || function(object)
246
+ - `type`: what type of field should JOE show
247
+ - `hidden`: boolean / function, value will be added (but unsees by user)
248
+ - `locked`: boolean
249
+ - `condition`: boolean
250
+ - `width`: used for layout control.
251
+ - can use pixels or percentages (as string)
252
+ - `comment`: a commentthat shows up at the beginning of the field
253
+ - `tooltip`: hover/clickable tooltip that shows up next to name
254
+
255
+ **field types:**
256
+
257
+ - `rendering`: for css html and js
258
+ - `text`: default single line text.
259
+ - autocomplete: boolean // obj of specs (template, idprop)
260
+ - values:array of possibilities
261
+ -maxlength:string
262
+ - `int`: integer field
263
+ - `number`: number (float) field
264
+ - `select`: select list.
265
+ - multiple(bool)
266
+ - values(array of objects, [{value:"",name/display:""]), can be a function
267
+ - disabled:boolean(func acceptable)
268
+ - idprop: string of prop name
269
+ - `geo`: shows a map
270
+ - *takes a string array "[lat,lon]"*
271
+ - center:[lat,lon], center of map
272
+ - zoom: zoom level (higher zooms in more)
273
+ - returns "[lat,lon]"
274
+ -`image` : shows an image and HxW as th image url is typed in.
275
+ - `multisorter` : allows arrays of objects to be selected and sorted in right bin.
276
+ - values(array of objects, [{value:"",name/display:""]), can be a function
277
+ - `content` : show content on in the editor
278
+ - run: function to be run(current_object,field_properties)
279
+ - template: html template for fillTemplate(template,current_object);
280
+ - `objectlist` : a table of objects with editable properties
281
+ - properties: array of objects|strings for the object property names
282
+ -name: value in object
283
+ -display: header in objectList
284
+ - max: integer, number or items that can be added. use zero for infinite.
285
+ - hideHeadings: don't show table headings
286
+ - `objectReference` : a list of object ids
287
+ - template
288
+ - autocomplete_template
289
+ - idprop
290
+ - values
291
+ - max(0 unlimited)
292
+ - sortable(true)
293
+ - `code` :
294
+ - language
295
+
296
+ - `boolean`:
297
+ - label:controls checkbox label
298
+ - `preview` :
299
+ -content: string or function(current joe object) to replace everything on page (template).
300
+ -bodycontent: same as content, only replaces body content.
301
+ -url: preview page if not the default one.
302
+ - encoded: boolean, if pre uriencoded
303
+ **labels:**
304
+
305
+ - pass an object instead of a string to the fields array.
306
+
307
+
308
+ {label:'Name of the following properties section'}
309
+
310
+ ##page sections
311
+ {section_start: 'SectionName',
312
+ section_label:'Section Name with Labels',
313
+ condition:function(item){
314
+ return item.show;}
315
+ },
316
+ {section_end: 'CreativeBrief'}
317
+
318
+ - pass an object instead of a string to the fields array. these show up on the details view as anchors.
319
+
320
+
321
+ - Object Properties
322
+ - `section_start`: name/id of section
323
+ - `'section_label`:use instead of section_start for display name
324
+ - `section_end`: name/id of section(str)
325
+ - template: html template for fillTemplate(template,current_object);
326
+ - Note: for fields, `condition` removes the field from the DOM while `hidden` only toggles visibility; sections use `condition` (and sidebar sections use `hidden`) differently than fields.
327
+
328
+ ##page sidebar
329
+ {sidebar_start: 'SectionName',
330
+ sidebar_label:'Section Name with Labels',
331
+ condition:function(item){
332
+ return item.show;}
333
+ },
334
+ {sidebar_end: 'CreativeBrief'}
335
+
336
+ - pass an object instead of a string to the fields array. these show up on the details view as anchors.
337
+
338
+
339
+ - Object Properties
340
+ - `sidebar_start`: name/id of sidebar
341
+ - `sidebar_label`:use instead of sidebar_start for display name
342
+ - `sidebar_end`: name/id of sidebar(str)
343
+ - template: html template for fillTemplate(template,current_object);
344
+
345
+ ###defaultProfile
346
+ overwrites the default profile
347
+
348
+ #schemas
349
+
350
+ a list of schema objects that can configure the editor fields, these can be given properties that are delegated to all the corresponding fields.
351
+
352
+ var animalschema =
353
+ {
354
+ title:'Animal', *what shows as the panel header*
355
+ fields:['id','name','legs','species','weight','color','gender','animalLink'], *list of visible fields*
356
+ _listID:'id', *the id for finding the object*
357
+ _listTitle:'${name} ${species}', *how to display items in the list*
358
+ menu:[array of menu buttons],
359
+ listMenuTitle: (string) template forjoe window title in list view,
360
+ listmenu:[array of menu buttons] (multi-edit and select all always show),
361
+ /*callback:function(obj){
362
+ alert(obj.name);
363
+ },*/
364
+ onblur:logit,
365
+
366
+ hideNumbers:boolean *toggle list numbers*
367
+ multipleCallback:function to be called after a multi-edit. passed list of edited items.
368
+ onUpdate: callback for after update. passed single edited items.
369
+ onMultipleUpdate:callback for after multi update.passed list of edited items.
370
+ filters: array of objects
371
+ checkChanges:(bool) whether or not to check for changes via interval and on leave
372
+ }
373
+ ##Table View
374
+ - add tableView object to a schema;
375
+ -cols = [strings||objects]
376
+ -string is the name and value
377
+ -display/header is the column title
378
+ -property/name = object property
379
+ ###Pre-formating
380
+ you can preformat at the joe call or schema level. The data item will be affected by the passed function (which should return the preformated item).
381
+
382
+ ##menu##
383
+ an array of menu buttons
384
+
385
+ //the default save button
386
+ //this is the dom object,
387
+ //use _joe.current.object for current object
388
+ condition:function(field,object) to call
389
+ self = Joe object
390
+ var __saveBtn__ = {name:'save',label:'Save', action:'_joe.updateObject(this);', css:'joe-save-button'};
391
+
392
+ ##itemMenu##
393
+ as array of buttons for each item in list views
394
+ - name
395
+ - action (action string)
396
+ - url (instead of js action)
397
+ - condition
398
+
399
+ ##itemExpander##
400
+ template or run for content to be shown under the main list item block.
401
+
402
+
403
+ ###Addition properties
404
+ **Changing the schema on the fly?**
405
+
406
+ _joe.resetSchema(new schema name);
407
+
408
+
409
+
410
+ **css (included) options**
411
+
412
+ - joe-left-button
413
+ - joe-right-button
414
+
415
+ ##FIELDS
416
+
417
+ {extend:'name',specs:{display:'Request Title'}},//extends the field 'name' with the specs provided.
418
+ ##usage
419
+ ### a | adding a new object
420
+
421
+ _joe.show({},{schema:'animal',callback:addAnimal);
422
+ //or goJoe(object,specs)
423
+
424
+ ...
425
+ function addAnimal(obj){
426
+ animals.push(obj);
427
+ }
428
+
429
+ ### b | viewing a list of objects
430
+
431
+ goJoe([array of objects],specs:{schema,subsets,subset})
432
+ goJoe.show(animals,{schema:'animal',subsets:[{name:'Two-Legged',filter:{legs:2}}]});
433
+ //use the specs property subset to pre-select a subset by name
434
+
435
+ **properties**
436
+
437
+ - _listWindowTitle: the title of the window (can be passed in with the schema);
438
+ - _listCount: added to the current object and can be used in the title.
439
+ - _listTitle:'${name} ${species}', *how to display items in the list*
440
+ - _icon: [str] template for a list item icon (standard min 50x50), 'http://www.icons.com/${itemname}', can be obj with width, height, url
441
+ - listSubMenu:a function or object that represents the list submenu
442
+ - stripeColor:string or function that returns valid css color descriptor.
443
+ - bgColor:string or function that returns valid css color descriptor.
444
+ - subsets: name:string, filter:object
445
+ - subMenu:a function or object that represents the single item submenu
446
+
447
+ - _listTemplate: html template that uses ${var} to write out the item properties for the list item.
448
+ - standard css class `joe-panel-content-option`
449
+
450
+ ### Helper shortcuts: subsets and stripes
451
+
452
+ Use these helpers to quickly generate subset/filter options and add per-item stripe colors.
453
+
454
+ ```javascript
455
+ // In a schema definition
456
+ {
457
+ // 1) Subsets from statuses of the current schema (auto colors)
458
+ subsets: function () {
459
+ return _joe.Filter.Options.status({
460
+ group: 'status', // optional grouping label
461
+ collapsed: true, // start group collapsed
462
+ none: true // include a "no status" option
463
+ // color: 'stripecolor' // uncomment to render colored stripes in the menu
464
+ });
465
+ },
466
+
467
+ // 2) Subsets for all distinct values of a property
468
+ // Example: recommendation_domain on 'recommendation' items
469
+ // (pass values to control order/allowlist)
470
+ // subsets: () => _joe.Filter.Options.getDatasetPropertyValues(
471
+ // 'recommendation', 'recommendation_domain', { group: 'domain', values: ['product','dietary','activity'] }
472
+ // ),
473
+
474
+ // 3) Subsets from another dataset (reference values)
475
+ // subsets: () => _joe.Filter.Options.datasetProperty('user','members',{ group: 'assignees', collapsed: true }),
476
+
477
+ // 4) Row stripe color by status (string or { color, title })
478
+ stripeColor: function (item) {
479
+ if (!item.status) return '';
480
+ const s = _joe.getDataItem(item.status, 'status');
481
+ return s && { color: s.color, title: s.name };
482
+ }
483
+ }
484
+ ```
485
+
486
+
487
+
488
+ ###c | Conditional select that changes the item schema
489
+
490
+ fields:{
491
+ species:{label:'Species',type:'select', values:['cat','dog','rat','thing'], onchange:adjustSchema},
492
+ [field_id]:{
493
+
494
+ +label : STR
495
+ +type : STR
496
+ value : STR (default value)
497
+ +values : ARRAY/FUNC (for select)
498
+
499
+ //modifiers
500
+ +hidden:BOOL/STRING(name of field that toggles this) //don't show, but value is passed
501
+ +locked:BOOL // show, but uneditable
502
+ //events
503
+ +onchange : FUNC
504
+ +onblur : FUNC
505
+ +onkeypress : FUNC
506
+ +rerender : STRING // name of field to rerender
507
+ }
508
+ }
509
+
510
+ function adjustSchema(dom){
511
+ var species = $(dom).val();
512
+ if(species == "thing"){
513
+ JOE.resetSchema('thing')
514
+ }
515
+ else{
516
+ JOE.resetSchema('animal')
517
+
518
+ }
519
+ }
520
+
521
+ ###d | duplicating an item
522
+
523
+ //duplicates the currently active object (being edited)
524
+ _joe.duplicateObject(specs);
525
+
526
+ **specs**
527
+
528
+ - `deletes`:array of properties to clear for new item
529
+ - note that you will need to delete guid/id fields or the id will be the same.
530
+
531
+
532
+
533
+ ### e | exporting an object in pretty format json (or minified)
534
+ JOE.exportJSON = function(object,objvarname,minify)
535
+
536
+ ##Useful Functions
537
+ _joe.reload(hideMessage,specs)
538
+ - use specs.overwreite object to extend reloaded object.
539
+
540
+ _joe.constructObjectFromFields()
541
+
542
+ ## AI / Chat Integrations (OpenAI Responses + Assistants)
543
+
544
+ JOE ships a few plugins and schemas to help you connect OpenAI assistants and the new Responses API to your data model.
545
+
546
+ - **`chatgpt` plugin**
547
+ - Central entry point for OpenAI calls (uses the `openai` Node SDK and the Responses API).
548
+ - Looks up the OpenAI API key from the `setting` schema:
549
+ - `OPENAI_API_KEY`: your secret key (can be a service‑account key, e.g. `sk-svcacct-...`).
550
+ - Key methods:
551
+ - **`autofill`** (`POST /API/plugin/chatgpt/autofill`):
552
+ - Given `{ object_id, schema, fields, prompt?, assistant_id?, model? }`, calls `openai.responses.create(...)` and returns a JSON patch for the requested fields only.
553
+ - Used by `_joe.Ai.populateField(...)` for schema‑driven “AI fill” buttons.
554
+ - **`executeJOEAiPrompt`**:
555
+ - Drives the `ai_prompt` → `ai_response` workflow using Responses API + optional helper functions.
556
+ - **Widget‑focused methods**:
557
+ - `widgetStart`, `widgetMessage`, `widgetHistory` for the embeddable AI widget (see below).
558
+
559
+ - **`chatgpt-assistants` plugin (legacy Assistants API)**
560
+ - Manages the older `beta.assistants` / `beta.threads` API for JOE’s rich in‑app chat (`<joe-ai-chatbox>`).
561
+ - Still used by the `ai_conversation` flow; new integrations should prefer the Responses‑based `chatgpt` plugin.
562
+
563
+ - **`chatgpt-tools` plugin**
564
+ - Exposes JOE “AI tools” configured via the `ai_tool` schema.
565
+ - Useful when you want OpenAI tools that call back into JOE (e.g. search, profile, etc.).
566
+
567
+ ### AI Assistant schemas
568
+
569
+ - **`ai_assistant` schema**
570
+ - Represents a JOE‑side “assistant config” that can be synced to an OpenAI assistant.
571
+ - Key fields:
572
+ - `ai_model`: default model (e.g. `gpt-4.1-mini`, `gpt-4o`).
573
+ - `instructions`: system prompt for the assistant.
574
+ - `assistant_id`: OpenAI assistant ID (`asst_...`) set by the sync button.
575
+ - `file_search_enabled`, `code_interpreter_enabled`: toggle built‑in tools.
576
+ - `tools`: JSON definition array for custom function tools.
577
+ - `assistant_thinking_text`: what the UI shows while the assistant is “thinking”.
578
+ - UI highlights the **default assistant**:
579
+ - The assistant whose `_id` matches the `DEFAULT_AI_ASSISTANT` setting is given a golden stripe in list view.
580
+ - Default assistant resolution:
581
+ - The client helper `Ai.getDefaultAssistant()` in `joe-ai.js` looks up `_joe.Data.setting.where({ name: 'DEFAULT_AI_ASSISTANT' })[0]` and caches it.
582
+
583
+ - **`ai_widget_conversation` schema**
584
+ - Lightweight conversation log used by the new embeddable widget (`<joe-ai-widget>`).
585
+ - Fields:
586
+ - `model`: model name used for the conversation (or inherited from `ai_assistant.ai_model`).
587
+ - `assistant`: optional reference to the JOE `ai_assistant` used.
588
+ - `assistant_id`: OpenAI assistant ID (`asst_...`) used by the Responses API.
589
+ - `system`: effective system prompt.
590
+ - `messages`: JSON array of `{ role, content, created_at }` for the widget chat.
591
+ - `last_message_at`, `source`, `tags`, standard system fields.
592
+ - This schema is what the widget endpoints in `chatgpt` read/write.
593
+
594
+ ### Embeddable AI Widget (`<joe-ai-widget>`)
595
+
596
+ JOE includes a small web component, defined in `js/joe-ai.js`, that can be dropped into any web page and talks to your JOE instance via the `chatgpt` plugin.
597
+
598
+ - **Custom element**: `<joe-ai-widget>`
599
+ - Attributes:
600
+ - `endpoint` (optional): Base URL to the JOE instance. Defaults to same origin.
601
+ Example: `endpoint="https://my-joe.example.com"`.
602
+ - `ai_assistant_id` (optional): `_id` of an `ai_assistant` document in JOE. If present, the server will load this assistant and use its `ai_model`, `instructions`, and `assistant_id` when starting the conversation.
603
+ - `assistant_id` (optional): Direct OpenAI assistant ID (`asst_...`). If provided, the widget uses Responses API with `assistant_id` instead of a bare `model`.
604
+ - `model` (optional): Model name to use when no assistant is supplied (e.g. `gpt-4.1-mini`).
605
+ - `title` (optional): Header title text shown in the widget panel.
606
+ - `placeholder` (optional): Input placeholder text.
607
+ - `source` (optional): Arbitrary string stored on the conversation (`ai_widget_conversation.source`).
608
+ - `conversation_id` (optional): Existing `ai_widget_conversation._id` to resume a saved chat.
609
+ - Behavior:
610
+ - On first attach:
611
+ - If `conversation_id` is set → calls `GET /API/plugin/chatgpt/widgetHistory?conversation_id=...` and renders messages.
612
+ - Otherwise → calls `POST /API/plugin/chatgpt/widgetStart` to create a new `ai_widget_conversation` and stores the returned `conversation_id` (and any `assistant_id`/`model`) as attributes.
613
+ - On send:
614
+ - Optimistically appends a `{ role:'user', content }` message in the UI.
615
+ - Calls `POST /API/plugin/chatgpt/widgetMessage` with `{ conversation_id, content, role:'user', assistant_id?, model? }`.
616
+ - Renders the updated `messages` and assistant reply from the response.
617
+
618
+ - **Server endpoints (`chatgpt` plugin)**:
619
+ - `POST /API/plugin/chatgpt/widgetStart` → `chatgpt.widgetStart(data)`
620
+ - Input: `{ model?, ai_assistant_id?, source? }`.
621
+ - Behavior:
622
+ - If `ai_assistant_id` is provided, loads that `ai_assistant` and seeds `model`, `assistant_id`, `system` from it.
623
+ - Creates `ai_widget_conversation` with empty `messages` and timestamps.
624
+ - Returns `{ success, conversation_id, model, assistant_id }`.
625
+ - `GET /API/plugin/chatgpt/widgetHistory?conversation_id=...` → `chatgpt.widgetHistory(data)`
626
+ - Input: `{ conversation_id }`.
627
+ - Returns `{ success, conversation_id, model, assistant_id, messages }`.
628
+ - `POST /API/plugin/chatgpt/widgetMessage` → `chatgpt.widgetMessage(data)`
629
+ - Input: `{ conversation_id, content, role='user', assistant_id?, model? }`.
630
+ - Behavior:
631
+ - Loads `ai_widget_conversation`, appends a user message.
632
+ - Calls `openai.responses.create`:
633
+ - With `assistant_id` if available (preferred), or
634
+ - With `model` and `instructions` (from `system`) otherwise.
635
+ - Appends an assistant message, updates `last_message_at`, saves the conversation.
636
+ - Returns `{ success, conversation_id, model, assistant_id, messages, last_message, usage }`.
637
+
638
+ ### AI Widget Test Page
639
+
640
+ To help you develop and debug the widget + plugin in your instance, JOE exposes an auth‑protected test page, similar to the MCP test pages.
641
+
642
+ - **Routes** (both require standard JOE `auth`):
643
+ - `/ai-widget-test.html`
644
+ - `${JOE.webconfig.joepath}ai-widget-test.html` (e.g. `/JsonObjectEditor/ai-widget-test.html`).
645
+
646
+ - **Behavior** (`server/modules/Server.js`):
647
+ - The route handler:
648
+ - Reads `assistant_id` or `assistant` from the query string.
649
+ - If not present, tries to resolve the default assistant via:
650
+ - `JOE.Utils.Settings('DEFAULT_AI_ASSISTANT', { object: true })` and uses its `.value` as `ai_assistant_id`.
651
+ - Renders a small HTML page that:
652
+ - Includes the MCP nav (`_www/mcp-nav.js`) so you can jump between MCP and widget tests.
653
+ - Mounts `<joe-ai-widget title="JOE AI Assistant" ai_assistant_id="...">` in a centered card.
654
+ - Loads `js/joe-ai.js`, which defines both `<joe-ai-chatbox>` (for in‑app use) and `<joe-ai-widget>` (for embedding).
655
+
656
+ - **Shared nav link**:
657
+ - The shared dev nav (`_www/mcp-nav.js`) includes an “AI Widget” link:
658
+
659
+ ```html
660
+ <a href="/ai-widget-test.html" target="ai_widget_test_win" rel="noopener">AI Widget</a>
661
+ ```
662
+
663
663
  - This appears on MCP test/export/prompt pages and on the AI widget test page itself.