zero-query 0.3.1 โ 0.5.2
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/README.md +48 -19
- package/cli/args.js +33 -0
- package/cli/commands/build.js +58 -0
- package/cli/commands/bundle.js +687 -0
- package/cli/commands/create.js +67 -0
- package/cli/commands/dev.js +520 -0
- package/cli/help.js +104 -0
- package/cli/index.js +53 -0
- package/cli/scaffold/LICENSE +21 -0
- package/cli/scaffold/index.html +62 -0
- package/cli/scaffold/scripts/app.js +101 -0
- package/cli/scaffold/scripts/components/about.js +119 -0
- package/cli/scaffold/scripts/components/api-demo.js +103 -0
- package/cli/scaffold/scripts/components/contacts/contacts.css +253 -0
- package/cli/scaffold/scripts/components/contacts/contacts.html +139 -0
- package/cli/scaffold/scripts/components/contacts/contacts.js +137 -0
- package/cli/scaffold/scripts/components/counter.js +65 -0
- package/cli/scaffold/scripts/components/home.js +137 -0
- package/cli/scaffold/scripts/components/not-found.js +16 -0
- package/cli/scaffold/scripts/components/todos.js +130 -0
- package/cli/scaffold/scripts/routes.js +13 -0
- package/cli/scaffold/scripts/store.js +96 -0
- package/cli/scaffold/styles/styles.css +556 -0
- package/cli/utils.js +122 -0
- package/dist/zquery.dist.zip +0 -0
- package/dist/zquery.js +448 -62
- package/dist/zquery.min.js +5 -5
- package/index.d.ts +213 -66
- package/index.js +14 -7
- package/package.json +8 -8
- package/src/component.js +408 -52
- package/src/core.js +26 -3
- package/src/router.js +2 -2
- package/cli.js +0 -1208
package/cli/index.js
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* zQuery CLI โ entry point
|
|
5
|
+
*
|
|
6
|
+
* Dispatches to the appropriate command module under cli/commands/.
|
|
7
|
+
* Run `zquery --help` for full usage information.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
const { join } = require('path');
|
|
11
|
+
const { watch } = require('fs');
|
|
12
|
+
|
|
13
|
+
const { args, flag } = require('./args');
|
|
14
|
+
const buildLibrary = require('./commands/build');
|
|
15
|
+
const bundleApp = require('./commands/bundle');
|
|
16
|
+
const devServer = require('./commands/dev');
|
|
17
|
+
const createProject = require('./commands/create');
|
|
18
|
+
const showHelp = require('./help');
|
|
19
|
+
|
|
20
|
+
const command = args[0];
|
|
21
|
+
|
|
22
|
+
if (!command || command === '--help' || command === '-h' || command === 'help') {
|
|
23
|
+
showHelp();
|
|
24
|
+
} else if (command === 'create') {
|
|
25
|
+
createProject(args);
|
|
26
|
+
} else if (command === 'build') {
|
|
27
|
+
console.log('\n zQuery Library Build\n');
|
|
28
|
+
buildLibrary();
|
|
29
|
+
|
|
30
|
+
if (flag('watch', 'w')) {
|
|
31
|
+
console.log(' Watching src/ for changes...\n');
|
|
32
|
+
const srcDir = join(process.cwd(), 'src');
|
|
33
|
+
let debounce;
|
|
34
|
+
|
|
35
|
+
function rebuild() {
|
|
36
|
+
clearTimeout(debounce);
|
|
37
|
+
debounce = setTimeout(() => {
|
|
38
|
+
console.log(' Rebuilding...\n');
|
|
39
|
+
try { buildLibrary(); } catch (e) { console.error(e.message); }
|
|
40
|
+
}, 200);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
watch(srcDir, { recursive: true }, rebuild);
|
|
44
|
+
watch(join(process.cwd(), 'index.js'), rebuild);
|
|
45
|
+
}
|
|
46
|
+
} else if (command === 'bundle') {
|
|
47
|
+
bundleApp();
|
|
48
|
+
} else if (command === 'dev') {
|
|
49
|
+
devServer();
|
|
50
|
+
} else {
|
|
51
|
+
console.error(`\n Unknown command: ${command}\n Run "zquery --help" for usage.\n`);
|
|
52
|
+
process.exit(1);
|
|
53
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Tony Wiedman "molex"
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>{{NAME}}</title>
|
|
7
|
+
<base href="/">
|
|
8
|
+
<link rel="stylesheet" href="styles/styles.css">
|
|
9
|
+
<script src="scripts/vendor/zquery.min.js"></script>
|
|
10
|
+
<script type="module" src="scripts/app.js"></script>
|
|
11
|
+
</head>
|
|
12
|
+
<body>
|
|
13
|
+
|
|
14
|
+
<!-- Sidebar Navigation -->
|
|
15
|
+
<aside class="sidebar" id="sidebar">
|
|
16
|
+
<div class="sidebar-header">
|
|
17
|
+
<span class="brand">โก {{NAME}}</span>
|
|
18
|
+
</div>
|
|
19
|
+
<nav class="sidebar-nav">
|
|
20
|
+
<a z-link="/" class="nav-link">
|
|
21
|
+
<span class="nav-icon">๐ </span> Home
|
|
22
|
+
</a>
|
|
23
|
+
<a z-link="/counter" class="nav-link">
|
|
24
|
+
<span class="nav-icon">๐ข</span> Counter
|
|
25
|
+
</a>
|
|
26
|
+
<a z-link="/todos" class="nav-link">
|
|
27
|
+
<span class="nav-icon">โ
</span> Todos
|
|
28
|
+
</a>
|
|
29
|
+
<a z-link="/contacts" class="nav-link">
|
|
30
|
+
<span class="nav-icon">๐</span> Contacts
|
|
31
|
+
</a>
|
|
32
|
+
<a z-link="/api" class="nav-link">
|
|
33
|
+
<span class="nav-icon">๐</span> API Demo
|
|
34
|
+
</a>
|
|
35
|
+
<a z-link="/about" class="nav-link">
|
|
36
|
+
<span class="nav-icon">๐ก</span> About
|
|
37
|
+
</a>
|
|
38
|
+
</nav>
|
|
39
|
+
<div class="sidebar-footer">
|
|
40
|
+
<small>zQuery <span id="nav-version"></span></small>
|
|
41
|
+
</div>
|
|
42
|
+
</aside>
|
|
43
|
+
|
|
44
|
+
<!-- Mobile Top Bar -->
|
|
45
|
+
<header class="topbar" id="topbar">
|
|
46
|
+
<button class="hamburger" id="menu-toggle" aria-label="Toggle menu">
|
|
47
|
+
<span></span><span></span><span></span>
|
|
48
|
+
</button>
|
|
49
|
+
<span class="topbar-brand">โก {{NAME}}</span>
|
|
50
|
+
</header>
|
|
51
|
+
|
|
52
|
+
<!-- Overlay for mobile menu -->
|
|
53
|
+
<div class="overlay" id="overlay"></div>
|
|
54
|
+
|
|
55
|
+
<!-- Main Content -->
|
|
56
|
+
<main class="content" id="app"></main>
|
|
57
|
+
|
|
58
|
+
<!-- Toast container for notifications -->
|
|
59
|
+
<div class="toast-container" id="toasts"></div>
|
|
60
|
+
|
|
61
|
+
</body>
|
|
62
|
+
</html>
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
// scripts/app.js โ application entry point
|
|
2
|
+
//
|
|
3
|
+
// This file bootstraps the zQuery app: imports all components,
|
|
4
|
+
// sets up routing, wires the responsive nav, and demonstrates
|
|
5
|
+
// several core APIs: $.router, $.ready, $.bus, $.on, and $.storage.
|
|
6
|
+
|
|
7
|
+
import './store.js';
|
|
8
|
+
import './components/home.js';
|
|
9
|
+
import './components/counter.js';
|
|
10
|
+
import './components/todos.js';
|
|
11
|
+
import './components/api-demo.js';
|
|
12
|
+
import './components/about.js';
|
|
13
|
+
import './components/contacts/contacts.js';
|
|
14
|
+
import './components/not-found.js';
|
|
15
|
+
import { routes } from './routes.js';
|
|
16
|
+
|
|
17
|
+
// ---------------------------------------------------------------------------
|
|
18
|
+
// Router โ SPA navigation with history mode
|
|
19
|
+
// ---------------------------------------------------------------------------
|
|
20
|
+
const router = $.router({
|
|
21
|
+
el: '#app',
|
|
22
|
+
routes,
|
|
23
|
+
fallback: 'not-found',
|
|
24
|
+
mode: 'history'
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Highlight the active nav link on every route change
|
|
28
|
+
router.onChange((to) => {
|
|
29
|
+
$.all('.nav-link').removeClass('active');
|
|
30
|
+
$.all(`.nav-link[z-link="${to.path}"]`).addClass('active');
|
|
31
|
+
|
|
32
|
+
// Close mobile menu on navigate
|
|
33
|
+
closeMobileMenu();
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
// ---------------------------------------------------------------------------
|
|
37
|
+
// Responsive sidebar toggle
|
|
38
|
+
// ---------------------------------------------------------------------------
|
|
39
|
+
const sidebar = $.id('sidebar');
|
|
40
|
+
const overlay = $.id('overlay');
|
|
41
|
+
const toggle = $.id('menu-toggle');
|
|
42
|
+
|
|
43
|
+
function openMobileMenu() {
|
|
44
|
+
sidebar.classList.add('open');
|
|
45
|
+
overlay.classList.add('visible');
|
|
46
|
+
toggle.classList.add('active');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function closeMobileMenu() {
|
|
50
|
+
sidebar.classList.remove('open');
|
|
51
|
+
overlay.classList.remove('visible');
|
|
52
|
+
toggle.classList.remove('active');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// $.on โ global delegated event listeners
|
|
56
|
+
$.on('click', '#menu-toggle', () => {
|
|
57
|
+
sidebar.classList.contains('open') ? closeMobileMenu() : openMobileMenu();
|
|
58
|
+
});
|
|
59
|
+
$.on('click', '#overlay', closeMobileMenu);
|
|
60
|
+
|
|
61
|
+
// Close sidebar on Escape key โ using $.on direct (no selector needed)
|
|
62
|
+
$.on('keydown', (e) => {
|
|
63
|
+
if (e.key === 'Escape') closeMobileMenu();
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// ---------------------------------------------------------------------------
|
|
67
|
+
// Toast notification system via $.bus (event bus)
|
|
68
|
+
// ---------------------------------------------------------------------------
|
|
69
|
+
// Any component can emit: $.bus.emit('toast', { message, type })
|
|
70
|
+
// Types: 'success', 'error', 'info'
|
|
71
|
+
$.bus.on('toast', ({ message, type = 'info' }) => {
|
|
72
|
+
const container = $.id('toasts');
|
|
73
|
+
const toast = $.create('div');
|
|
74
|
+
toast.className = `toast toast-${type}`;
|
|
75
|
+
toast.textContent = message;
|
|
76
|
+
container.appendChild(toast);
|
|
77
|
+
// Auto-remove after 3 seconds
|
|
78
|
+
setTimeout(() => {
|
|
79
|
+
toast.classList.add('toast-exit');
|
|
80
|
+
setTimeout(() => toast.remove(), 300);
|
|
81
|
+
}, 3000);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// ---------------------------------------------------------------------------
|
|
85
|
+
// On DOM ready โ final setup
|
|
86
|
+
// ---------------------------------------------------------------------------
|
|
87
|
+
$.ready(() => {
|
|
88
|
+
// Display version in the sidebar footer
|
|
89
|
+
const versionEl = $.id('nav-version');
|
|
90
|
+
if (versionEl) versionEl.textContent = 'v' + $.version;
|
|
91
|
+
|
|
92
|
+
// Restore last theme from localStorage ($.storage)
|
|
93
|
+
const savedTheme = $.storage.get('theme');
|
|
94
|
+
if (savedTheme) document.documentElement.setAttribute('data-theme', savedTheme);
|
|
95
|
+
|
|
96
|
+
// Set active link on initial load
|
|
97
|
+
const current = window.location.pathname;
|
|
98
|
+
$.all(`.nav-link[z-link="${current}"]`).addClass('active');
|
|
99
|
+
|
|
100
|
+
console.log('โก {{NAME}} โ powered by zQuery v' + $.version);
|
|
101
|
+
});
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
// scripts/components/about.js โ about page with theme switcher
|
|
2
|
+
//
|
|
3
|
+
// Demonstrates: $.storage (localStorage wrapper), $.bus for notifications,
|
|
4
|
+
// $.version, component methods, data-theme attribute toggling
|
|
5
|
+
|
|
6
|
+
$.component('about-page', {
|
|
7
|
+
state: () => ({
|
|
8
|
+
theme: 'dark',
|
|
9
|
+
}),
|
|
10
|
+
|
|
11
|
+
mounted() {
|
|
12
|
+
// Read persisted theme via $.storage
|
|
13
|
+
this.state.theme = $.storage.get('theme') || 'dark';
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
toggleTheme() {
|
|
17
|
+
const next = this.state.theme === 'dark' ? 'light' : 'dark';
|
|
18
|
+
this.state.theme = next;
|
|
19
|
+
// Apply theme via data attribute
|
|
20
|
+
document.documentElement.setAttribute('data-theme', next);
|
|
21
|
+
// Persist via $.storage (wraps localStorage)
|
|
22
|
+
$.storage.set('theme', next);
|
|
23
|
+
$.bus.emit('toast', { message: `Switched to ${next} theme`, type: 'info' });
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
render() {
|
|
27
|
+
return `
|
|
28
|
+
<div class="page-header">
|
|
29
|
+
<h1>About</h1>
|
|
30
|
+
<p class="subtitle">Built with zQuery v${$.version} โ a zero-dependency frontend library.</p>
|
|
31
|
+
</div>
|
|
32
|
+
|
|
33
|
+
<div class="card">
|
|
34
|
+
<h3>๐จ Theme</h3>
|
|
35
|
+
<p>Toggle between dark and light mode. Persisted to <code>localStorage</code> via <code>$.storage</code>.</p>
|
|
36
|
+
<div class="theme-toggle">
|
|
37
|
+
<span>Current: <strong>${this.state.theme}</strong></span>
|
|
38
|
+
<button class="btn btn-outline" @click="toggleTheme">${this.state.theme === 'dark' ? 'โ๏ธ Light Mode' : '๐ Dark Mode'}</button>
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<div class="card">
|
|
43
|
+
<h3>๐งฐ Features Used in This App</h3>
|
|
44
|
+
<div class="feature-grid">
|
|
45
|
+
<div class="feature-item">
|
|
46
|
+
<strong>$.component()</strong>
|
|
47
|
+
<span>Reactive components with state, lifecycle hooks, and template rendering</span>
|
|
48
|
+
</div>
|
|
49
|
+
<div class="feature-item">
|
|
50
|
+
<strong>$.router()</strong>
|
|
51
|
+
<span>SPA routing with history mode, z-link navigation, and fallback pages</span>
|
|
52
|
+
</div>
|
|
53
|
+
<div class="feature-item">
|
|
54
|
+
<strong>$.store()</strong>
|
|
55
|
+
<span>Centralized state management with actions, getters, and subscriptions</span>
|
|
56
|
+
</div>
|
|
57
|
+
<div class="feature-item">
|
|
58
|
+
<strong>$.get()</strong>
|
|
59
|
+
<span>HTTP client for fetching JSON APIs with async/await</span>
|
|
60
|
+
</div>
|
|
61
|
+
<div class="feature-item">
|
|
62
|
+
<strong>$.signal() / $.computed()</strong>
|
|
63
|
+
<span>Fine-grained reactive primitives for derived state</span>
|
|
64
|
+
</div>
|
|
65
|
+
<div class="feature-item">
|
|
66
|
+
<strong>$.bus</strong>
|
|
67
|
+
<span>Event bus for cross-component communication (toast notifications)</span>
|
|
68
|
+
</div>
|
|
69
|
+
<div class="feature-item">
|
|
70
|
+
<strong>$.storage</strong>
|
|
71
|
+
<span>localStorage wrapper for persisting user preferences</span>
|
|
72
|
+
</div>
|
|
73
|
+
<div class="feature-item">
|
|
74
|
+
<strong>$.debounce()</strong>
|
|
75
|
+
<span>Debounced search input in the todos filter</span>
|
|
76
|
+
</div>
|
|
77
|
+
<div class="feature-item">
|
|
78
|
+
<strong>$.escapeHtml()</strong>
|
|
79
|
+
<span>Safe rendering of user-generated and API content</span>
|
|
80
|
+
</div>
|
|
81
|
+
<div class="feature-item">
|
|
82
|
+
<strong>$.uuid()</strong>
|
|
83
|
+
<span>Unique ID generation for new todo items</span>
|
|
84
|
+
</div>
|
|
85
|
+
<div class="feature-item">
|
|
86
|
+
<strong>z-model / z-ref</strong>
|
|
87
|
+
<span>Two-way data binding and DOM element references</span>
|
|
88
|
+
</div>
|
|
89
|
+
<div class="feature-item">
|
|
90
|
+
<strong>templateUrl / styleUrl</strong>
|
|
91
|
+
<span>External HTML templates and CSS with auto-scoping (contacts page)</span>
|
|
92
|
+
</div>
|
|
93
|
+
<div class="feature-item">
|
|
94
|
+
<strong>z-if / z-for / z-show</strong>
|
|
95
|
+
<span>Structural directives for conditional & list rendering</span>
|
|
96
|
+
</div>
|
|
97
|
+
<div class="feature-item">
|
|
98
|
+
<strong>z-bind / z-class / z-style</strong>
|
|
99
|
+
<span>Dynamic attributes, classes, and inline styles</span>
|
|
100
|
+
</div>
|
|
101
|
+
<div class="feature-item">
|
|
102
|
+
<strong>$.on()</strong>
|
|
103
|
+
<span>Global delegated event listeners for the hamburger menu</span>
|
|
104
|
+
</div>
|
|
105
|
+
</div>
|
|
106
|
+
</div>
|
|
107
|
+
|
|
108
|
+
<div class="card card-muted">
|
|
109
|
+
<h3>๐ Next Steps</h3>
|
|
110
|
+
<ul class="next-steps">
|
|
111
|
+
<li>Read the <a href="https://z-query.com/docs" target="_blank" rel="noopener">full documentation</a></li>
|
|
112
|
+
<li>Explore the <a href="https://github.com/tonywied17/zero-query" target="_blank" rel="noopener">source on GitHub</a></li>
|
|
113
|
+
<li>Run <code>npx zquery bundle</code> to build for production</li>
|
|
114
|
+
<li>Run <code>npx zquery dev</code> for live-reload development</li>
|
|
115
|
+
</ul>
|
|
116
|
+
</div>
|
|
117
|
+
`;
|
|
118
|
+
}
|
|
119
|
+
});
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
// scripts/components/api-demo.js โ HTTP client demonstration
|
|
2
|
+
//
|
|
3
|
+
// Demonstrates: $.get() for fetching JSON, z-if/z-else conditional
|
|
4
|
+
// rendering, z-show visibility, z-for list rendering,
|
|
5
|
+
// z-text content binding, @click event handling,
|
|
6
|
+
// loading/error states, $.escapeHtml(), async patterns
|
|
7
|
+
|
|
8
|
+
$.component('api-demo', {
|
|
9
|
+
state: () => ({
|
|
10
|
+
users: [],
|
|
11
|
+
selectedUser: null,
|
|
12
|
+
posts: [],
|
|
13
|
+
loading: false,
|
|
14
|
+
error: '',
|
|
15
|
+
}),
|
|
16
|
+
|
|
17
|
+
mounted() {
|
|
18
|
+
this.fetchUsers();
|
|
19
|
+
},
|
|
20
|
+
|
|
21
|
+
async fetchUsers() {
|
|
22
|
+
this.state.loading = true;
|
|
23
|
+
this.state.error = '';
|
|
24
|
+
try {
|
|
25
|
+
// $.get() โ zero-config JSON fetching
|
|
26
|
+
const res = await $.get('https://jsonplaceholder.typicode.com/users');
|
|
27
|
+
this.state.users = res.data.slice(0, 6);
|
|
28
|
+
} catch (err) {
|
|
29
|
+
this.state.error = 'Failed to load users. Check your connection.';
|
|
30
|
+
}
|
|
31
|
+
this.state.loading = false;
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
async selectUser(id) {
|
|
35
|
+
this.state.selectedUser = this.state.users.find(u => u.id === Number(id));
|
|
36
|
+
this.state.loading = true;
|
|
37
|
+
try {
|
|
38
|
+
const res = await $.get(`https://jsonplaceholder.typicode.com/posts?userId=${id}`);
|
|
39
|
+
this.state.posts = res.data.slice(0, 4);
|
|
40
|
+
} catch (err) {
|
|
41
|
+
this.state.error = 'Failed to load posts.';
|
|
42
|
+
}
|
|
43
|
+
this.state.loading = false;
|
|
44
|
+
$.bus.emit('toast', { message: `Loaded posts for ${this.state.selectedUser.name}`, type: 'success' });
|
|
45
|
+
},
|
|
46
|
+
|
|
47
|
+
clearSelection() {
|
|
48
|
+
this.state.selectedUser = null;
|
|
49
|
+
this.state.posts = [];
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
render() {
|
|
53
|
+
const { selectedUser } = this.state;
|
|
54
|
+
|
|
55
|
+
return `
|
|
56
|
+
<div class="page-header">
|
|
57
|
+
<h1>API Demo</h1>
|
|
58
|
+
<p class="subtitle">Fetching data with <code>$.get()</code>. Directives: <code>z-if</code>, <code>z-show</code>, <code>z-for</code>, <code>z-text</code>.</p>
|
|
59
|
+
</div>
|
|
60
|
+
|
|
61
|
+
<div class="card card-error" z-show="error"><p>โ <span z-text="error"></span></p></div>
|
|
62
|
+
<div class="loading-bar" z-show="loading"></div>
|
|
63
|
+
|
|
64
|
+
<div z-if="!selectedUser">
|
|
65
|
+
<div class="card">
|
|
66
|
+
<h3>Users</h3>
|
|
67
|
+
<p class="muted">Click a user to fetch their posts.</p>
|
|
68
|
+
<div class="user-grid" z-if="users.length > 0">
|
|
69
|
+
<button z-for="u in users" class="user-card" @click="selectUser({{u.id}})">
|
|
70
|
+
<strong>{{u.name}}</strong>
|
|
71
|
+
<small>@{{u.username}}</small>
|
|
72
|
+
<small class="muted">{{u.company.name}}</small>
|
|
73
|
+
</button>
|
|
74
|
+
</div>
|
|
75
|
+
<p z-else z-show="!loading">No users loaded.</p>
|
|
76
|
+
</div>
|
|
77
|
+
</div>
|
|
78
|
+
|
|
79
|
+
<div z-else>
|
|
80
|
+
<div class="card">
|
|
81
|
+
<div class="user-detail-header">
|
|
82
|
+
<div>
|
|
83
|
+
<h3>${selectedUser ? $.escapeHtml(selectedUser.name) : ''}</h3>
|
|
84
|
+
<p class="muted">${selectedUser ? `@${$.escapeHtml(selectedUser.username)} ยท ${$.escapeHtml(selectedUser.email)}` : ''}</p>
|
|
85
|
+
</div>
|
|
86
|
+
<button class="btn btn-ghost btn-sm" @click="clearSelection">โ Back</button>
|
|
87
|
+
</div>
|
|
88
|
+
</div>
|
|
89
|
+
|
|
90
|
+
<div class="card">
|
|
91
|
+
<h3>Recent Posts</h3>
|
|
92
|
+
<div class="posts-list" z-if="posts.length > 0">
|
|
93
|
+
<article z-for="p in posts" class="post-item">
|
|
94
|
+
<h4>{{p.title}}</h4>
|
|
95
|
+
<p>{{p.body.substring(0, 120)}}โฆ</p>
|
|
96
|
+
</article>
|
|
97
|
+
</div>
|
|
98
|
+
<p z-else class="muted" z-show="!loading">No posts found.</p>
|
|
99
|
+
</div>
|
|
100
|
+
</div>
|
|
101
|
+
`;
|
|
102
|
+
}
|
|
103
|
+
});
|