` elements.
+
+```typescript
+const link = new GefestA("Visit site", "https://example.com");
+link.setAttribute('target', '_blank');
+```
+
+### GefestImg
+
+Wraps HTML ` ` elements.
+
+```typescript
+const image = new GefestImg("", "path/to/image.jpg");
+image.setAttribute('alt', 'Description');
+```
+
+### Other Primitives
+
+- `GefestP`: Paragraph (``)
+- `GefestSpan`: Span (``)
+- `GefestHeader`: Header (``)
+- `GefestI`: Italic (``)
+- `GefestSmall`: Small text (``)
+- `GefestCenter`: Centered content (``)
+
+## Advanced Features
+
+### Nested Elements
+
+```typescript
+const link = new GefestA("Read more", "#");
+const button = new GefestButton([link, " about this"]);
+```
+
+### Custom Elements
+
+Create your own elements by extending `GefestElement`:
+
+```typescript
+import { GefestElement } from './element';
+
+export class GefestDiv extends GefestElement {
+ protected wrapHTML(content: string): string {
+ const attrString = this.attributesToString();
+ return `${content}
`;
+ }
+}
+```
+
+### Custom Styles
+
+Implement custom styling by extending `GefestStyle`:
+
+```typescript
+import { GefestStyle, GefestElement } from './style';
+
+export class BootstrapStyle extends GefestStyle {
+ applyStyle(html: string, element?: GefestElement): string {
+ // Apply Bootstrap classes or inline styles
+ if (element?.getClassList().includes('btn')) {
+ return html.replace('Click ');
+```
+
+## API Reference
+
+### GefestElement
+
+#### Properties
+- `gefestId: string` - Unique identifier for the element (readonly)
+- `style: GefestStyle | null` - Style to apply to the element
+- `onClick: (() => void) | null` - Click event handler (getter/setter)
+- `isHidden: boolean` - Whether the element should be hidden (sets the 'hidden' attribute when true)
+
+#### Methods
+- `constructor(content: (GefestElement | string)[] | string)`
+- `build(isFromStyle?: boolean): string`
+- `update(): void` - Re-renders the element in the DOM
+- `setAttribute(key: string, value: string): void`
+- `getAttribute(key: string): string | undefined`
+- `removeAttribute(key: string): void`
+- `addClass(className: string): void`
+- `removeClass(className: string): void`
+- `getClassList(): string[]`
+- `setPersonalStyle(style: string | null): void`
+
+#### Static Methods
+- `fromHTML(html: string): GefestElement`
+
+### GefestEngine
+
+#### Static Methods
+- `register(element: GefestElement): string`
+- `activateOnClick(): Promise`
+- `main(main: () => Promise): Promise`
+- `reRenderByGI(gefestId: string): void`
+
+### GefestStyle
+
+#### Methods
+- `applyStyle(html: string, element?: GefestElement): string`
+
+## Examples
+
+### Complete Application
+
+```typescript
+import { GefestEngine } from './engine';
+import { GefestButton, GefestP } from './primitives';
+
+async function main() {
+ const title = new GefestP("Welcome to Gefest!");
+ title.addClass('title');
+
+ const button = new GefestButton("Get Started");
+ button.addClass('start-btn');
+ button.onClick = () => {
+ alert('Welcome!');
+ };
+
+ // For custom elements, define them as shown above
+ const container = new (class extends GefestElement {
+ protected wrapHTML(content: string): string {
+ const attrString = this.attributesToString();
+ return `${content}
`;
+ }
+ })([title, button]);
+ container.addClass('container');
+
+ document.body.innerHTML = container.build();
+}
+
+GefestEngine.main(main);
+```
+
+### Form Creation
+
+```typescript
+import { GefestButton } from './primitives/button';
+
+const submitBtn = new GefestButton("Submit");
+submitBtn.setAttribute('type', 'submit');
+submitBtn.addClass('btn-submit');
+
+const form = document.createElement('form');
+form.innerHTML = submitBtn.build();
+document.body.appendChild(form);
+```
+
+## Contributing
+
+To add new primitives or extend the framework:
+
+1. Create new classes extending `GefestElement`
+2. Implement the `wrapHTML` method to return the appropriate HTML tag
+3. Add event handling if needed
+4. Test with `GefestEngine.main()`
+
+## License
+
+This framework is part of the AmeVox project. See project license for details.
diff --git a/src/element.ts b/src/element.ts
index 880d970..df36fdb 100644
--- a/src/element.ts
+++ b/src/element.ts
@@ -1,33 +1,149 @@
+import { GefestEngine } from './engine';
import { GefestStyle } from './style';
// The general class of Gefest
export abstract class GefestElement {
style: GefestStyle | null = null;
+ isHidden: boolean = false;
+ //onClick: (() => void) | null = null;
+ readonly gefestId: string;
protected content: (GefestElement | string)[] | string;
protected attributes: Record = {};
+ protected classList: Set = new Set();
+
+ protected clickHandler: (() => void) | null = null;
+
+ set onClick(handler: (() => void) | null) {
+ if (handler) {
+ this.clickHandler = handler;
+ } else {
+ this.clickHandler = null;
+ }
+ }
+
+ get onClick(): (() => void) | null {
+ if (this.clickHandler === null)
+ return null;
+ return () => {
+ this.clickHandler!();
+ GefestEngine.activateOnClick();
+ };
+ }
+
+ /**
+ * Creates a new instance of the element from an HTML string.
+ * @param html The HTML string to parse.
+ * @returns The created GefestElement.
+ */
+ static fromHTML(html: string): GefestElement {
+ const htmlElement = new DOMParser().parseFromString(html, 'text/html').body.firstChild as HTMLElement;
+ const element = new (class extends GefestElement {
+ protected wrapHTML(content: string): string {
+ return content;
+ }
+ })(htmlElement.innerHTML);
+
+ for (const attr of htmlElement.attributes) {
+ try {
+ element.setAttribute(attr.name, attr.value);
+ } catch (_) {}
+ }
+
+ for (const className of htmlElement.classList) {
+ element.addClass(className);
+ }
+
+ return element;
+ }
/**
* Creates a new instance of the element.
* @param content The content for the element. Can be a string, an array of strings, an array of GefestElements or a combination of both.
*/
constructor (content : (GefestElement | string)[] | string = []) {
+ this.gefestId = GefestEngine.register(this);
this.content = content;
}
- /**
- * The build method is used to build the element and return it as a string. Dont't redefine this method in the child class, if you want to add some styles to the element, use the setStyle method and then call the render method in the build method of the child class.
- * @return {string} The built element as a string.
- */
- build (): string {
- return this.applyStyle(this.render());
+ /**
+ * Call to GefestEngine for rerender element
+ */
+ update (): void {
+ GefestEngine.reRenderByGI(this.gefestId);
+ }
+
+ /**
+ * Builds the element and returns it as a string. If the element has a style, it applies the style to the rendered HTML.
+ * @param isFromStyle Indicates if the build is being called from a style application. This prevents infinite recursion when applying styles.
+ * @returns The built element as a string.
+ */
+ build (isFromStyle: boolean = false): string {
+ if (this.isHidden)
+ this.setAttribute('hidden', '');
+ else
+ this.removeAttribute('hidden');
+
+ if (!isFromStyle)
+ return this.applyStyle(this.render(), this);
+ return this.render();
};
+ /**
+ * Checks if an attribute key is reserved.
+ * @param key The attribute key.
+ */
+ checkReservedAttribute(key: string): void {
+ if (({ "style": true, "class": true })[key])
+ throw new Error(`The attribute "${
+ key
+ }" is reserved. Use the setPersonalStyle method for styles and setClass method for classes.`);
+ }
+
+ /**
+ * Sets a personal style for the element. This will be applied on top of any style that is set for the element.
+ * @param style The personal style to set. If null, the personal style will be removed.
+ */
+ setPersonalStyle(style: string | null): void {
+ if (style === null) {
+ delete this.attributes.style;
+ return;
+ }
+ this.attributes.style = style;
+ }
+
+ /**
+ * Adds a class to the element's class list and updates the "class" attribute accordingly.
+ * @param className The class name to add.
+ */
+ addClass(className: string): void {
+ this.classList.add(className);
+ this.attributes.class = Array.from(this.classList).join(' ');
+ }
+
+ /**
+ * Removes a class from the element's class list and updates the "class" attribute accordingly.
+ * @param className The class name to remove.
+ */
+ removeClass(className: string): void {
+ this.classList.delete(className);
+ this.attributes.class = Array.from(this.classList).join(' ');
+ }
+
+ /**
+ * Gets the list of classes for the element.
+ * @returns The array of class names.
+ */
+ getClassList(): string[] {
+ return Array.from(this.classList);
+ }
+
/**
* Sets an attribute for the element.
* @param key The attribute key.
* @param value The attribute value.
*/
setAttribute (key: string, value: string): void {
+ this.checkReservedAttribute(key);
this.attributes[key] = value;
}
@@ -45,6 +161,7 @@ export abstract class GefestElement {
* @param key The attribute key.
*/
removeAttribute (key: string): void {
+ this.checkReservedAttribute(key);
delete this.attributes[key];
}
@@ -53,9 +170,9 @@ export abstract class GefestElement {
* @param html The HTML to apply the style to.
* @returns The styled HTML.
*/
- protected applyStyle(html: string): string {
+ protected applyStyle(html: string, element?: GefestElement): string {
if (this.style)
- return this.style.applyStyle(html);
+ return this.style.applyStyle(html, element);
return html;
}
@@ -76,7 +193,7 @@ export abstract class GefestElement {
*/
protected wrapHTML (content: string): string {
const attrString = this.attributesToString();
- return `${content}
`;
+ return `${content}
`;
}
/**
diff --git a/src/engine.ts b/src/engine.ts
new file mode 100644
index 0000000..688c77f
--- /dev/null
+++ b/src/engine.ts
@@ -0,0 +1,43 @@
+import { GefestElement } from "./element";
+
+// For protecting the set of elements from direct access
+const elements: Set = new Set();
+const elementsMapping: Map = new Map ();
+export class GefestEngine {
+ static register (element: GefestElement) : string {
+ elements.add(element);
+ // Generate GefestID for the element
+ const gefestId = `gefest-${Math.random().toString(36).substr(2, 9)}`;
+ element.setAttribute('data-gefest-id', gefestId);
+ elementsMapping.set(gefestId, element);
+
+ return gefestId;
+ }
+
+ static async activateOnClick(): Promise {
+ for (let element of elements) {
+ const elementHTML = document.querySelectorAll(`[data-gefest-id="${element.gefestId}"]`);
+ if (elementHTML.length > 0) {
+ const htmlElement = elementHTML[0] as HTMLElement;
+ htmlElement.onclick = element.onClick;
+ }
+ }
+ }
+
+ static async main(main : () => Promise): Promise {
+ await main();
+ await GefestEngine.activateOnClick();
+ }
+
+ static reRenderByGI (gefestId: string) {
+ const gefestElement = elementsMapping.get(gefestId);
+ if (!gefestElement) return;
+ const elementHTML = document.querySelectorAll(`[data-gefest-id="${gefestId}"]`);
+
+ if (elementHTML.length > 0) {
+ const htmlElement = elementHTML[0] as HTMLElement;
+ htmlElement.outerHTML = gefestElement.build();
+ GefestEngine.activateOnClick();
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/primitives/a.ts b/src/primitives/a.ts
index 1b84d1e..db05ced 100644
--- a/src/primitives/a.ts
+++ b/src/primitives/a.ts
@@ -1,9 +1,10 @@
import { GefestElement } from "../element";
export class GefestA extends GefestElement {
- constructor(content : (GefestElement | string)[] | string, href: string) {
+ constructor(content : (GefestElement | string)[] | string, href: string | null = null) {
super(content);
- this.setAttribute('href', href);
+ if (href !== null)
+ this.setAttribute('href', href);
}
protected wrapHTML (content: string): string {
diff --git a/src/primitives/header.ts b/src/primitives/header.ts
new file mode 100644
index 0000000..81f99f9
--- /dev/null
+++ b/src/primitives/header.ts
@@ -0,0 +1,16 @@
+import { GefestElement } from "../element";
+
+type HeaderLevel = 1 | 2 | 3 | 4 | 5 | 6;
+
+export class GefestHeader extends GefestElement {
+ protected level: HeaderLevel;
+ constructor(content : (GefestElement | string)[] | string, level : HeaderLevel = 1) {
+ super(content);
+ this.level = level;
+ }
+
+ protected wrapHTML (content: string): string {
+ const attrString = this.attributesToString();
+ return `${content} `;
+ }
+}
\ No newline at end of file
diff --git a/src/primitives/i.ts b/src/primitives/i.ts
new file mode 100644
index 0000000..c1d2c38
--- /dev/null
+++ b/src/primitives/i.ts
@@ -0,0 +1,12 @@
+import { GefestElement } from "../element";
+
+export class GefestI extends GefestElement {
+ constructor(content : (GefestElement | string)[] | string) {
+ super(content);
+ }
+
+ protected wrapHTML (content: string): string {
+ const attrString = this.attributesToString();
+ return `${content} `;
+ }
+}
\ No newline at end of file
diff --git a/src/primitives/img.ts b/src/primitives/img.ts
new file mode 100644
index 0000000..6398502
--- /dev/null
+++ b/src/primitives/img.ts
@@ -0,0 +1,13 @@
+import { GefestElement } from "../element";
+
+export class GefestImg extends GefestElement {
+ constructor(content : (GefestElement | string)[] | string, src: string) {
+ super(content);
+ this.setAttribute('src', src);
+ }
+
+ protected wrapHTML (content: string): string {
+ const attrString = this.attributesToString();
+ return ` ${content}`;
+ }
+}
\ No newline at end of file
diff --git a/src/primitives/small.ts b/src/primitives/small.ts
new file mode 100644
index 0000000..1842372
--- /dev/null
+++ b/src/primitives/small.ts
@@ -0,0 +1,12 @@
+import { GefestElement } from "../element";
+
+export class GefestSmall extends GefestElement {
+ constructor(content : (GefestElement | string)[] | string) {
+ super(content);
+ }
+
+ protected wrapHTML (content: string): string {
+ const attrString = this.attributesToString();
+ return `${content} `;
+ }
+}
\ No newline at end of file
diff --git a/src/primitives/span.ts b/src/primitives/span.ts
new file mode 100644
index 0000000..f049e88
--- /dev/null
+++ b/src/primitives/span.ts
@@ -0,0 +1,12 @@
+import { GefestElement } from "../element";
+
+export class GefestSpan extends GefestElement {
+ constructor(content : (GefestElement | string)[] | string) {
+ super(content);
+ }
+
+ protected wrapHTML (content: string): string {
+ const attrString = this.attributesToString();
+ return `${content} `;
+ }
+}
\ No newline at end of file
diff --git a/src/style.ts b/src/style.ts
index 2a8714b..c215635 100644
--- a/src/style.ts
+++ b/src/style.ts
@@ -1,5 +1,7 @@
+import { GefestElement } from "./element";
+
export abstract class GefestStyle {
- applyStyle (html: string): string {
+ applyStyle (html: string, element ?: GefestElement): string {
throw new Error('You need to implement the applyStyle method in the child class');
}
}
\ No newline at end of file