feat: add struts learning tracker
This commit is contained in:
111
web/index.jsp
111
web/index.jsp
@@ -199,6 +199,28 @@
|
||||
color: var(--muted);
|
||||
text-align: center;
|
||||
}
|
||||
.tracker {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
margin-top: 14px;
|
||||
}
|
||||
.tracker-item {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: flex-start;
|
||||
padding: 12px 14px;
|
||||
border-radius: 16px;
|
||||
border: 1px solid var(--line);
|
||||
background: rgba(255,255,255,0.5);
|
||||
}
|
||||
.tracker-item input {
|
||||
margin-top: 3px;
|
||||
}
|
||||
.tracker-item strong {
|
||||
display: block;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
@media (max-width: 1100px) {
|
||||
.layout { grid-template-columns: 1fr; }
|
||||
.grid, .pipeline, .metrics { grid-template-columns: 1fr 1fr; }
|
||||
@@ -229,6 +251,8 @@
|
||||
<div class="metric"><span>Guide pages</span><strong>4</strong></div>
|
||||
<div class="metric"><span>Demo forms</span><strong>3</strong></div>
|
||||
<div class="metric"><span>JSON endpoints</span><strong>2</strong></div>
|
||||
<div class="metric"><span>Core lab track</span><strong>4</strong></div>
|
||||
<div class="metric"><span>Completed labs</span><strong id="completedLabs">0</strong></div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -257,9 +281,67 @@
|
||||
<a class="link-btn" href="api/users">JSON endpoint</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<div class="eyebrow">Lab tracker</div>
|
||||
<h2>Mark the demos you have finished</h2>
|
||||
<div class="tracker">
|
||||
<label class="tracker-item">
|
||||
<input type="checkbox" data-track="hello">
|
||||
<span>
|
||||
<strong>Hello action</strong>
|
||||
See the smallest request -> action -> result path.
|
||||
</span>
|
||||
</label>
|
||||
<label class="tracker-item">
|
||||
<input type="checkbox" data-track="login">
|
||||
<span>
|
||||
<strong>Login flow</strong>
|
||||
Follow validation, action errors, and success routing.
|
||||
</span>
|
||||
</label>
|
||||
<label class="tracker-item">
|
||||
<input type="checkbox" data-track="validation">
|
||||
<span>
|
||||
<strong>Validation flow</strong>
|
||||
Compare invalid and valid submissions.
|
||||
</span>
|
||||
</label>
|
||||
<label class="tracker-item">
|
||||
<input type="checkbox" data-track="json">
|
||||
<span>
|
||||
<strong>JSON and AJAX</strong>
|
||||
Finish by explaining how Struts can return API-style payloads.
|
||||
</span>
|
||||
</label>
|
||||
</div>
|
||||
</section>
|
||||
</aside>
|
||||
|
||||
<main class="content">
|
||||
<section class="card">
|
||||
<div class="eyebrow">Request lifecycle</div>
|
||||
<h2>How to explain Struts2 with one mental model</h2>
|
||||
<div class="pipeline">
|
||||
<div class="step">
|
||||
<strong>1. Request enters</strong>
|
||||
<p>The browser sends a URL and optional form fields or query params.</p>
|
||||
</div>
|
||||
<div class="step">
|
||||
<strong>2. Action mapping</strong>
|
||||
<p><code>struts.xml</code> resolves the action name and target class.</p>
|
||||
</div>
|
||||
<div class="step">
|
||||
<strong>3. Parameter binding</strong>
|
||||
<p>Struts populates action properties before <code>execute()</code> runs.</p>
|
||||
</div>
|
||||
<div class="step">
|
||||
<strong>4. Result routing</strong>
|
||||
<p>The returned result name decides which JSP or JSON renderer will respond.</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="card">
|
||||
<div class="eyebrow">Catalog</div>
|
||||
<h2>Interactive demos and guides</h2>
|
||||
@@ -385,6 +467,33 @@
|
||||
const searchInput = document.getElementById('searchInput');
|
||||
const cards = Array.from(document.querySelectorAll('.demo-card'));
|
||||
const emptyState = document.getElementById('emptyState');
|
||||
const trackerItems = Array.from(document.querySelectorAll('[data-track]'));
|
||||
const trackerKey = 'struts_demo_tracker';
|
||||
|
||||
function getTrackerState() {
|
||||
return JSON.parse(localStorage.getItem(trackerKey) || '{}');
|
||||
}
|
||||
|
||||
function renderTracker() {
|
||||
const state = getTrackerState();
|
||||
let completed = 0;
|
||||
trackerItems.forEach((item) => {
|
||||
item.checked = Boolean(state[item.dataset.track]);
|
||||
if (item.checked) {
|
||||
completed += 1;
|
||||
}
|
||||
});
|
||||
document.getElementById('completedLabs').textContent = completed;
|
||||
}
|
||||
|
||||
trackerItems.forEach((item) => {
|
||||
item.addEventListener('change', () => {
|
||||
const state = getTrackerState();
|
||||
state[item.dataset.track] = item.checked;
|
||||
localStorage.setItem(trackerKey, JSON.stringify(state));
|
||||
renderTracker();
|
||||
});
|
||||
});
|
||||
|
||||
searchInput.addEventListener('input', () => {
|
||||
const keyword = searchInput.value.trim().toLowerCase();
|
||||
@@ -398,6 +507,8 @@
|
||||
});
|
||||
emptyState.style.display = visible ? 'none' : 'block';
|
||||
});
|
||||
|
||||
renderTracker();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Reference in New Issue
Block a user