0
# Utilities & Helpers
1
2
A comprehensive set of utility functions for serialization, buffer handling, view management, promise resolution, and common operations used throughout the Jupyter widgets ecosystem.
3
4
## Capabilities
5
6
### Model Serialization
7
8
Utilities for handling widget model references in complex nested data structures.
9
10
```typescript { .api }
11
/**
12
* Recursively replace model ID strings with actual model instances
13
* @param value - Data structure containing model references
14
* @param manager - Widget manager for model resolution
15
* @returns Promise resolving to data with unpacked model instances
16
*/
17
function unpack_models(
18
value: any | Dict<unknown> | string | (Dict<unknown> | string)[],
19
manager?: IWidgetManager
20
): Promise<WidgetModel | Dict<WidgetModel> | WidgetModel[] | any>;
21
22
/**
23
* Recursively replace model instances with their ID strings
24
* @param value - Data structure containing model instances
25
* @param widget - Widget context for serialization
26
* @returns Data with model instances replaced by ID strings
27
*/
28
function pack_models(
29
value: WidgetModel | Dict<WidgetModel> | WidgetModel[] | any,
30
widget?: WidgetModel
31
): any | Dict<unknown> | string | (Dict<unknown> | string)[];
32
```
33
34
**Usage Examples:**
35
36
```typescript
37
// Unpack model references received from kernel
38
const rawData = {
39
title: "My Dashboard",
40
widgets: ["IPY_MODEL_abc123", "IPY_MODEL_def456"],
41
config: {
42
layout: "IPY_MODEL_layout1",
43
theme: "IPY_MODEL_theme1"
44
}
45
};
46
47
const processedData = await unpack_models(rawData, widgetManager);
48
// Now processedData.widgets contains actual WidgetModel instances
49
// processedData.config.layout is a LayoutModel instance
50
51
// Pack models for transmission to kernel
52
const dataToSend = {
53
selectedWidgets: [widget1, widget2],
54
mainLayout: layoutModel
55
};
56
57
const serializedData = pack_models(dataToSend);
58
// Becomes: { selectedWidgets: ["IPY_MODEL_abc123", "IPY_MODEL_def456"], mainLayout: "IPY_MODEL_layout1" }
59
```
60
61
### Binary Buffer Handling
62
63
Functions for managing binary data within JSON-serializable state objects.
64
65
```typescript { .api }
66
/**
67
* Insert binary buffers into a state object at specified paths
68
* @param state - State object to modify
69
* @param buffer_paths - Array of paths where buffers should be placed
70
* @param buffers - Array of binary buffers to insert
71
*/
72
function put_buffers(
73
state: Dict<BufferJSON>,
74
buffer_paths: (string | number)[][],
75
buffers: (DataView | ArrayBuffer | ArrayBufferView | { buffer: ArrayBuffer })[]
76
): void;
77
78
/**
79
* Extract binary buffers from a state object, replacing them with null/removed keys
80
* @param state - State object containing binary data
81
* @returns Object with separated state, buffer paths, and buffers
82
*/
83
function remove_buffers(
84
state: BufferJSON | ISerializeable
85
): ISerializedState;
86
87
/**
88
* Type representing JSON-serializable data that may contain binary buffers
89
*/
90
type BufferJSON =
91
| { [property: string]: BufferJSON }
92
| BufferJSON[]
93
| string
94
| number
95
| boolean
96
| null
97
| ArrayBuffer
98
| DataView;
99
100
/**
101
* Result of buffer extraction
102
*/
103
interface ISerializedState {
104
state: JSONObject;
105
buffers: ArrayBuffer[];
106
buffer_paths: (string | number)[][];
107
}
108
```
109
110
**Usage Examples:**
111
112
```typescript
113
// Prepare data with binary content for transmission
114
const stateWithBuffers = {
115
metadata: { name: "image_data", type: "png" },
116
imageData: new Uint8Array([137, 80, 78, 71, /* ... */]),
117
thumbnails: [
118
new Uint8Array([/* thumb1 */]),
119
new Uint8Array([/* thumb2 */])
120
]
121
};
122
123
// Extract buffers for separate transmission
124
const { state, buffer_paths, buffers } = remove_buffers(stateWithBuffers);
125
// state = { metadata: {...}, thumbnails: [null, null] }
126
// buffer_paths = [["imageData"], ["thumbnails", 0], ["thumbnails", 1]]
127
// buffers = [ArrayBuffer, ArrayBuffer, ArrayBuffer]
128
129
// Reconstruct on receiving end
130
const receivedState = { metadata: {...}, thumbnails: [null, null] };
131
put_buffers(receivedState, buffer_paths, buffers);
132
// receivedState now has binary data restored
133
```
134
135
### View Management
136
137
Utility class for managing ordered collections of views with automatic synchronization.
138
139
```typescript { .api }
140
/**
141
* Utility for managing ordered collections of views
142
*/
143
class ViewList<T> {
144
/**
145
* Create a view list with handlers for view creation and removal
146
* @param create_view - Function to create views from models
147
* @param remove_view - Function to clean up views (null for default removal)
148
* @param context - Context for handler function calls
149
*/
150
constructor(
151
create_view: (model: any, index: any) => T | Promise<T>,
152
remove_view: ((view: T) => void) | null,
153
context: any
154
);
155
156
/**
157
* Initialize the view list with handlers
158
* @param create_view - View creation function
159
* @param remove_view - View removal function
160
* @param context - Execution context
161
*/
162
initialize(
163
create_view: (model: any, index: any) => T | Promise<T>,
164
remove_view: ((view: T) => void) | null,
165
context: any
166
): void;
167
168
/**
169
* Update the view collection to match a new model list
170
* @param new_models - Array of models for the views
171
* @param create_view - Optional override for view creation
172
* @param remove_view - Optional override for view removal
173
* @param context - Optional override for execution context
174
* @returns Promise resolving to array of all views
175
*/
176
update(
177
new_models: any[],
178
create_view?: (model: any, index: any) => T | Promise<T>,
179
remove_view?: (view: T) => void,
180
context?: any
181
): Promise<T[]>;
182
183
/**
184
* Remove all views from the list
185
* @returns Promise that resolves when all views are removed
186
*/
187
remove(): Promise<void>;
188
189
/**
190
* Dispose the view list without removing views (synchronous cleanup)
191
*/
192
dispose(): void;
193
194
// Properties
195
views: Promise<T>[];
196
}
197
```
198
199
**Usage Examples:**
200
201
```typescript
202
// Create view list for managing child widgets
203
const createChildView = async (model: WidgetModel, index: number) => {
204
const view = await this.create_child_view(model);
205
this.childContainer.appendChild(view.el);
206
return view;
207
};
208
209
const removeChildView = (view: WidgetView) => {
210
if (view.el.parentNode) {
211
view.el.parentNode.removeChild(view.el);
212
}
213
view.remove();
214
};
215
216
const childViews = new ViewList(createChildView, removeChildView, this);
217
218
// Update views when model list changes
219
this.listenTo(this.model, 'change:children', () => {
220
childViews.update(this.model.get('children'));
221
});
222
223
// Get all current views
224
const allViews = await Promise.all(childViews.views);
225
226
// Clean up when done
227
await childViews.remove();
228
```
229
230
### Promise Utilities
231
232
Helper functions for working with promises in complex data structures.
233
234
```typescript { .api }
235
/**
236
* Resolve all promises in a dictionary object
237
* @param d - Dictionary containing promises or values
238
* @returns Promise resolving to dictionary with resolved values
239
*/
240
function resolvePromisesDict<V>(
241
d: Dict<PromiseLike<V> | V>
242
): Promise<Dict<V>>;
243
244
/**
245
* Create a promise rejection handler with logging
246
* @param message - Error message to log
247
* @param log - Whether to log the error
248
* @returns Function that logs and re-throws errors
249
*/
250
function reject(message: string, log: boolean): (error: Error) => never;
251
252
/**
253
* Simple dictionary type
254
*/
255
type Dict<T> = { [keys: string]: T };
256
```
257
258
**Usage Examples:**
259
260
```typescript
261
// Resolve multiple async operations
262
const operations = {
263
loadData: fetchUserData(),
264
loadConfig: fetchConfiguration(),
265
loadTheme: fetchThemeSettings()
266
};
267
268
const results = await resolvePromisesDict(operations);
269
// results.loadData, results.loadConfig, results.loadTheme are all resolved
270
271
// Create error handler with context
272
const handleModelError = reject('Failed to create model', true);
273
274
try {
275
const model = await createComplexModel();
276
} catch (error) {
277
handleModelError(error); // Logs "Failed to create model" then re-throws
278
}
279
```
280
281
### Data Manipulation
282
283
Utility functions for common data operations.
284
285
```typescript { .api }
286
/**
287
* Find elements in first array that are not in second array
288
* @param a - First array
289
* @param b - Second array
290
* @returns Elements in a but not in b
291
*/
292
function difference(a: string[], b: string[]): string[];
293
294
/**
295
* Deep equality comparison using lodash
296
* @param a - First value to compare
297
* @param b - Second value to compare
298
* @returns True if values are deeply equal
299
*/
300
function isEqual(a: unknown, b: unknown): boolean;
301
302
/**
303
* Object.assign polyfill for older environments
304
* @param target - Target object
305
* @param sources - Source objects to merge
306
* @returns Merged target object
307
*/
308
const assign: (target: any, ...sources: any[]) => any;
309
310
/**
311
* Generate a UUID using Lumino's UUID implementation
312
* @returns UUID string
313
*/
314
function uuid(): string;
315
```
316
317
**Usage Examples:**
318
319
```typescript
320
// Find differences in arrays
321
const oldClasses = ['widget', 'active', 'highlighted'];
322
const newClasses = ['widget', 'inactive'];
323
const toRemove = difference(oldClasses, newClasses); // ['active', 'highlighted']
324
const toAdd = difference(newClasses, oldClasses); // ['inactive']
325
326
// Deep equality checking
327
const state1 = { user: { name: 'Alice', preferences: { theme: 'dark' } } };
328
const state2 = { user: { name: 'Alice', preferences: { theme: 'dark' } } };
329
console.log(isEqual(state1, state2)); // true
330
331
// Safe object merging
332
const defaults = { timeout: 5000, retries: 3 };
333
const userOptions = { timeout: 10000 };
334
const config = assign({}, defaults, userOptions); // { timeout: 10000, retries: 3 }
335
336
// Generate unique identifiers
337
const widgetId = uuid(); // e.g., "f47ac10b-58cc-4372-a567-0e02b2c3d479"
338
```
339
340
### Type Checking Utilities
341
342
Functions for runtime type checking and validation.
343
344
```typescript { .api }
345
/**
346
* Check if an object has a toJSON method
347
* @param object - Object to check
348
* @returns True if object is serializable
349
*/
350
function isSerializable(object: unknown): object is ISerializeable;
351
352
/**
353
* Check if data is a plain object (not array, null, etc.)
354
* @param data - Data to check
355
* @returns True if data is a plain object
356
*/
357
function isObject(data: BufferJSON): data is Dict<BufferJSON>;
358
359
/**
360
* Interface for objects that can be serialized to JSON
361
*/
362
interface ISerializeable {
363
toJSON(options?: {}): JSONObject;
364
}
365
```
366
367
**Usage Examples:**
368
369
```typescript
370
// Check serializability before processing
371
const processData = (data: unknown) => {
372
if (isSerializable(data)) {
373
const jsonData = data.toJSON();
374
// Process as JSON
375
} else if (isObject(data)) {
376
// Process as plain object
377
Object.keys(data).forEach(key => {
378
processData(data[key]);
379
});
380
}
381
};
382
383
// Type-safe object iteration
384
const handleComplexState = (state: BufferJSON) => {
385
if (isObject(state)) {
386
// TypeScript knows state is Dict<BufferJSON>
387
for (const [key, value] of Object.entries(state)) {
388
console.log(`Processing ${key}:`, value);
389
}
390
}
391
};
392
```
393
394
## Advanced Utility Patterns
395
396
### Batch Operations
397
398
```typescript
399
// Batch model operations with error handling
400
const batchUpdateModels = async (updates: Array<{model: WidgetModel, changes: any}>) => {
401
const operations = updates.map(({model, changes}) =>
402
model.set(changes) && model.save_changes()
403
);
404
405
try {
406
await Promise.all(operations);
407
console.log('All models updated successfully');
408
} catch (error) {
409
reject('Batch update failed', true)(error);
410
}
411
};
412
413
// Batch view creation with ViewList
414
const createViewBatch = async (models: WidgetModel[]) => {
415
const viewList = new ViewList(
416
async (model: WidgetModel) => await createWidgetView(model),
417
(view: WidgetView) => view.remove(),
418
this
419
);
420
421
return await viewList.update(models);
422
};
423
```
424
425
### State Synchronization
426
427
```typescript
428
// Synchronize complex nested state with binary data
429
const syncComplexState = async (localState: any, manager: IWidgetManager) => {
430
// Unpack any model references
431
const withModels = await unpack_models(localState, manager);
432
433
// Handle binary data if present
434
if (withModels._buffer_paths && withModels._buffers) {
435
put_buffers(withModels, withModels._buffer_paths, withModels._buffers);
436
delete withModels._buffer_paths;
437
delete withModels._buffers;
438
}
439
440
return withModels;
441
};
442
443
// Prepare state for transmission
444
const prepareStateForSync = (state: any) => {
445
// Pack model instances to references
446
const packedState = pack_models(state);
447
448
// Extract binary buffers
449
const { state: cleanState, buffer_paths, buffers } = remove_buffers(packedState);
450
451
return {
452
state: cleanState,
453
buffer_paths: buffer_paths,
454
buffers: buffers
455
};
456
};
457
```
458
459
### Error Recovery
460
461
```typescript
462
// Robust operation with retries
463
const robustOperation = async <T>(
464
operation: () => Promise<T>,
465
retries = 3,
466
delay = 1000
467
): Promise<T> => {
468
let lastError: Error;
469
470
for (let attempt = 0; attempt <= retries; attempt++) {
471
try {
472
return await operation();
473
} catch (error) {
474
lastError = error as Error;
475
if (attempt < retries) {
476
await new Promise(resolve => setTimeout(resolve, delay * Math.pow(2, attempt)));
477
}
478
}
479
}
480
481
throw reject(`Operation failed after ${retries} attempts`, true)(lastError!);
482
};
483
484
// Safe view cleanup
485
const safeCleanup = async (views: Promise<WidgetView>[]) => {
486
const settledResults = await Promise.allSettled(views);
487
488
settledResults.forEach((result, index) => {
489
if (result.status === 'fulfilled') {
490
try {
491
result.value.remove();
492
} catch (error) {
493
console.warn(`Failed to remove view ${index}:`, error);
494
}
495
} else {
496
console.warn(`View ${index} was rejected:`, result.reason);
497
}
498
});
499
};
500
```
501
502
## Constants and Assets
503
504
### Built-in Assets
505
506
```typescript { .api }
507
/**
508
* SVG icon displayed in error widgets
509
*/
510
const BROKEN_FILE_SVG_ICON: string;
511
```
512
513
**Usage Example:**
514
515
```typescript
516
// Use in custom error display
517
const showErrorIcon = () => {
518
const errorDiv = document.createElement('div');
519
errorDiv.innerHTML = BROKEN_FILE_SVG_ICON;
520
errorDiv.className = 'widget-error-display';
521
return errorDiv;
522
};
523
```