Skip to content

Commit 5757bab

Browse files
authored
feat(core): add add_nodes API for batch node creation (#667)
* feat(core): add add_nodes API for batch node creation\n\n- Introduce add_nodes method to add multiple nodes efficiently in a single layout pass\n- Update legacy bundle accordingly\n- Add example/test_batch_add.html to demonstrate and benchmark batch addition\n\nRefs: performance optimization for bulk node insertion * feat: add add_nodes method for batch node creation - Refactor add_node to use private helper methods _add_node_data and _refresh_node_ui - Add add_nodes method for optimized batch node creation - Improve performance by reducing layout calculations from O(n) to O(1) - Add unit tests for add_nodes functionality - Remove add_nodes from legacy version as suggested in PR feedback Addresses feedback from PR #667: - Split add_node into smaller, reusable methods - Simplify condition checking in add_nodes - Focus only on modern ES6 version in src/ directory - Add comprehensive unit tests * test: add comprehensive unit tests for add_nodes method - Add 10 test cases covering various scenarios: - Error handling (not editable, parent not found, invalid input) - Core functionality (method calls, parameter passing) - Event handling and return values - Failed node creation scenarios - Use mocking to avoid DOM-related issues in test environment - Ensure proper testing of the batch node creation workflow - All tests pass successfully * test: enhance add_nodes unit tests with comprehensive coverage - Add performance test for large-scale node creation (1000 nodes) - Add complex data structure test with Unicode and multilingual support - Add comprehensive direction handling test (13 different direction types) - Refactor create_fake_mind to accept options parameter for better flexibility - Remove default editable:true from create_fake_mind, require explicit declaration - Update all test cases to explicitly pass editable:true when needed - Ensure 100% test pass rate with improved test design and clarity Performance improvements verified: - Batch operations call _refresh_node_ui only once (O(1) vs O(n)) - Large node creation completes within 100ms performance threshold - Complex data structures and Unicode characters handled correctly Total test coverage: 13 test cases covering error handling, core functionality, performance optimization, data integrity, and edge cases.
1 parent a9a5ce8 commit 5757bab

2 files changed

Lines changed: 477 additions & 23 deletions

File tree

src/jsmind.js

Lines changed: 103 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,40 @@ export default class jsMind {
414414
}
415415
return this.mind.get_node(node);
416416
}
417+
/**
418+
* Add node data to the mind map without triggering UI refresh.
419+
* @private
420+
* @param {import('./jsmind.node.js').Node} parent_node
421+
* @param {string} node_id
422+
* @param {string} topic
423+
* @param {Record<string, any>=} data
424+
* @param {('left'|'center'|'right'|'-1'|'0'|'1'|number)=} direction
425+
* @returns {import('./jsmind.node.js').Node|null}
426+
*/
427+
_add_node_data(parent_node, node_id, topic, data, direction) {
428+
var dir = Direction.of(direction);
429+
if (dir === undefined) {
430+
dir = this.layout.calculate_next_child_direction(parent_node);
431+
}
432+
var node = this.mind.add_node(parent_node, node_id, topic, data, dir);
433+
if (!!node) {
434+
this.view.add_node(node);
435+
this.view.reset_node_custom_style(node);
436+
}
437+
return node;
438+
}
439+
440+
/**
441+
* Refresh UI after node changes.
442+
* @private
443+
* @param {import('./jsmind.node.js').Node} parent_node
444+
*/
445+
_refresh_node_ui(parent_node) {
446+
this.layout.layout();
447+
this.view.show(false);
448+
this.expand_node(parent_node);
449+
}
450+
417451
/**
418452
* Add a new node to the mind map.
419453
* @param {string | import('./jsmind.node.js').Node} parent_node
@@ -424,30 +458,78 @@ export default class jsMind {
424458
* @returns {import('./jsmind.node.js').Node|null}
425459
*/
426460
add_node(parent_node, node_id, topic, data, direction) {
427-
if (this.get_editable()) {
428-
var the_parent_node = this.get_node(parent_node);
429-
var dir = Direction.of(direction);
430-
if (dir === undefined) {
431-
dir = this.layout.calculate_next_child_direction(the_parent_node);
432-
}
433-
var node = this.mind.add_node(the_parent_node, node_id, topic, data, dir);
434-
if (!!node) {
435-
this.view.add_node(node);
436-
this.layout.layout();
437-
this.view.show(false);
438-
this.view.reset_node_custom_style(node);
439-
this.expand_node(the_parent_node);
440-
this.invoke_event_handle(EventType.edit, {
441-
evt: 'add_node',
442-
data: [the_parent_node.id, node_id, topic, data, dir],
443-
node: node_id,
444-
});
445-
}
446-
return node;
447-
} else {
461+
if (!this.get_editable()) {
448462
logger.error('fail, this mind map is not editable');
449463
return null;
450464
}
465+
466+
var the_parent_node = this.get_node(parent_node);
467+
if (!the_parent_node) {
468+
logger.error('parent node not found');
469+
return null;
470+
}
471+
472+
var node = this._add_node_data(the_parent_node, node_id, topic, data, direction);
473+
if (!!node) {
474+
this._refresh_node_ui(the_parent_node);
475+
this.invoke_event_handle(EventType.edit, {
476+
evt: 'add_node',
477+
data: [the_parent_node.id, node_id, topic, data, Direction.of(direction)],
478+
node: node_id,
479+
});
480+
}
481+
return node;
482+
}
483+
484+
/**
485+
* Add multiple nodes to the mind map with optimized performance.
486+
* @param {string | import('./jsmind.node.js').Node} parent_node - Parent node for all new nodes
487+
* @param {Array<{node_id: string, topic: string, data?: Record<string, any>, direction?: ('left'|'center'|'right'|'-1'|'0'|'1'|number)}>} nodes_data - Array of node data objects
488+
* @returns {Array<import('./jsmind.node.js').Node|null>} Array of created nodes
489+
*/
490+
add_nodes(parent_node, nodes_data) {
491+
if (!this.get_editable()) {
492+
logger.error('fail, this mind map is not editable');
493+
return [];
494+
}
495+
496+
var the_parent_node = this.get_node(parent_node);
497+
if (!the_parent_node) {
498+
logger.error('parent node not found');
499+
return [];
500+
}
501+
502+
if (!Array.isArray(nodes_data) || nodes_data.length === 0) {
503+
logger.warn('nodes_data should be a non-empty array');
504+
return [];
505+
}
506+
507+
var created_nodes = [];
508+
509+
// Batch create node data without triggering UI refresh
510+
for (var i = 0; i < nodes_data.length; i++) {
511+
var node_data = nodes_data[i];
512+
var node = this._add_node_data(
513+
the_parent_node,
514+
node_data.node_id,
515+
node_data.topic,
516+
node_data.data,
517+
node_data.direction
518+
);
519+
created_nodes.push(node);
520+
}
521+
522+
// Refresh UI once after all nodes are added
523+
if (!!created_nodes.length) {
524+
this._refresh_node_ui(the_parent_node);
525+
this.invoke_event_handle(EventType.edit, {
526+
evt: 'add_nodes',
527+
data: [the_parent_node.id, nodes_data],
528+
nodes: created_nodes.filter(node => node !== null).map(node => node.id),
529+
});
530+
}
531+
532+
return created_nodes;
451533
}
452534
/**
453535
* Insert a node before target node.

0 commit comments

Comments
 (0)