Updated NJET
This commit is contained in:
parent
62eb1060d9
commit
fbd72f727a
473
src/snek/static/njet.js
Normal file
473
src/snek/static/njet.js
Normal file
@ -0,0 +1,473 @@
|
||||
|
||||
|
||||
class RestClient {
|
||||
constructor({ baseURL = '', headers = {} } = {}) {
|
||||
this.baseURL = baseURL;
|
||||
this.headers = { ...headers };
|
||||
|
||||
// Interceptor containers
|
||||
this.interceptors = {
|
||||
request: {
|
||||
handlers: [],
|
||||
use: (fn) => {
|
||||
this.interceptors.request.handlers.push(fn);
|
||||
}
|
||||
},
|
||||
response: {
|
||||
handlers: [],
|
||||
use: (successFn, errorFn) => {
|
||||
this.interceptors.response.handlers.push({ success: successFn, error: errorFn });
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// Core request method
|
||||
request(config) {
|
||||
// Merge defaults
|
||||
const cfg = {
|
||||
method: 'GET',
|
||||
url: '',
|
||||
data: null,
|
||||
headers: {},
|
||||
...config
|
||||
};
|
||||
cfg.headers = { ...this.headers, ...cfg.headers };
|
||||
|
||||
// Apply request interceptors
|
||||
let chain = Promise.resolve(cfg);
|
||||
this.interceptors.request.handlers.forEach((fn) => {
|
||||
chain = chain.then(fn);
|
||||
});
|
||||
|
||||
// Perform fetch
|
||||
chain = chain.then((c) => {
|
||||
const url = this.baseURL + c.url;
|
||||
const options = { method: c.method, headers: c.headers };
|
||||
if (c.data != null) {
|
||||
options.body = JSON.stringify(c.data);
|
||||
if (!options.headers['Content-Type']) {
|
||||
options.headers['Content-Type'] = 'application/json';
|
||||
}
|
||||
}
|
||||
return fetch(url, options).then(async (response) => {
|
||||
const text = await response.text();
|
||||
let data;
|
||||
try {
|
||||
data = JSON.parse(text);
|
||||
} catch (e) {
|
||||
data = text;
|
||||
}
|
||||
const result = {
|
||||
data,
|
||||
status: response.status,
|
||||
statusText: response.statusText,
|
||||
headers: RestClient._parseHeaders(response.headers),
|
||||
config: c,
|
||||
request: response
|
||||
};
|
||||
if (!response.ok) {
|
||||
return Promise.reject(result);
|
||||
}
|
||||
return result;
|
||||
});
|
||||
});
|
||||
|
||||
// Apply response interceptors
|
||||
this.interceptors.response.handlers.forEach(({ success, error }) => {
|
||||
chain = chain.then(success, error);
|
||||
});
|
||||
|
||||
return chain;
|
||||
}
|
||||
|
||||
// Helper methods for HTTP verbs
|
||||
get(url, config) {
|
||||
return this.request({ ...config, method: 'GET', url });
|
||||
}
|
||||
delete(url, config) {
|
||||
return this.request({ ...config, method: 'DELETE', url });
|
||||
}
|
||||
head(url, config) {
|
||||
return this.request({ ...config, method: 'HEAD', url });
|
||||
}
|
||||
options(url, config) {
|
||||
return this.request({ ...config, method: 'OPTIONS', url });
|
||||
}
|
||||
post(url, data, config) {
|
||||
return this.request({ ...config, method: 'POST', url, data });
|
||||
}
|
||||
put(url, data, config) {
|
||||
return this.request({ ...config, method: 'PUT', url, data });
|
||||
}
|
||||
patch(url, data, config) {
|
||||
return this.request({ ...config, method: 'PATCH', url, data });
|
||||
}
|
||||
|
||||
// Utility to parse Fetch headers into an object
|
||||
static _parseHeaders(headers) {
|
||||
const result = {};
|
||||
for (const [key, value] of headers.entries()) {
|
||||
result[key] = value;
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
class Njet extends HTMLElement {
|
||||
static _root = null
|
||||
static showDialog = null
|
||||
|
||||
get isRoot() {
|
||||
return Njet._root === this
|
||||
}
|
||||
|
||||
get root() {
|
||||
return Njet._root
|
||||
}
|
||||
|
||||
get rest() {
|
||||
return Njet._root._rest
|
||||
}
|
||||
|
||||
_subscriptions = {}
|
||||
_elements = []
|
||||
_rest = null
|
||||
|
||||
match(args) {
|
||||
return Object.entries(args).every(([key, value]) => this[key] === value);
|
||||
}
|
||||
|
||||
set(key, value) {
|
||||
this.dataset[key] = value
|
||||
}
|
||||
|
||||
get(key, defaultValue) {
|
||||
if (this.dataset[key]) {
|
||||
return this.dataset[key]
|
||||
}
|
||||
if (defaultValue === undefined) {
|
||||
return
|
||||
}
|
||||
this.dataset[key] = defaultValue
|
||||
return this.dataset[key]
|
||||
}
|
||||
|
||||
showDialog(args){
|
||||
|
||||
// const dialog = this.createComponent('njet-dialog', args)
|
||||
// dialog.show()
|
||||
// return dialog()
|
||||
}
|
||||
|
||||
find(args) {
|
||||
for (let element of this.root._elements) {
|
||||
if (element.match(args)) {
|
||||
return element
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
findAll(args) {
|
||||
return this.root._elements.filter(element => element.match(args))
|
||||
}
|
||||
|
||||
subscribe(event, callback) {
|
||||
if (!this.root._subscriptions[event]) {
|
||||
this.root._subscriptions[event] = []
|
||||
}
|
||||
this.root._subscriptions[event].push(callback)
|
||||
}
|
||||
|
||||
publish(event, data) {
|
||||
if (this.root._subscriptions[event]) {
|
||||
this.root._subscriptions[event].forEach(callback => callback(data))
|
||||
}
|
||||
}
|
||||
|
||||
static registerComponent(name, component) {
|
||||
customElements.define(name, component);
|
||||
}
|
||||
|
||||
constructor(config = {}) {
|
||||
super();
|
||||
if (!Njet._root) {
|
||||
Njet._root = this
|
||||
Njet._rest = new RestClient({ baseURL: config.baseURL || null })
|
||||
}
|
||||
this.root._elements.push(this)
|
||||
this.classList.add('njet');
|
||||
this.config = config;
|
||||
this.render.call(this);
|
||||
this.initProps(config);
|
||||
if (typeof this.construct === 'function')
|
||||
this.construct.call(this)
|
||||
}
|
||||
|
||||
initProps(config) {
|
||||
const props = Object.keys(config)
|
||||
props.forEach(prop => {
|
||||
if (config[prop] !== undefined) {
|
||||
this[prop] = config[prop];
|
||||
}
|
||||
});
|
||||
if (config.classes) {
|
||||
this.classList.add(...config.classes);
|
||||
}
|
||||
}
|
||||
|
||||
duplicate() {
|
||||
const duplicatedConfig = { ...this.config };
|
||||
if (duplicatedConfig.items) {
|
||||
duplicatedConfig.items = duplicatedConfig.items.map(item => {
|
||||
return typeof item.duplicate === 'function' ? item.duplicate() : item;
|
||||
});
|
||||
}
|
||||
return new this.constructor(duplicatedConfig);
|
||||
}
|
||||
|
||||
set width(val) {
|
||||
this.style.width = typeof val === 'number' ? `${val}px` : val;
|
||||
}
|
||||
|
||||
get width() { return this.style.width; }
|
||||
|
||||
set height(val) {
|
||||
this.style.height = typeof val === 'number' ? `${val}px` : val;
|
||||
}
|
||||
|
||||
get height() { return this.style.height; }
|
||||
|
||||
set left(val) {
|
||||
this.style.position = 'absolute';
|
||||
this.style.left = typeof val === 'number' ? `${val}px` : val;
|
||||
}
|
||||
|
||||
get left() { return this.style.left; }
|
||||
|
||||
set top(val) {
|
||||
this.style.position = 'absolute';
|
||||
this.style.top = typeof val === 'number' ? `${val}px` : val;
|
||||
}
|
||||
|
||||
get top() { return this.style.top; }
|
||||
|
||||
set opacity(val) { this.style.opacity = val; }
|
||||
get opacity() { return this.style.opacity; }
|
||||
|
||||
set disabled(val) { this.toggleAttribute('disabled', !!val); }
|
||||
get disabled() { return this.hasAttribute('disabled'); }
|
||||
|
||||
set visible(val) { this.style.display = val ? '' : 'none'; }
|
||||
get visible() { return this.style.display !== 'none'; }
|
||||
|
||||
render() {}
|
||||
}
|
||||
|
||||
class Component extends Njet {}
|
||||
|
||||
class NjetPanel extends Component {
|
||||
render() {
|
||||
this.innerHTML = '';
|
||||
const { title, items = [] } = this.config;
|
||||
this.style.border = '1px solid #ccc';
|
||||
this.style.padding = '10px';
|
||||
if (title) {
|
||||
const header = document.createElement('h3');
|
||||
header.textContent = title;
|
||||
this.appendChild(header);
|
||||
}
|
||||
items.forEach(item => this.appendChild(item));
|
||||
}
|
||||
}
|
||||
Njet.registerComponent('njet-panel', NjetPanel);
|
||||
|
||||
class NjetButton extends Component {
|
||||
render() {
|
||||
this.classList.add('njet-button');
|
||||
this.innerHTML = '';
|
||||
const button = document.createElement('button');
|
||||
button.textContent = this.config.text || 'Button';
|
||||
if (typeof this.config.handler === 'function') {
|
||||
button.addEventListener('click', (event) => this.config.handler.call(this));
|
||||
}
|
||||
const observer = new MutationObserver(() => {
|
||||
button.disabled = this.disabled;
|
||||
});
|
||||
observer.observe(this, { attributes: true, attributeFilter: ['disabled'] });
|
||||
button.disabled = this.disabled;
|
||||
this.appendChild(button);
|
||||
}
|
||||
}
|
||||
Njet.registerComponent('njet-button', NjetButton);
|
||||
|
||||
class NjetDialog extends Component {
|
||||
render() {
|
||||
this.innerHTML = '';
|
||||
const { title, content, primaryButton, secondaryButton } = this.config;
|
||||
this.classList.add('njet-dialog');
|
||||
this.style.position = 'fixed';
|
||||
this.style.top = '50%';
|
||||
this.style.left = '50%';
|
||||
this.style.transform = 'translate(-50%, -50%)';
|
||||
this.style.padding = '20px';
|
||||
this.style.border = '1px solid #444';
|
||||
this.style.backgroundColor = '#fff';
|
||||
this.style.boxShadow = '0 4px 8px rgba(0, 0, 0, 0.2)';
|
||||
this.style.minWidth = '300px';
|
||||
if (title) {
|
||||
const header = document.createElement('h2');
|
||||
header.textContent = title;
|
||||
this.appendChild(header);
|
||||
}
|
||||
if (content) {
|
||||
const body = document.createElement('div');
|
||||
body.innerHTML = content;
|
||||
this.appendChild(body);
|
||||
}
|
||||
const buttonContainer = document.createElement('div');
|
||||
buttonContainer.style.marginTop = '20px';
|
||||
buttonContainer.style.display = 'flex';
|
||||
buttonContainer.style.justifyContent = 'flenjet-end';
|
||||
buttonContainer.style.gap = '10px';
|
||||
if (secondaryButton) {
|
||||
const secondary = new NjetButton(secondaryButton);
|
||||
buttonContainer.appendChild(secondary);
|
||||
}
|
||||
if (primaryButton) {
|
||||
const primary = new NjetButton(primaryButton);
|
||||
buttonContainer.appendChild(primary);
|
||||
}
|
||||
this.appendChild(buttonContainer);
|
||||
}
|
||||
|
||||
show(){
|
||||
document.body.appendChild(this)
|
||||
}
|
||||
}
|
||||
Njet.registerComponent('njet-dialog', NjetDialog);
|
||||
|
||||
class NjetGrid extends Component {
|
||||
render() {
|
||||
this.classList.add('njet-grid');
|
||||
this.innerHTML = '';
|
||||
const table = document.createElement('table');
|
||||
table.style.width = '100%';
|
||||
table.style.borderCollapse = 'collapse';
|
||||
const data = this.config.data || [];
|
||||
data.forEach(row => {
|
||||
const tr = document.createElement('tr');
|
||||
Object.values(row).forEach(cell => {
|
||||
const td = document.createElement('td');
|
||||
td.textContent = cell;
|
||||
td.style.border = '1px solid #ddd';
|
||||
td.style.padding = '4px';
|
||||
tr.appendChild(td);
|
||||
});
|
||||
table.appendChild(tr);
|
||||
});
|
||||
this.appendChild(table);
|
||||
}
|
||||
}
|
||||
Njet.registerComponent('njet-grid', NjetGrid);
|
||||
/*
|
||||
const button = new NjetButton({
|
||||
classes: ['my-button'],
|
||||
text: 'Shared',
|
||||
tag: 'shared',
|
||||
width: 120,
|
||||
height: 30,
|
||||
handler() {
|
||||
this.root.findAll({ tag: 'shared' }).forEach(e => {
|
||||
e.disabled = !e.disabled;
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const button2 = new NjetButton({
|
||||
classes: ['my-button'],
|
||||
text: 'Single',
|
||||
iValue: 0,
|
||||
width: 120,
|
||||
height: 30,
|
||||
handler() {
|
||||
this.iValue++;
|
||||
const panel = this.closest('njet-panel');
|
||||
if (panel) {
|
||||
const h3 = panel.querySelector('h3');
|
||||
if (h3) h3.textContent = `Click ${this.iValue}`;
|
||||
}
|
||||
this.publish("btn2Click", `Click ${this.iValue}`);
|
||||
}
|
||||
});
|
||||
|
||||
const grid = new NjetGrid({
|
||||
data: [
|
||||
{ id: 1, name: 'John' },
|
||||
{ id: 2, name: 'Jane' }
|
||||
],
|
||||
width: '100%',
|
||||
visible: true
|
||||
});
|
||||
|
||||
const panel = new NjetPanel({
|
||||
title: 'My Panel',
|
||||
items: [button, grid, button2],
|
||||
left: 50,
|
||||
top: 50,
|
||||
construct: function () {
|
||||
this.subscribe('btn2Click', (data) => {
|
||||
this._title = data
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
document.body.appendChild(panel);
|
||||
|
||||
const panelClone = panel.duplicate();
|
||||
const panell = panelClone.duplicate();
|
||||
panell.left = 120;
|
||||
panell.width = 300;
|
||||
panelClone.appendChild(panell);
|
||||
panelClone.left = 300;
|
||||
panelClone.top = 50;
|
||||
document.body.appendChild(panelClone);
|
||||
|
||||
const dialog = new NjetDialog({
|
||||
title: 'Confirm Action',
|
||||
content: 'Are you sure you want to continue?',
|
||||
primaryButton: {
|
||||
text: 'Yes',
|
||||
handler: function () {
|
||||
alert('Confirmed');
|
||||
this.closest('njet-dialog').remove();
|
||||
}
|
||||
},
|
||||
secondaryButton: {
|
||||
text: 'Cancel',
|
||||
handler: function () {
|
||||
this.closest('njet-dialog').remove();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.body.appendChild(dialog);
|
||||
*/
|
||||
|
||||
class NjetComponent extends Component {}
|
||||
const njet = Njet;
|
||||
njet.showDialog = function(args){
|
||||
const dialog = new NjetDialog(args)
|
||||
dialog.show()
|
||||
return dialog
|
||||
}
|
||||
|
||||
window.njet = njet
|
||||
|
||||
export { Njet, NjetButton, NjetPanel, NjetDialog, NjetGrid, NjetComponent, njet};
|
Loading…
Reference in New Issue
Block a user