asjs-express 1.4.1 → 1.5.0

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": "asjs-express",
3
- "version": "1.4.1",
3
+ "version": "1.5.0",
4
4
  "description": "Lightweight Express view engine with EJS-like templates, layouts, async page rendering, form enhancement, and a built-in client router.",
5
5
  "main": "index.js",
6
6
  "bin": {
@@ -4,26 +4,128 @@ const { setupAsjs } = require('asjs-express');
4
4
  const app = express();
5
5
  const port = process.env.PORT || __PORT__;
6
6
 
7
+ app.use(express.urlencoded({ extended: false }));
8
+
7
9
  const asjs = setupAsjs(app, {
8
10
  rootDir: __dirname,
9
11
  defaultLayout: 'layouts/main',
10
12
  navItems: [
11
- { href: '/', label: 'Home', activeMode: 'exact' }
13
+ { href: '/', label: 'Home', activeMode: 'exact' },
14
+ { href: '/about', label: 'About', activeMode: 'exact' },
15
+ { href: '/contact', label: 'Contact', activeMode: 'exact' }
12
16
  ],
17
+ brand: {
18
+ href: '/',
19
+ mark: '__APP_INITIALS__',
20
+ name: '__APP_TITLE__',
21
+ tagline: 'Built with ASJS + Express'
22
+ },
13
23
  transitions: 'fade',
14
24
  prefetch: true,
15
25
  loadingBar: true
16
26
  });
17
27
 
28
+ // Home
18
29
  app.get('/', asjs.page('home', {
19
30
  title: '__APP_TITLE__',
20
- headline: '__APP_TITLE__ is ready.',
21
- description: 'Header, router, loading bar, and SPA-ready page transitions are already connected.',
22
- nextStep: 'Add a second route whenever you need it. ASJS will keep the same internal navigation flow.'
31
+ headline: 'Welcome to __APP_TITLE__',
32
+ description: 'A three-page starter built with ASJS and Express. SPA navigation, smooth transitions, and server-rendered pages are already set up.',
33
+ features: [
34
+ {
35
+ label: 'SPA Navigation',
36
+ text: 'Pages swap without a full reload. The header and layout stay in place while only the content area changes.'
37
+ },
38
+ {
39
+ label: 'Server Rendering',
40
+ text: 'Each page is fully rendered on the server. Route data is prepared in the callback before the HTML response is sent.'
41
+ },
42
+ {
43
+ label: 'Form Handling',
44
+ text: 'Forms submit to the server and return a new rendered state. Validation errors are shown without losing the page shell.'
45
+ }
46
+ ]
47
+ }));
48
+
49
+ // About
50
+ app.get('/about', asjs.page('about', {
51
+ title: 'About — __APP_TITLE__',
52
+ headline: 'About this project',
53
+ description: 'This app is built with ASJS on top of Express. ASJS adds layouts, async page models, client-side transitions, and form helpers without a front-end build step.',
54
+ facts: [
55
+ { label: 'Engine', value: 'ASJS __ASJS_VERSION__' },
56
+ { label: 'Framework', value: 'Express' },
57
+ { label: 'Rendering', value: 'Server-first' },
58
+ { label: 'Templates', value: 'EJS syntax' }
59
+ ],
60
+ capabilities: [
61
+ 'Shared layouts with a consistent header and navigation across all pages',
62
+ 'SPA navigation with smooth page transitions and a top loading bar',
63
+ 'Async route callbacks that prepare data before the HTML is rendered',
64
+ 'Form progressive enhancement with server-side validation',
65
+ 'Plugin and hook API for attaching middleware and shared services'
66
+ ]
67
+ }));
68
+
69
+ // Contact — GET
70
+ const contactInitialState = {
71
+ title: 'Contact — __APP_TITLE__',
72
+ headline: 'Get in touch',
73
+ description: 'Send a message using the form below. This form is handled on the server and returns a validation state or a success state.',
74
+ formValues: { name: '', email: '', message: '' },
75
+ formErrors: {},
76
+ submitted: false
77
+ };
78
+
79
+ app.get('/contact', asjs.page('contact', contactInitialState));
80
+
81
+ // Contact — POST
82
+ app.post('/contact', asjs.createPageRoute('contact', {}, async (req) => {
83
+ const formValues = asjs.normalizeFields(req.body, ['name', 'email', 'message']);
84
+ const formErrors = asjs.validateFields(formValues, {
85
+ name: {
86
+ required: 'Please enter your name.'
87
+ },
88
+ email: {
89
+ required: 'Please enter your email address.',
90
+ pattern: {
91
+ value: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
92
+ message: 'Please enter a valid email address.'
93
+ }
94
+ },
95
+ message: {
96
+ required: 'Please write a message.',
97
+ minLength: {
98
+ value: 10,
99
+ message: 'Please write at least 10 characters.'
100
+ }
101
+ }
102
+ });
103
+
104
+ if (asjs.hasValidationErrors(formErrors)) {
105
+ return {
106
+ status: 422,
107
+ title: 'Contact — __APP_TITLE__',
108
+ headline: 'Get in touch',
109
+ description: 'A few fields need attention before the form can be sent.',
110
+ formValues,
111
+ formErrors,
112
+ submitted: false
113
+ };
114
+ }
115
+
116
+ return {
117
+ title: 'Message sent — __APP_TITLE__',
118
+ headline: 'Message received',
119
+ description: 'Your message has been received. This is a starter demo — no real email was sent.',
120
+ formValues: contactInitialState.formValues,
121
+ formErrors: {},
122
+ submitted: true,
123
+ sentValues: formValues
124
+ };
23
125
  }));
24
126
 
25
127
  app.use(asjs.errors());
26
128
 
27
129
  app.listen(port, () => {
28
- console.log(`__APP_TITLE__ running at http://localhost:${port}`);
130
+ console.log('__APP_TITLE__ is running at http://localhost:' + port);
29
131
  });
@@ -0,0 +1,28 @@
1
+ <div class="page-intro">
2
+ <span class="section-label">About</span>
3
+ <h1><%= headline %></h1>
4
+ <p><%= description %></p>
5
+ </div>
6
+
7
+ <div class="render-summary-grid render-summary-grid--wide">
8
+ <% facts.forEach(function(fact) { %>
9
+ <div class="render-item">
10
+ <span><%= fact.label %></span>
11
+ <strong><%= fact.value %></strong>
12
+ </div>
13
+ <% }) %>
14
+ </div>
15
+
16
+ <div class="section-card">
17
+ <span class="section-label">Capabilities</span>
18
+ <h2>What ASJS provides</h2>
19
+ <ul class="check-list">
20
+ <% capabilities.forEach(function(cap) { %>
21
+ <li><%= cap %></li>
22
+ <% }) %>
23
+ </ul>
24
+ <div class="hero-actions" style="margin-top: 24px;">
25
+ <a href="/" class="button button-primary" data-asjs-transition="fade">Back to home</a>
26
+ <a href="/contact" class="button button-secondary" data-asjs-transition="fade">Contact</a>
27
+ </div>
28
+ </div>
@@ -0,0 +1,53 @@
1
+ <% if (locals.submitted) { %>
2
+ <div class="section-card status-panel--success" style="margin-top: 0;">
3
+ <span class="section-label" style="background: #dcfce7; color: #15803d;">Sent</span>
4
+ <h2 style="margin-top: 14px; letter-spacing: -0.04em;"><%= headline %></h2>
5
+ <p><%= description %></p>
6
+ <div class="render-summary-grid" style="margin-top: 20px;">
7
+ <div class="render-item">
8
+ <span>Name</span>
9
+ <strong><%= sentValues.name %></strong>
10
+ </div>
11
+ <div class="render-item">
12
+ <span>Email</span>
13
+ <strong><%= sentValues.email %></strong>
14
+ </div>
15
+ </div>
16
+ <div class="hero-actions" style="margin-top: 24px;">
17
+ <a href="/" class="button button-primary" data-asjs-transition="fade">Back to home</a>
18
+ <a href="/contact" class="button button-secondary" data-asjs-transition="fade">Send another</a>
19
+ </div>
20
+ </div>
21
+ <% } else { %>
22
+ <div class="page-intro">
23
+ <span class="section-label">Contact</span>
24
+ <h1><%= headline %></h1>
25
+ <p><%= description %></p>
26
+ </div>
27
+
28
+ <div class="section-card form-panel">
29
+ <form class="webas-form" method="POST" action="/contact">
30
+ <div class="field-grid">
31
+ <div class="field-group">
32
+ <span>Name</span>
33
+ <input type="text" name="name" value="<%= formValues.name %>" placeholder="Your name" autocomplete="name">
34
+ <% if (formErrors.name) { %><span class="field-error"><%= formErrors.name %></span><% } %>
35
+ </div>
36
+ <div class="field-group">
37
+ <span>Email</span>
38
+ <input type="email" name="email" value="<%= formValues.email %>" placeholder="you@example.com" autocomplete="email">
39
+ <% if (formErrors.email) { %><span class="field-error"><%= formErrors.email %></span><% } %>
40
+ </div>
41
+ </div>
42
+ <div class="field-group">
43
+ <span>Message</span>
44
+ <textarea name="message" placeholder="Your message (at least 10 characters)"><%= formValues.message %></textarea>
45
+ <% if (formErrors.message) { %><span class="field-error"><%= formErrors.message %></span><% } %>
46
+ </div>
47
+ <div class="form-actions">
48
+ <button type="submit" class="button button-primary">Send message</button>
49
+ <span>The form is validated on the server before the page re-renders.</span>
50
+ </div>
51
+ </form>
52
+ </div>
53
+ <% } %>
@@ -1,5 +1,33 @@
1
- <section style="max-width: 720px; margin: 40px auto; padding: 24px;">
1
+ <div class="page-intro">
2
+ <span class="section-label">Starter</span>
2
3
  <h1><%= headline %></h1>
3
4
  <p><%= description %></p>
4
- <p><%= nextStep %></p>
5
- </section>
5
+ <div class="hero-actions">
6
+ <a href="/about" class="button button-primary" data-asjs-transition="fade">Learn more</a>
7
+ <a href="/contact" class="button button-secondary" data-asjs-transition="fade">Get in touch</a>
8
+ </div>
9
+ </div>
10
+
11
+ <div class="section-card">
12
+ <span class="section-label">What's included</span>
13
+ <h2>Built-in from the start</h2>
14
+ <div class="card-grid" style="margin-top: 16px;">
15
+ <% features.forEach(function(feature) { %>
16
+ <div class="info-card">
17
+ <h3><%= feature.label %></h3>
18
+ <p><%= feature.text %></p>
19
+ </div>
20
+ <% }) %>
21
+ </div>
22
+ </div>
23
+
24
+ <div class="section-card code-panel">
25
+ <span class="section-label">Quick start</span>
26
+ <h2>Add a new page in three lines</h2>
27
+ <p>Open <code>app.js</code> and add a route. ASJS handles the layout, navigation state, and transitions automatically.</p>
28
+ <pre class="code-block">app.get('/new-page', asjs.page('new-page', {
29
+ title: 'New page',
30
+ headline: 'Hello from a new page'
31
+ }));</pre>
32
+ <p style="margin-top: 14px;">Then create <code>views/new-page.asjs</code> with your HTML. The layout, header, and navigation are inherited.</p>
33
+ </div>
@@ -1,5 +1,5 @@
1
1
  <!DOCTYPE html>
2
- <html>
2
+ <html lang="en">
3
3
  <head>
4
4
  <meta charset="UTF-8">
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
@@ -8,9 +8,15 @@
8
8
  </head>
9
9
  <body<%- asjs.bodyAttrs() %>>
10
10
  <%- asjs.progressMarkup() %>
11
- <%- asjs.header() %>
12
- <main class="view-frame"<%- asjs.viewAttrs() %>>
13
- <%- body %>
14
- </main>
11
+ <div class="page-shell">
12
+ <%- asjs.header() %>
13
+ <main class="view-frame"<%- asjs.viewAttrs() %>>
14
+ <%- body %>
15
+ </main>
16
+ <footer class="footer">
17
+ <span>Built with <strong>ASJS __ASJS_VERSION__</strong> + Express</span>
18
+ <span>&copy; __YEAR__ __APP_TITLE__</span>
19
+ </footer>
20
+ </div>
15
21
  </body>
16
22
  </html>