json-object-editor 0.10.665 → 0.10.668

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "json-object-editor",
3
- "version": "0.10.665",
3
+ "version": "0.10.668",
4
4
  "description": "JOE the Json Object Editor | Platform Edition",
5
5
  "main": "app.js",
6
6
  "scripts": {
@@ -98,8 +98,19 @@ var schema = {
98
98
  'name',
99
99
  'info',
100
100
  {name:"prompt_method",placeholder:"name of method to call in plugin", comment:'use executeJOEAiPrompt to use the JOE ui here for all smarts', default:"executeJOEAiPrompt",display:"Prompt Plugin Method",type:'text'},
101
- {name:'content_items',type:'objectList',
102
- properties:['itemtype','reference']
101
+ {name:'content_items',type:'objectList',display:'Content Items',
102
+ properties:['itemtype','reference'],
103
+ comment:`
104
+ <div>
105
+ <p>Define which objects are automatically passed when running this prompt via <code>select_prompt</code>.</p>
106
+ <ul>
107
+ <li><b><code>itemtype</code></b> - Schema name (e.g., <code>"task"</code>)</li>
108
+ <li><b><code>reference</code></b> - Parameter name for the object's <code>_id</code> (e.g., <code>"task"</code>)</li>
109
+ </ul>
110
+ <p>When running from a matching object: <code>params[reference]</code> = object <code>_id</code>, <code>content_objects[itemtype]</code> = full object (available in helper functions).</p>
111
+ <p><b>Note:</b> Only objects matching the current object's <code>itemtype</code> are auto-passed. For multiple objects, call programmatically or load in helper functions.</p>
112
+ </div>
113
+ `
103
114
  },
104
115
  {section_start:'Input'},
105
116
  {name: 'functions', type: 'code', display: 'Helper Functions', language:'javascript',comment: `
@@ -0,0 +1,314 @@
1
+ class JoeWorkflowWidget extends HTMLElement {
2
+ constructor() {
3
+ super();
4
+ this.updateTimer = null;
5
+ this._rendering = false;
6
+ this._renderScheduled = false;
7
+ this.config = this.getDefaultConfig();
8
+ this.joeIndex = null;
9
+ this.widgetFieldName = null;
10
+ this._liveObj = null; // constructed snapshot for live completion when autoUpdate enabled
11
+ }
12
+
13
+ static get observedAttributes() {
14
+ return ['schema', '_id'];
15
+ }
16
+
17
+ connectedCallback() {
18
+ this.schemaName = this.getAttribute('schema');
19
+ this.objectId = this.getAttribute('_id');
20
+ this.classList.add('joe-workflow-widget');
21
+
22
+ // Get workflow config from field definition
23
+ this.workflowField = this.closest('.joe-object-field');
24
+ if (this.workflowField) {
25
+ var fieldName = this.workflowField.getAttribute('data-name');
26
+ this.widgetFieldName = fieldName;
27
+ if (fieldName && window._joe) {
28
+ var fieldDef = window._joe.getField(fieldName);
29
+ this.config = (fieldDef && fieldDef.workflow_config) || this.getDefaultConfig();
30
+ }
31
+ }
32
+
33
+ this.config = this.config || this.getDefaultConfig();
34
+
35
+ // Determine joeIndex for navigation and updates
36
+ try{
37
+ var overlay = this.closest('.joe-overlay');
38
+ if(overlay && overlay.getAttribute('data-joeindex') !== null){
39
+ this.joeIndex = parseInt(overlay.getAttribute('data-joeindex'), 10);
40
+ if(isNaN(this.joeIndex)){ this.joeIndex = null; }
41
+ }
42
+ }catch(e){}
43
+
44
+ // Get schema name from JOE if not provided
45
+ if (!this.schemaName && window._joe && window._joe.current && window._joe.current.schema) {
46
+ this.schemaName = window._joe.current.schema.__schemaname || window._joe.current.schema.name;
47
+ }
48
+
49
+ this.scheduleRender();
50
+ this.setupUpdateListener();
51
+ }
52
+
53
+ getDefaultConfig() {
54
+ return {
55
+ sections: 'all',
56
+ fields: 'all',
57
+ excludeSections: ['system'],
58
+ excludeFields: ['_id', 'created', 'joeUpdated', 'itemtype', 'tags', 'status'],
59
+ mustBeTrue: [],
60
+ autoUpdate: false // Disabled by default to prevent lockups
61
+ };
62
+ }
63
+
64
+ setupUpdateListener() {
65
+ // Only set up listeners if autoUpdate is enabled
66
+ if (!this.config || !this.config.autoUpdate) {
67
+ return;
68
+ }
69
+
70
+ // Simplest live update (no tech debt):
71
+ // On change, compute a constructed snapshot once (debounced) and re-render from that.
72
+ // Critically, we never call _jco(true) from render() to avoid re-entrancy loops.
73
+ if(this.joeIndex === null || !window.getJoe){ return; }
74
+ var self = this;
75
+ var overlay = this.closest('.joe-overlay');
76
+ if(!overlay){ return; }
77
+ this._onFormChange = function(ev){
78
+ if(ev && ev.target && self.contains(ev.target)){ return; }
79
+ clearTimeout(self.updateTimer);
80
+ self.updateTimer = setTimeout(function(){
81
+ try{
82
+ var j = getJoe(self.joeIndex);
83
+ self._liveObj = (j && j.constructObjectFromFields) ? j.constructObjectFromFields(self.joeIndex) : null;
84
+ }catch(e){
85
+ self._liveObj = null;
86
+ }
87
+ self.scheduleRender();
88
+ }, 350);
89
+ };
90
+ overlay.addEventListener('change', this._onFormChange, true);
91
+ }
92
+
93
+ disconnectedCallback() {
94
+ try{
95
+ var overlay = this.closest('.joe-overlay');
96
+ if(overlay && this._onFormChange){
97
+ overlay.removeEventListener('change', this._onFormChange, true);
98
+ }
99
+ }catch(e){}
100
+ if (this.updateTimer) {
101
+ clearTimeout(this.updateTimer);
102
+ }
103
+ }
104
+
105
+ attributeChangedCallback(attr, oldValue, newValue) {
106
+ if (oldValue !== newValue) {
107
+ if (attr === 'schema') {
108
+ this.schemaName = newValue;
109
+ } else if (attr === '_id') {
110
+ this.objectId = newValue;
111
+ }
112
+ this.scheduleRender();
113
+ }
114
+ }
115
+
116
+ scheduleRender() {
117
+ if (this._renderScheduled) {
118
+ return;
119
+ }
120
+ this._renderScheduled = true;
121
+ var self = this;
122
+ // Defer to avoid re-entrancy with JOE render/construct cycles
123
+ setTimeout(function() {
124
+ self._renderScheduled = false;
125
+ self.render();
126
+ }, 0);
127
+ }
128
+
129
+ render() {
130
+ var self = this;
131
+ if (this._rendering) {
132
+ return;
133
+ }
134
+ this._rendering = true;
135
+ var joe = window._joe;
136
+ if (!joe || !joe.current) {
137
+ this.innerHTML = '<div class="joe-workflow-error">Workflow widget requires active JOE instance</div>';
138
+ this._rendering = false;
139
+ return;
140
+ }
141
+
142
+ var fields = joe.current.fields || [];
143
+ var sections = joe.current.sections || {};
144
+ // IMPORTANT: Do NOT call _jco(true) here. It triggers constructObjectFromFields which can
145
+ // cause re-entrant rerenders / attributeChangedCallback loops and lock up the UI.
146
+ // Use the live object snapshot. (When autoUpdate is re-enabled later, we can switch to a safer source.)
147
+ var currentObj = this._liveObj || joe.current.object || {};
148
+
149
+ // Filter sections to show
150
+ var sectionsToShow = [];
151
+ if (this.config.sections === 'all') {
152
+ for (var secId in sections) {
153
+ if (this.config.excludeSections && this.config.excludeSections.indexOf(secId) !== -1) continue;
154
+ if (secId === 'system') continue;
155
+ if (!sections[secId] || !sections[secId].fields || !sections[secId].fields.length) continue;
156
+ sectionsToShow.push(secId);
157
+ }
158
+ } else if (Array.isArray(this.config.sections)) {
159
+ sectionsToShow = this.config.sections.filter(function(secId) {
160
+ return sections[secId] &&
161
+ (!self.config.excludeSections || self.config.excludeSections.indexOf(secId) === -1);
162
+ });
163
+ }
164
+
165
+ // Calculate progress for each section
166
+ var sectionProgress = {};
167
+ sectionsToShow.forEach(function(sectionId) {
168
+ var section = sections[sectionId];
169
+ if (!section) return;
170
+
171
+ var fieldsInSection = section.fields || [];
172
+ if (fieldsInSection.length === 0) return;
173
+
174
+ var completed = 0;
175
+ var totalCount = 0;
176
+ var missing = [];
177
+ fieldsInSection.forEach(function(fname) {
178
+ var field = joe.getField(fname) || { name: fname, type: 'text' };
179
+ var ftype = (joe.propAsFuncOrValue && joe.propAsFuncOrValue(field.type, currentObj)) || field.type || 'text';
180
+ ftype = (ftype || '').toString().toLowerCase();
181
+ // Never include content fields in completion counts (widgets, labels, etc).
182
+ if(ftype === 'content'){ return; }
183
+ if(self.config.excludeFields && self.config.excludeFields.indexOf(field.name) !== -1){ return; }
184
+ if(self.config.fields === 'requiredOnly' && !joe.propAsFuncOrValue(field.required, currentObj)){ return; }
185
+ if(field.hidden && joe.propAsFuncOrValue(field.hidden, currentObj)){ return; }
186
+ if(field.condition && !joe.propAsFuncOrValue(field.condition, currentObj)){ return; }
187
+ totalCount++;
188
+ if (self.isFieldComplete(field, currentObj)) {
189
+ completed++;
190
+ }else{
191
+ missing.push(field.display || field.label || field.name);
192
+ }
193
+ });
194
+
195
+ if(!totalCount){ return; }
196
+ var percentage = Math.round((completed / totalCount) * 100);
197
+ sectionProgress[sectionId] = {
198
+ name: section.name || sectionId,
199
+ completed: completed,
200
+ total: totalCount,
201
+ percentage: percentage,
202
+ isComplete: percentage === 100,
203
+ anchor: section.anchor || sectionId,
204
+ missing: missing
205
+ };
206
+ });
207
+
208
+ // Render widget HTML
209
+ var widgetTitle = this.config.title;
210
+ if (!widgetTitle && this.schemaName) {
211
+ // Capitalize first letter of schema name
212
+ widgetTitle = (this.schemaName.charAt(0).toUpperCase() + this.schemaName.slice(1)) + ' Workflow';
213
+ }
214
+ if (!widgetTitle) {
215
+ widgetTitle = 'Workflow'; // fallback
216
+ }
217
+ var html = '<div class="joe-workflow-widget-header">' + widgetTitle + '</div>';
218
+
219
+ var hasSections = false;
220
+ sectionsToShow.forEach(function(sectionId) {
221
+ var progress = sectionProgress[sectionId];
222
+ if (!progress) return;
223
+ hasSections = true;
224
+
225
+ var statusHtml = progress.isComplete
226
+ ? '<span class="joe-workflow-checkmark" title="Complete">✓</span>'
227
+ : '<span class="joe-workflow-percentage" title="' + progress.completed + ' of ' + progress.total + ' fields complete">' + progress.percentage + '%</span>';
228
+
229
+ var ji = (self.joeIndex !== null ? self.joeIndex : 0);
230
+ var safeSectionIdHtml = (sectionId || '').toString()
231
+ .replace(/&/g, '&amp;')
232
+ .replace(/"/g, '&quot;');
233
+ // Use double-quotes inside gotoSection(...) to avoid JS escaping issues.
234
+ var clickAction = 'onclick="try{ getJoe(' + ji + ').gotoSection(&quot;' + safeSectionIdHtml + '&quot;); }catch(e){}" style="cursor:pointer;"';
235
+
236
+ var tip = '';
237
+ if(progress.missing && progress.missing.length){
238
+ tip = progress.missing.join(' | ')
239
+ .replace(/&/g, '&amp;')
240
+ .replace(/"/g, '&quot;')
241
+ .replace(/</g, '&lt;')
242
+ .replace(/>/g, '&gt;');
243
+ }
244
+
245
+ html += '<div class="joe-workflow-section" ' + clickAction + (tip ? (' title="' + tip + '"') : '') + '>';
246
+ html += '<div class="joe-workflow-section-label">' + progress.name + '</div>';
247
+ html += '<div class="joe-workflow-section-status">' + statusHtml + '</div>';
248
+ html += '</div>';
249
+ });
250
+
251
+ if (!hasSections) {
252
+ html += '<div class="joe-workflow-empty">No sections to track</div>';
253
+ }
254
+
255
+ this.innerHTML = html;
256
+ this._rendering = false;
257
+ }
258
+
259
+ isFieldComplete(field, obj) {
260
+ var value = obj[field.name];
261
+ var fieldType = (field.type || '').toLowerCase();
262
+ var mustBeTrue = this.config.mustBeTrue && Array.isArray(this.config.mustBeTrue)
263
+ && this.config.mustBeTrue.indexOf(field.name) !== -1;
264
+
265
+ if (value === undefined || value === null) {
266
+ return false;
267
+ }
268
+
269
+ switch (fieldType) {
270
+ case 'text':
271
+ case 'rendering':
272
+ case 'code':
273
+ case 'wysiwyg':
274
+ case 'url':
275
+ return typeof value === 'string' && value.trim().length > 0;
276
+
277
+ case 'objectreference':
278
+ return Array.isArray(value) ? value.length > 0 : !!value;
279
+
280
+ case 'objectlist':
281
+ return Array.isArray(value) && value.length > 0;
282
+
283
+ case 'number':
284
+ return typeof value === 'number' && !isNaN(value);
285
+
286
+ case 'boolean':
287
+ if (mustBeTrue) {
288
+ return value === true;
289
+ }
290
+ return typeof value === 'boolean';
291
+
292
+ case 'date':
293
+ case 'date-time':
294
+ return !!value;
295
+
296
+ case 'select':
297
+ return value !== '' && value !== null && value !== undefined;
298
+
299
+ case 'uploader':
300
+ return Array.isArray(value) && value.length > 0;
301
+
302
+ default:
303
+ if (Array.isArray(value)) {
304
+ return value.length > 0;
305
+ }
306
+ if (typeof value === 'object') {
307
+ return Object.keys(value).length > 0;
308
+ }
309
+ return !!value;
310
+ }
311
+ }
312
+ }
313
+
314
+ window.customElements.define('joe-workflow-widget', JoeWorkflowWidget);