Skip to content

Commit a5c9b48

Browse files
committed
Use instance-local DOM references
1 parent 84130bb commit a5c9b48

9 files changed

Lines changed: 462 additions & 53 deletions

File tree

README.md

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,14 @@ alongside the solver. When you scaffold a new SolverForge project with
4444
```html
4545
<body class="sf-app">
4646
<script>
47+
var tabs = SF.createTabs({
48+
tabs: [
49+
{ id: 'plan', content: '<div>Plan view</div>', active: true },
50+
{ id: 'gantt', content: '<div>Gantt view</div>' },
51+
],
52+
});
53+
document.body.appendChild(tabs.el);
54+
4755
var backend = SF.createBackend({ type: 'axum' });
4856
4957
var header = SF.createHeader({
@@ -54,15 +62,15 @@ alongside the solver. When you scaffold a new SolverForge project with
5462
{ id: 'plan', label: 'Plan', icon: 'fa-list-check', active: true },
5563
{ id: 'gantt', label: 'Gantt', icon: 'fa-chart-gantt' },
5664
],
57-
onTabChange: function (id) { SF.showTab(id); },
65+
onTabChange: function (id) { tabs.show(id); },
5866
actions: {
5967
onSolve: function () { solver.start(); },
6068
onStop: function () { solver.stop(); },
6169
},
6270
});
6371
document.body.prepend(header);
6472
65-
var bar = SF.createStatusBar({ constraints: myConstraints });
73+
var bar = SF.createStatusBar({ header: header, constraints: myConstraints });
6674
header.after(bar.el);
6775
6876
var solver = SF.createSolver({
@@ -81,16 +89,16 @@ alongside the solver. When you scaffold a new SolverForge project with
8189
| Factory | Returns | Description |
8290
|---------|---------|-------------|
8391
| `SF.createHeader(config)` | `HTMLElement` | Sticky header with logo, title, nav tabs, solve/stop/analyze buttons |
84-
| `SF.createStatusBar(config)` | `{el, updateScore, setSolving, updateMoves, colorDotsFromAnalysis}` | Score display + constraint dot indicators |
92+
| `SF.createStatusBar(config)` | `{el, bindHeader, updateScore, setSolving, updateMoves, colorDotsFromAnalysis}` | Score display + constraint dot indicators, with optional header binding for local solve/stop controls |
8593
| `SF.createButton(config)` | `HTMLButtonElement` | Button with variant/size/icon/shape modifiers |
8694
| `SF.createModal(config)` | `{el, body, open, close, setBody}` | Dialog with emerald gradient header, backdrop, Escape key |
8795
| `SF.createTable(config)` | `HTMLElement` | Data table with headers and row click |
88-
| `SF.createTabs(config)` | `{el, show}` | Tab panel container |
96+
| `SF.createTabs(config)` | `{el, show}` | Tab panel container with instance-scoped tab switching |
8997
| `SF.createFooter(config)` | `HTMLElement` | Footer with links and version |
9098
| `SF.createApiGuide(config)` | `HTMLElement` | REST API documentation panel |
9199
| `SF.showToast(config)` | `void` | Toast notification (auto-dismiss) |
92100
| `SF.showError(title, detail)` | `void` | Danger toast shorthand |
93-
| `SF.showTab(tabId)` | `void` | Activate a tab panel by ID |
101+
| `SF.showTab(tabId, root?)` | `void` | Activate a tab panel within an optional root container |
94102

95103
### Timeline Rail
96104

js-src/00-core.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ const SF = (function () {
66
'use strict';
77

88
const sf = { version: '0.1.0' };
9+
var uidCounter = 0;
910

1011
/* ── Utilities ── */
1112

@@ -41,6 +42,11 @@ const SF = (function () {
4142
return el;
4243
};
4344

45+
sf.uid = function (prefix) {
46+
uidCounter += 1;
47+
return (prefix || 'sf') + '-' + uidCounter;
48+
};
49+
4450
if (typeof window !== 'undefined') window.SF = sf;
4551
return sf;
4652
})();

js-src/04-header.js

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,14 @@
77

88
sf.createHeader = function (config) {
99
var header = sf.el('header', { className: 'sf-header' });
10+
var controls = {
11+
actions: null,
12+
spinner: null,
13+
solveBtn: null,
14+
stopBtn: null,
15+
analyzeBtn: null,
16+
nav: null,
17+
};
1018

1119
// Logo
1220
if (config.logo) {
@@ -31,6 +39,7 @@
3139
// Nav tabs
3240
if (config.tabs && config.tabs.length > 0) {
3341
var nav = sf.el('nav', { className: 'sf-header-nav' });
42+
controls.nav = nav;
3443
config.tabs.forEach(function (tab) {
3544
var btn = sf.el('button', {
3645
className: 'sf-nav-btn' + (tab.active ? ' active' : ''),
@@ -53,19 +62,21 @@
5362
// Action buttons
5463
if (config.actions) {
5564
var actions = sf.el('div', { className: 'sf-header-actions' });
65+
controls.actions = actions;
5666

5767
// Spinner
58-
var spinner = sf.el('div', { className: 'sf-solving-spinner', id: 'sfSolvingSpinner' });
68+
var spinner = sf.el('div', { className: 'sf-solving-spinner' });
69+
controls.spinner = spinner;
5970
actions.appendChild(spinner);
6071

6172
if (config.actions.onSolve) {
6273
var solveBtn = sf.createButton({
6374
text: 'Solve',
6475
variant: 'success',
6576
icon: 'fa-play',
66-
id: 'sfSolveBtn',
6777
onClick: config.actions.onSolve,
6878
});
79+
controls.solveBtn = solveBtn;
6980
actions.appendChild(solveBtn);
7081
}
7182

@@ -74,10 +85,10 @@
7485
text: 'Stop',
7586
variant: 'danger',
7687
icon: 'fa-stop',
77-
id: 'sfStopBtn',
7888
onClick: config.actions.onStop,
7989
});
8090
stopBtn.style.display = 'none';
91+
controls.stopBtn = stopBtn;
8192
actions.appendChild(stopBtn);
8293
}
8394

@@ -86,16 +97,17 @@
8697
variant: 'ghost',
8798
icon: 'fa-chart-bar',
8899
circle: true,
89-
id: 'sfAnalyzeBtn',
90100
tooltip: 'Score Analysis',
91101
onClick: config.actions.onAnalyze,
92102
});
103+
controls.analyzeBtn = analyzeBtn;
93104
actions.appendChild(analyzeBtn);
94105
}
95106

96107
header.appendChild(actions);
97108
}
98109

110+
header.sfControls = controls;
99111
return header;
100112
};
101113

js-src/05-statusbar.js

Lines changed: 20 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -8,30 +8,31 @@
88
sf.createStatusBar = function (config) {
99
var bar = sf.el('div', { className: 'sf-statusbar' });
1010
var lastScore = null;
11+
var controls = null;
1112

1213
// Score display
13-
var scoreEl = sf.el('span', { className: 'sf-statusbar-score', id: 'sfScoreDisplay' }, '\u2014');
14+
var scoreEl = sf.el('span', { className: 'sf-statusbar-score' }, '\u2014');
1415
bar.appendChild(scoreEl);
1516

1617
// Separator
1718
bar.appendChild(sf.el('span', { className: 'sf-statusbar-sep' }, '|'));
1819

1920
// Constraint dots container
20-
var dotsContainer = sf.el('div', { className: 'sf-statusbar-constraints', id: 'sfConstraintDots' });
21+
var dotsContainer = sf.el('div', { className: 'sf-statusbar-constraints' });
2122
bar.appendChild(dotsContainer);
2223

2324
// Separator + moves display
24-
var movesSep = sf.el('span', { className: 'sf-statusbar-sep', id: 'sfMovesSep' }, '|');
25+
var movesSep = sf.el('span', { className: 'sf-statusbar-sep' }, '|');
2526
movesSep.style.display = 'none';
2627
bar.appendChild(movesSep);
2728

28-
var movesEl = sf.el('span', { id: 'sfMovesDisplay' });
29+
var movesEl = sf.el('span');
2930
movesEl.style.display = 'none';
3031
bar.appendChild(movesEl);
3132

3233
// Separator + status text
3334
bar.appendChild(sf.el('span', { className: 'sf-statusbar-sep' }, '|'));
34-
var statusEl = sf.el('span', { id: 'sfStatusText' });
35+
var statusEl = sf.el('span');
3536
bar.appendChild(statusEl);
3637

3738
// Build initial constraint dots
@@ -41,6 +42,11 @@
4142

4243
var api = { el: bar };
4344

45+
api.bindHeader = function (header) {
46+
controls = header && header.sfControls ? header.sfControls : null;
47+
return api;
48+
};
49+
4450
api.updateScore = function (scoreStr) {
4551
if (scoreStr && scoreStr !== lastScore) {
4652
scoreEl.textContent = scoreStr;
@@ -57,9 +63,9 @@
5763
};
5864

5965
api.setSolving = function (solving) {
60-
var solveBtn = document.getElementById('sfSolveBtn');
61-
var stopBtn = document.getElementById('sfStopBtn');
62-
var spinner = document.getElementById('sfSolvingSpinner');
66+
var solveBtn = controls && controls.solveBtn;
67+
var stopBtn = controls && controls.stopBtn;
68+
var spinner = controls && controls.spinner;
6369

6470
if (solveBtn) solveBtn.style.display = solving ? 'none' : '';
6571
if (stopBtn) stopBtn.style.display = solving ? '' : 'none';
@@ -99,8 +105,8 @@
99105
api.colorDotsFromAnalysis = function (constraints) {
100106
if (!constraints || constraints.length === 0) return;
101107
buildDots(dotsContainer, constraints, config && config.onConstraintClick);
102-
constraints.forEach(function (c, i) {
103-
var dot = document.getElementById('sf-cdot-' + i);
108+
dotsContainer.querySelectorAll('.sf-constraint-dot').forEach(function (dot, i) {
109+
var c = constraints[i];
104110
if (!dot) return;
105111
var isHard = c.type === 'hard';
106112
var scoreVal = isHard ? sf.score.parseHard(c.score) : sf.score.parseSoft(c.score);
@@ -110,6 +116,10 @@
110116
});
111117
};
112118

119+
if (config && config.header) {
120+
api.bindHeader(config.header);
121+
}
122+
113123
return api;
114124
};
115125

@@ -119,7 +129,6 @@
119129
constraints.forEach(function (c, i) {
120130
var dot = sf.el('div', {
121131
className: 'sf-constraint-dot',
122-
id: 'sf-cdot-' + i,
123132
title: c.name || ('Constraint ' + i),
124133
dataset: { type: c.type || 'hard', index: String(i) },
125134
});

js-src/07-tabs.js

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,21 +5,24 @@
55
(function (sf) {
66
'use strict';
77

8-
sf.showTab = function (tabId) {
9-
document.querySelectorAll('.sf-tab-panel').forEach(function (p) {
8+
sf.showTab = function (tabId, root) {
9+
var scope = root || document;
10+
scope.querySelectorAll('.sf-tab-panel').forEach(function (p) {
1011
p.classList.remove('active');
1112
});
12-
var panel = document.getElementById('sf-tab-' + tabId);
13+
var panel = scope.querySelector('[data-tab-id="' + tabId + '"]');
1314
if (panel) panel.classList.add('active');
1415
};
1516

1617
sf.createTabs = function (config) {
1718
var container = sf.el('div', { className: 'sf-tabs-container' });
19+
var tabsId = sf.uid('sf-tabs');
1820

1921
config.tabs.forEach(function (tab) {
2022
var panel = sf.el('div', {
2123
className: 'sf-tab-panel' + (tab.active ? ' active' : ''),
22-
id: 'sf-tab-' + tab.id,
24+
id: tabsId + '-' + tab.id,
25+
dataset: { tabId: tab.id },
2326
});
2427
if (tab.content) {
2528
if (typeof tab.content === 'string') panel.innerHTML = tab.content;
@@ -28,7 +31,12 @@
2831
container.appendChild(panel);
2932
});
3033

31-
return { el: container, show: sf.showTab };
34+
return {
35+
el: container,
36+
show: function (tabId) {
37+
sf.showTab(tabId, container);
38+
},
39+
};
3240
};
3341

3442
})(SF);

js-src/14-gantt.js

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@
99
sf.gantt = {};
1010

1111
sf.gantt.create = function (config) {
12-
var chartPaneId = config.chartPane || 'sf-gantt-chart-pane';
13-
var gridPaneId = config.gridPane || 'sf-gantt-grid-pane';
14-
var chartContainerId = config.chartContainer || 'sf-gantt-container';
15-
var svgId = config.svgId || 'sf-gantt-svg';
12+
var instanceId = sf.uid('sf-gantt');
13+
var chartPaneId = config.chartPane || (instanceId + '-chart-pane');
14+
var gridPaneId = config.gridPane || (instanceId + '-grid-pane');
15+
var chartContainerId = config.chartContainer || (instanceId + '-container');
16+
var svgId = config.svgId || (instanceId + '-svg');
1617
var ganttChart = null;
1718
var splitInstance = null;
1819
var tasks = [];

0 commit comments

Comments
 (0)