diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..0fb2b2e
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,13 @@
+root = true
+
+[*]
+charset = utf-8
+end_of_line = lf
+insert_final_newline = true
+trim_trailing_whitespace = true
+indent_style = space
+indent_size = 4
+tab_width = 4
+
+[*.md]
+trim_trailing_whitespace = false
\ No newline at end of file
diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml
new file mode 100644
index 0000000..5158943
--- /dev/null
+++ b/.github/workflows/pr-checks.yml
@@ -0,0 +1,30 @@
+name: PR checks
+
+on:
+ pull_request:
+ branches: [ master ]
+ types: [ opened, synchronize ]
+
+permissions:
+ contents: write
+
+jobs:
+ lint:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ - uses: actions/setup-node@v6
+ with:
+ node-version: 25
+ - run: npm install
+ - run: npm run lint
+
+ test:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v6
+ - uses: actions/setup-node@v6
+ with:
+ node-version: 25
+ - run: npm install
+ - run: npm run test
diff --git a/.gitignore b/.gitignore
index 23c4d09..f534a85 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,3 @@
/node_modules
*.min.js
-devdoc
\ No newline at end of file
+/tmp
diff --git a/API.md b/API.md
deleted file mode 100644
index b6fbd84..0000000
--- a/API.md
+++ /dev/null
@@ -1,68 +0,0 @@
-# HTML Artisan *v2.1.0* API Reference
-
-## Content
-
- 1. [HtmlArtisan()](#htmlartisan)
- 2. [HtmlArtisan.fixConflict()](#fixconflict)
- 3. [HtmlArtisan.version](#version)
-
----
-
-### `HtmlArtisan(tag [, attributes, children, callback])`
-Creates an HTML element with the desired tag and attributes, and attaches the desired children.
-
-#### Parameters
- - **tag** *{string}*
- The HTML tag for the element. Defaults to 'div'.
-
- - **attributes** *{Object}* (optional)
-A map containing pairs of attributeName: attributeValue.
-Valid attributes are:
- - **Any valid HTML attribute**, including data-* attributes.
-CSS classes can be passed as either 'className' or 'class'.
-The 'style' attribute can be passed as a string, or a map containing pairs of cssProperty: cssValue.
- - **'events'**: a map of event handlers. E.g. {click: function() {...}, mouseover: function() {...}}
- - **'if'**: a condition that will determine whether or not this element (and all its children) should be rendered. It can be an expression, a boolean value or a function.
- - **'callback'**: a function that will be called once the element and all its children are created, immediately before returning the element. In this function's environment, *'this'* is the element that has been created (with all children already attached, too). The created element is also passed as an argument to the callback function, so arrow functions can be used in this context, too.
-
- - **children** *{Array[]|HTMLElement|Function|string}* (optional)
- An array of children, or a single child.
- If an array of children is passed, each child element can be expressed or passed in a variety of ways:
- - With a DOM element object, as in `document.createElement(...)`
- - With an array representing another HtmlArtisan element (HtmlArtisan array expression): `[tag, attributes, children]`.
- - With a function returning an element or an array of elements. The element or elements returned by a generator function can be, again, expressed in any of the formats accepted by HtmlArtisan (HTMLElement, string, HtmlArtisan array expression, and even another generator function; a combination of any number of these formats is also possible).
- - With a string. In this case, a child text node will be created.
-
- These different ways can be combined as needed to build our children arrays.
- Moreover, instead of passing an array of children, you can pass a single child element. In this case, the amount of ways to express that child element are more limited:
- - With a DOM element object.
- - With a function returning an element or an array of elements.
- - With a string. In this case, a child text node will be created.
-
- If you want to pass a single array representing another HtmlArtisan element, you have to enclose it in another array: `[ [tag, attributes, children] ]`
-
-- **callback** *{Function}* (optional)
-This callback function works exactly the same as the one that can be passed in the 'attributes' parameter. If both of them are defined, this function will override the one from 'attributes'.
-
-#### Returns
-The created HTML element with all its children and attributes already attached.
-
----
-
-### `HtmlArtisan.fixConflict([removeAll])`
-Fixes conflicts concerning the use of the `HtmlArtisan` and/or `h` namespaces by restoring the state of said namespaces to the values they had before HtmlArtisan took control of them.
-
-#### Parameters
- - **removeAll** *{boolean}* (optional, default `false`)
- If `false`, only the `h` alias will be restored.
- If `true`, `HtmlArtisan` will also be restored.
-
-#### Returns
-The `HtmlArtisan` object, so it can be assigned to a new variable.
-
----
-
-### `HtmlArtisan.version`
-A static property containing the version of HTML Artisan that is being used.
-
----
diff --git a/README.md b/README.md
index 715363e..a462b33 100644
--- a/README.md
+++ b/README.md
@@ -8,39 +8,30 @@
-**Key aspects**
-
+
+- [What is HTML Artisan?](#introduction)
+ - [Why use HTML Artisan?](#why)
+ - [Browser compatibility](#compatibility)
+- [Installation](#installation)
+ - [`npm` package](#installation-npm)
+ - [Standalone](#installation-standalone)
+- [Usage](#usage)
+ - [Examples](#examples-intro)
+ - [`HtmlArtisan` and `h()`](#htmlartisan-object)
+ - [Basic usage](#basic-usage)
+ - [Defining attributes](#attributes)
+ - [Creating children](#children)
+ - [Using callbacks](#callbacks)
+- [Building from source](#building)
+- [HTML Artisan's present and future](#outro)
+- [Bug reporting](#bug-reporting)
+
+
-# HTML Artisan *v2.1.0*
+
+# HTML Artisan *v3.0.0*
***HTML Artisan*** is a lightweight JS library for the dynamic, simple and easy-to-read generation of complex HTML structures.
@@ -51,17 +42,17 @@ With ***HTML Artisan***, you will be able to create very **readable**, **underst
So you can say good bye to code like this...
```javascript
-var div = document.createElement('div');
+let div = document.createElement('div');
div.className = 'my-div';
div.setAttribute('data-my-attr', 'my-data');
-var child = document.createElement('div');
+let child = document.createElement('div');
child.className = 'child';
child.addEventListener('click', function() {
console.log('clicked');
});
-var subChild = document.createElement('p');
+let subChild = document.createElement('p');
subChild.innerHTML = 'This is my text';
-var anotherFirstLevelChild = document.createElement('div');
+let anotherFirstLevelChild = document.createElement('div');
anotherFirstLevelChild.className = 'sublevel'
child.appendChild(subChild);
div.appendChild(child);
@@ -74,30 +65,31 @@ And hello to code like this!
h('div', {
'class': 'my-div',
'data-my-attr': 'my-data',
- events: {click: function () { console.log('clicked'); }}
+ events: { click: () => { console.log('clicked'); } },
}, [
['div', {'class': 'child'}, [
- ['p', null, 'This is my text']
+ ['p', null, 'This is my text'],
]],
- ['div', {'class': 'sublevel'}]
+ ['div', {'class': 'sublevel'}],
]);
```
***HTML Artisan*** code is versatile and easy to read and maintain, as it resembles a well organized tree structure.
-
-## Why should I use HTML Artisan?
-- **Very light**: the library is about *~1.14 KB* in its minified release version.
+## Why use HTML Artisan?
+
+- **Very light**: the library is about *~1.2 KiB* in its minified, standalone version.
- Keeps all the **performance** benefits of vanilla JS but with a **prettier syntax**. HTML structures are generated using core JS functionality, so it's very **efficient** and **fast**.
- Produces **structured**, **easy to read** and **maintainable** code.
- Can be **easily combined** with other libraries to boost productivity.
- It's ideal for small dynamic projects where using a front end framework like Vue.js or React would be overkill.
-
+
## Browser compatibility
+
HTML Artisan is built to be compatible with all major browsers, on both desktop and mobile devices. HTML Artisan is guaranteed to work in the following browsers, in which it has been thoroughly tested (compatibility is not, however, limited to this list):
- Google Chrome >= 85
@@ -106,72 +98,145 @@ HTML Artisan is built to be compatible with all major browsers, on both desktop
- Safari >= 14
- Opera >= 71
-**Attention**: support for Internet Explorer and older versions of major browsers has been dropped starting at v2.0.0. If you need to use HtmlArtisan on Internet Explorer, or an older, non-ES6-compliant version of any other browser, you should use [version 1.3.0](https://github.com/gabrielrf96/html-artisan/releases/tag/v1.3.0). Be aware, however, that v1.3.0 is discontinued and will not receive future improvements and/or new functionalities.
+Note that minimum required browser versions will be higher if you use HTML Artisan as a JS module directly, without a bundler or similar tool.
+**Older browsers**: Internet Explorer and older versions of major browsers are no longer supported. If you want to use HtmlArtisan in any of them, you can use [version 1.3.0](https://github.com/gabrielrf96/html-artisan/releases/tag/v1.3.0). Be aware, however, that v1 is discontinued and will never receive any further updates.
-
-## How to use?
-
-### Installation
-You have two options to install HTML Artisan:
+
+
+# Installation
+
+You have two options to get HTML Artisan: as an `npm` package, or as a standalone, minified JS file.
+
+
+
+## `npm` package
-1. Download the JS file directly from the [Releases](https://github.com/gabrielrf96/html-artisan/releases) section in the GitHub repo.
+This is the recommended way. Install using `npm`:
-2. Install using `npm`:
```shell
npm i html-artisan
```
+And import like this:
+
+```javascript
+import { HtmlArtisan, h } from 'html-artisan';
+```
+
+As you will see a bit later, `HtmlArtisan` is the main object, and `h()` is a convenient alias for `HtmlArtisan.build()`.
+
+
+
+## Standalone
+
+Download the minified JS file directly from the [Releases](https://github.com/gabrielrf96/html-artisan/releases) section in the GitHub repo, and include it in your HTML as you would any other JS file.
+
+The standalone version will declare `HtmlArtisan` and `h` globally (in `window`). It also provides an extra method `HtmlArtisan.fixConflict()` that is not present in the `npm` package version, intended to fix naming conflicts, in the case that you use some other library that also declares `HtmlArtisan` and/or `h` globally.
+
+
+
+
+# Usage
+
-### Examples
-In this repository, you will find a set of [examples](examples) that depict the basic use of the library, and its main functionalities and utilities. To get started you can check the basic example [here](examples/basic.html).
-You can also dive directly into the source code [here](src/htmlartisan.js).
+## Examples
+
+In this repository, you will find a set of [examples](examples) that depict the basic usage of the library, and its main functionalities and utilities. To get started you can check the basic example [here](examples/basic.html).
-### The HtmlArtisan object
-Once included in your project, ***HTML Artisan*** will create a namespace/object containing all functionality. This is the `HtmlArtisan` object.
-***HTML Artisan*** will also create a short, convenient alias for this object: `h`
+## `HtmlArtisan` and `h()`
+`HtmlArtisan` is the main object of this library, containing:
+- The `build()` method, which provides the main HTML-building functionality.
+- 2 properties, `author` and `version`, which just contain that plain information.
+
+`h()` is an alias pointing to `HtmlArtisan.build()`. Both of them do exactly the same in exactly the same way, so you can use whichever you prefer. That said, however, the recommended and most convenient way of using the library is via the alias function `h()`.
+
+If you installed HTML Artisan as an `npm` package, you will have to import the object or alias:
+
+```javascript
+import { h } from 'html-artisan';
+
+// or
+import { HtmlArtisan } from 'html-artisan';
+```
+
+If you are using the standalone version, both `HtmlArtisan` and the alias function `h()` will already be defined automatically in the global `window` context as soon as you include the JS file in your HTML.
-### Namespace conflict
-If this convenience alias causes a conflict in your project, because the `h` namespace needs to be used by some other library (or simply because you don't fancy that name, or want to use it for another global variable), you can easily fix this conflict with `HtmlArtisan.fixConflict()`.
-You can either replace the alias with a new one of your choosing:
+### Namespace conflicts
+
+If you're using the `npm` package, naming conflicts are not really an issue, as you can import using aliases if two things have the same name.
+
+However, in the standalone version, it is possible that you're already using some other library that declares either `HtmlArtisan` or, even more likely, `h`. In that case, and as long as HTML Artisan is loaded last, you can use `HtmlArtisan.fixConflict()` to restore the previous values of `HtmlArtisan` and `h`, and assign `HtmlArtisan` to a new home.
+
+```html
+
+
+
+
+
+```
+
```javascript
-var ht = HtmlArtisan.fixConflict();
-// Now 'h' doesn't exist (or has been restored to its original self).
-// HtmlArtisan, however, still exists
+// But we can fix it!
+
+const Artisan = HtmlArtisan.fixConflict();
+
+// Now, HTML Artisan can be referenced as `Artisan` instead.
+
+// In addition, `fixConflict()` has restored `HtmlArtisan` and `h` to the values
+// that the previous library, `some-other-library-using-the-same-awesome-names.js`, had set!
```
-Or you can just completely remove all HtmlArtisan variables from the global scope (both `HtmlArtisan` and `h`) by calling `fixConflict` with its `removeAll` parameter set to `true`.
+If you want to fix naming conflicts but still have a convenient alias, you can pass a new alias name
+to the `fixConflict()` method, which will declare a new alias in the global `window` context:
+
```javascript
-var ht = HtmlArtisan.fixConflict(true);
-// Now 'HtmlArtisan' and 'h' don't exist (or have been restored to their original selves)
+const Artisan = HtmlArtisan.fixConflict('ht');
+
+// Now, HTML Artisan can be referenced as `Artisan`, and a new alias `ht` exists, pointing at `HtmlArtisan.build()`.
+
+// Same as before, the original `HtmlArtisan` and `h` set by the other library are also restored.
+
```
-
-### Basic usage
-The ***HTML Artisan*** object itself is a function besides being its own namespace. Calling the `HtmlArtisan` object as a function will unleash its HTML-generating power.
+
+
+## Basic usage
-Remember, calling `HtmlArtisan` is equivalent to calling its alias, `h`.
+The most important thing to learn about HTML Artisan is, of course, how to build HTML, which is its whole purpose.
+
+In order to do that, you can call either the `HtmlArtisan.build()` method or the `h()` alias function. As mentioned before, both of them do exactly the same, and have the same arguments. The alias function is the preferred way, though, so we will use only that from this point forward in the examples.
+
+`h()` receives 4 arguments:
+- `tag`: the HTML tag for the created element.
+- `attributes`: an optional map of HTML attributes for the created element.
+- `children`: an optional list of child elements that we want appended to the element.
+- `callback`: we'll learn more about this a bit later, but it's also optional.
For example, this code:
```javascript
-h('div', {
- 'class': 'my-div',
- 'style': {color: '#333', width: '90%', margin: '0 auto'}
-}, [
- ['p', null, 'This a paragraph generated with HTML Artisan.'],
- ['p', null, 'HTML Artisan is so cool!'],
- ['p', null, [
- "Check out HTML Artisan creator's website at ",
- ['a', {href: 'https://www.gabrielrf.dev'}, 'www.gabrielrf.dev'],
- ]]
-]);
+h(
+ 'div',
+ {
+ class: 'my-div',
+ style: { color: '#333', width: '90%', margin: '0 auto' },
+ },
+ [
+ ['p', null, 'This a paragraph generated with HTML Artisan.'],
+ ['p', null, 'HTML Artisan is so cool!'],
+ ['p', null, [
+ "Check out HTML Artisan creator's website at ",
+ ['a', { href: 'https://www.gabrielrf.dev' }, 'www.gabrielrf.dev'],
+ ]],
+ ],
+);
```
Will generate this HTML structure:
@@ -186,27 +251,23 @@ Will generate this HTML structure:
```
-For an extended look into how `HtmlArtisan()` works (and its parameters) you can check the [API reference](API.md).
+Note that the `children` can be nested in any way we need, which will produce an entire HTML tree from a single call to `h()`. Don't worry about the format for now, we will dive deep into it in the next sections.
-
-### Advanced functionalities and tips
-***HTML Artisan*** comes with some convenient functionalities intended to boost your productivity.
+
----
+## Defining attributes
-
-#### Defining attributes
If provided, the `attributes` parameter will define a series of HTML attributes for our element.
This parameter acts like a map, where all the **keys** are the attribute **names**.
-So, for example, we can easily set an **id**, **class** and some custom **data** attributes for our element:
+So, for example, we can easily set an **`id`**, **`class`** and some custom **`data`** attributes for our element like this:
Code:
```javascript
h('div', {
id: 'super-container',
- 'class': 'my-div my-thing',
+ class: 'my-div my-thing',
'data-has-things': 'yas',
- 'data-foo': 'bar'
+ 'data-foo': 'bar',
});
```
@@ -215,29 +276,27 @@ Output:
```
-HTML Artisan will automatically decide whether each passed attribute should be assigned as an object property, or as an HTML element attribute. You need not worry about that.
-
-If you need to set an object property manually (because you need, for example, to set a method on the element you are creating), you can do so using callbacks. See the callbacks section for more information on this topic.
+If, for whatever reason, you need to set a property manually, you can do so using callbacks. See the callbacks section for more information on this topic.
Some things to take into account while defining attributes:
-- Since 'class' is a reserved keyword, it should be quoted.
-- CSS classes can be passed as either 'class' or 'className'.
+- CSS classes can be passed as either `class` or `className`, but for the moment only as a raw `string` of space-separated class names.
-- You can pass the 'style' attribute as either a *raw string* or a map of *cssProperty: cssValue*:
+- You can pass the `style` attribute as either a raw `string` or a map of `cssProperty: cssValue`:
```javascript
h('div', {
- 'style': 'margin: 20px; padding: 10px; border: 1px solid red; font-size: 16px;'
+ style: 'margin: 20px; padding: 10px; border: 1px solid red; font-size: 16px;',
});
+
// This is equivalent:
h('div', {
- 'style': {
+ style: {
margin: '20px',
padding: '10px',
border: '1px solid red',
- 'font-size': '16px'
- }
+ 'font-size': '16px',
+ },
});
```
@@ -250,113 +309,159 @@ Some things to take into account while defining attributes:
events: {
click: function(e) {
console.log('I have been clicked!');
- }
+ },
// ...
- }
+ },
});
```
- **if**: a condition that will determine whether or not this element (and all its children) should be rendered. It can be an expression, a boolean value or a function. To see how it works, you can [check out this example](examples/conditional_elements.html).
- - **callback**: a callback function that will be executed once the object (and all its children) has finished being created. For more information on this topic, check the callbacks section of the documentation.
-
----
+ - **callback**: a callback function that will be executed once the object is ready, and all its children (if any) have been appended to it. For more information on this topic, check the callbacks section of the documentation.
-#### Creating children
-As you have seen, `HtmlArtisan()` accepts a `children` parameter.
-You can pass a **single string / function / DOM element**, or an **array of child elements**.
+## Creating children
-##### Passing a string / function / DOM element
-If you pass a single string, a single child text node will be created:
+As you have seen, `HtmlArtisan()` accepts a `children` parameter: a list of children that will be appended to the created element. Children can be defined in a variety of different formats in HTML Artisan.
-Code:
-```javascript
-h('p', null, 'This is a paragraph');
-// This is equivalent to passing the string as the only element of a children array:
-h('p', null, ['This is an equally valid way of creating a paragraph'])
-```
+If no children are passed (argument is omitted, or set to `null` or an empty `array`), then the created element will be empty (will have no child nodes).
-Output:
-```html
-This is a paragraph
+### Child formats
-This an equally valid way of creating a paragraph
-```
+Child formats are the different ways in which a child can be passed to `h()`. When you pass an array of children to `h()`, you can combine as many different formats as you need in the same call.
-Similarly, you can pass a single DOM element or a function as a single child element, without needing to enclose them
-in a children array. For more information about passing functions as children, see the next section.
+There are currently four accepted formats:
-##### Passing an array of children
-Children of an HTML-Artisan-defined element can be represented in a variety of ways:
+- **`string`**: will result in a single text node being appended to the element.
-- An **HTML element** that has been previously created:
+
+ Example
+ Code:
```javascript
- var childOne = document.createElement('div');
- var childTwo = document.createElement('p');
- var result = h('div', null, [childOne, childTwo]);
+ h('p', null, ['This is a paragraph'])
```
-- An **array representing another 'call' to HtmlArtisan()**:
+ Output:
+ ```html
+ This is a paragraph
+ ```
+
- This is what we call an **HTML Artisan array expression**. It is the result of enclosing the parameters you would pass to HtmlArtisan() in an array. For example:
+---
+
+- **Generator `function`**: you can pass a function that returns a single child or array of children, and whatever the generator function returns will be appended as children of the created element. The children returned by a generator function can be in any combination of the accepted child formats.
- If the call to create an element was like this:
+
+ Example
```javascript
- h('div', {'class': 'my-div'}, 'This is text content!')
+ let elems = ['1', '2', '3', '4', '5'];
+ let element = h('ul', null, [
+ () => {
+ let result = [];
+ for (let i = 0; i < elems.length; i++) {
+ result.push( h('li', null, elems[i]) );
+ }
+ return result;
+ }
+ ]);
```
- Then, the equivalent HTML Artisan array expression, which could be passed as a child of another element, would be like this:
+ The element or elements returned by the generator function can be expressed in any of the accepted child formats:
+ - An HTML element.
+ - A single string.
+ - An array representing another call to `h()` (an ***HTML Artisan array expression***). If you intend to return a single HTML Artisan array expression, it will need to be enclosed in additional array brackects. Otherwise, HTML Artisan will have no way to know if it's an array expression, or an array of children.
+ - Another generator function (nesting is possible).
+
+ Of course, any combination of these formats is also accepted within the same generator function.
+
+ See the ***child_functions*** example file for more information.
+
+
+---
+
+- **`HTMLElement`**: an existing HTML element node, which will just be appended normally without any processing.
+
+
+ Example
```javascript
- ['div', {'class': 'my-div'}, 'This is text content!']
+ let childOne = document.createElement('div');
+ let childTwo = document.createElement('p');
+ let result = h('div', null, [childOne, childTwo]);
```
+
- Here is an example of how to pass this "HTML Artisan array expressions" as children of other elements created via HtmlArtisan:
+---
+
+- **HTML Artisan array expression**: an array representing a new call to `h()`, containing the same four arguments that `h()` expects.
+
+
+ Example
+
+ If the call to create an element is like this:
```javascript
- var element = h('div', null, [
- ['div', {'class': 'my-div'}] // if no children are passed, an empty element will be created
- ]);
+ h('div', {class: 'my-div'}, 'This is text content!')
```
-- A **function** that returns an element or an array of elements:
+ Then, the equivalent HTML Artisan array expression, which could be passed as a child of another element, would be like this:
- This is what we call a **generator function**.
+ ```javascript
+ ['div', {class: 'my-div'}, 'This is text content!']
+ ```
+ Here is an example of how to pass these "HTML Artisan array expressions" as children of other elements created via HtmlArtisan:
```javascript
- var elems = ['1', '2', '3', '4', '5'];
- var element = h('ul', null, [
- function() {
- var result = [];
- for (var i = 0; i < elems.length; i++) {
- result.push( h('li', null, elems[i]) );
- }
- return result;
- }
+ let element = h('div', null, [
+ ['div', {class: 'my-div'}, 'This is text content!'],
+ ['div', {class: 'other-div'}, 'This is other text content!'],
]);
```
- The element or elements returned by the generator function can be expressed in any of the ways accepted by HTML Artisan:
- - An HTML element.
- - A single string.
- - An array representing another 'call' to HtmlArtisan (an ***HTML Artisan array expression***). If you intend to return a single HTML Artisan array expression, it will need to be enclosed in additional Array brackects. Otherwise, HTML Artisan will have no way to know if it's an array expression, or an array of children.
- - Another generator function (nesting is possible).
+ Output:
+ ```html
+
+
This is text content!
+
This is other text content!
+
+ ```
+
- Of course, any combination of these formats is also accepted within the same generator function.
- See the ***child_functions*** example for more information.
+### Passing a single child
-- A **string**. Like we said earlier, passing a string will create a text child node:
+If you only want to append a single child, you can pass it as a single value without enclosing it in an array. This is possible with all child formats except for *HTML Artisan array expressions*, which must **always** be enclosed in an array, even if it's a single one. That's because, otherwise, it would be very difficult for HTML Artisan to know whether an array in the `children` argument should be treated as a single child, or as an array of children.
- ```javascript
- h('p', null, ['This is a paragraph!']);
- ```
+Therefore, a single `string`, generator `function`, or `HTMLElement` may be passed to `h()`:
-When creating an element via HtmlArtisan, you can pass children in any of the formats above, and you can also use any combination of them as needed.
+```javascript
+// All of these are valid:
+let withSingleString = h('p', null, 'This is a paragraph');
+
+let withSingleGeneratorFunction = h('ul', null, () => {
+ let result = [];
+ for (let i = 0; i < 10; i++) {
+ result.push( h('li', null, `${i}`) );
+ }
+ return result;
+});
+
+let htmlElement = document.createElement('div');
+let withSingleHtmlElement = h('div', null, htmlElement);
+
+// But if you want a single HTML Artisan array expression,
+// this is the only valid way to do it (note the enclosing array):
+let withSingleHtmlArtisanArrayExpression = h('div', null, [
+ ['div', {class: 'my-div'}, 'This is text content!'],
+]);
+```
+
+### Combining child formats
+
+You can pass children in any of the accepted child formats previously described, and you can also use any combination of them as needed.
Let's imagine, for instance, that you need to create an element with the following children:
@@ -364,47 +469,45 @@ Let's imagine, for instance, that you need to create an element with the followi
- A list of paragraphs containing texts from an array.
- A final paragraph with a custom text.
-You could then call HtmlArtisan like this:
+You could do that like this:
```javascript
-var elems = ['Text 1', 'Text 2', 'Text 3', 'Text 4', 'Text 5'];
-var element = h('div', null, [
+let texts = ['Text 1', 'Text 2', 'Text 3', 'Text 4', 'Text 5'];
+let element = h('div', null, [
'A simple text node',
- function() {
- return elems.map(txt => h('p', null, txt));
- },
- ['p', null, 'A final paragraph with a custom text.']
+ () => texts.map(txt => h('p', null, txt)),
+ ['p', null, 'A final paragraph with a custom text.'],
]);
```
If you are able to use the spread operator in your development environment, that generator function can be completely replaced like this:
```javascript
-var elems = ['Text 1', 'Text 2', 'Text 3', 'Text 4', 'Text 5'];
-var element = h('div', null, [
+let texts = ['Text 1', 'Text 2', 'Text 3', 'Text 4', 'Text 5'];
+let element = h('div', null, [
'A simple text node',
- ...elems.map(txt => h('p', null, txt)),
- ['p', null, 'A final paragraph with a custom text.']
+ ...texts.map(txt => h('p', null, txt)),
+ ['p', null, 'A final paragraph with a custom text.'],
]);
```
-You might still want tu use generator functions for more complex logics, though.
-
----
+You might still want to use generator functions for more complex logic, though.
-#### Using callbacks
+
+## Using callbacks
+
Sometimes, you need to do some special post-processing on the elements you have created.
-Using the DOM API, this is very simple, since every object is created in a top-level environment, and are accessible if you need them:
+Using the DOM API directly, this is very simple, since every object is created in a top-level environment, and are accessible if you need them:
```javascript
-var elem1 = document.createElement('div');
-var child = document.createElement('p');
+let elem1 = document.createElement('div');
+let child = document.createElement('p');
elem1.appendChild(child);
-// 'child' is accessible, we can do some extra coding on it:
+// 'child' is accessible, we can do some extra operations on it:
child.myMethod = function() {
// ...
}
@@ -419,7 +522,7 @@ Yes, the code is well-structured and easy to read... but what if you need to tou
To solve that issue, we have **callback** functions. A callback function defined for an HTML Artisan element will be executed once the element has been created. An HTML Artisan element is considered 'created' once:
- The element itself has been created.
-- All attributes, standard and special, have been attached to it.
+- All attributes, standard and special, have been processed and/or attached to it.
- All defined children hierarchy has been created and already appended to the element.
So, if you define a callback function for an element, you can access that element and do some post-processing on it **when it has already been created and has all its beautiful children already appended to it**.
@@ -432,8 +535,8 @@ A callback function can be defined in two ways:
h('div', {
callback: function() {
//...
- }
- })
+ },
+ });
```
- By passing a function directly into the `callback` parameter:
@@ -441,28 +544,28 @@ A callback function can be defined in two ways:
```javascript
h('div', null, null, function() {
//...
- })
+ });
```
-If both ways are used at the same time, the second one will prevail (and it will be the only one to be executed).
+If both ways are used at the same time, the second one will have priority (and it will be the only one to be executed).
In the scope of an HTML Artisan callback function, `this` is the created element whose callback function we're defining. Here's a quick example of this:
```javascript
-h('div', {'class': 'container'}, [
- ['p', {'class': 'first-level-child'}, [
- ['a', {'class': 'second-level-child'}, null, function() {
+h('div', { class: 'container' }, [
+ ['p', { class: 'first-level-child' }, [
+ ['a', { class: 'second-level-child' }, null, function() {
// 'this' is the 'second-level-child' element
this.specialMethod = function() {
// ...
- }
+ };
- this.getSomethingUseless = function() { return null; }
+ this.getSomethingUseless = function() { return null; };
// Some more post-processing
// ...
- }]
- ]]
+ }],
+ ]],
]);
```
@@ -473,37 +576,37 @@ function postProcessLink() {
// 'this' is the 'second-level-child' element
this.specialMethod = function() {
// ...
- }
+ };
- this.getSomethingUseless = function() { return null; }
+ this.getSomethingUseless = function() { return null; };
// Some more post-processing
// ...
}
-h('div', {'class': 'container'}, [
- ['p', {'class': 'first-level-child'}, [
- ['a', {'class': 'second-level-child'}, null, postProcessLink]
- ]]
+h('div', { class: 'container' }, [
+ ['p', { class: 'first-level-child' }, [
+ ['a', { class: 'second-level-child' }, null, postProcessLink],
+ ]],
]);
```
Since v2.0.0, the created element is also passed as an argument to the callback function, so arrow functions are also suitable for callbacks:
```javascript
-h('div', {'class': 'container'}, [
- ['p', {'class': 'first-level-child'}, [
- ['a', {'class': 'second-level-child'}, null, elem => {
+h('div', { class: 'container' }, [
+ ['p', { class: 'first-level-child' }, [
+ ['a', { class: 'second-level-child' }, null, elem => {
elem.specialMethod = function() {
// ...
- }
+ };
- elem.getSomethingUseless = function() { return null; }
+ elem.getSomethingUseless = function() { return null; };
// Some more post-processing
// ...
- }]
- ]]
+ }],
+ ]],
]);
```
@@ -511,18 +614,19 @@ As it was also shown before, the callback could also be passed within the
attribute map:
```javascript
-h('div', {'class': 'container'}, [
- ['p', {'class': 'first-level-child'}, [
- ['a', {'class': 'second-level-child', callback: postProcessLink}, null]
- ]]
+h('div', { class: 'container' }, [
+ ['p', { class: 'first-level-child' }, [
+ ['a', { class: 'second-level-child', callback: postProcessLink }, null],
+ ]],
]);
```
----
-## Building from source
-Since v2.0.0, HtmlArtisan uses the **webpack** bundler. To build HtmlArtisan from source, you will need to have **Node.js** and **npm** already installed.
+
+# Building from source
+
+Since v2.0.0, HtmlArtisan uses the **webpack** bundler. To build HTML Artisan from source as a standalone, minified JS file, you will need to have **`node`** and **`npm`** already installed.
Once those requirements are met, the first step is cloning the repository:
@@ -540,29 +644,25 @@ npm run build
Now, you will find the minified, production-ready JS file right in the `dist` directory, at the root of the repository. This is the exact same file that you can download from the [Releases](https://github.com/gabrielrf96/html-artisan/releases) section on the project's GitHub page.
----
-## HTML Artisan's present and future
-I'm still working to improve **HTML Artisan** and bring new functionalities and utilities.
-
-### Currently working on / future improvements
+# HTML Artisan's present and future
+
HTML Artisan has reached a point where I'm satisfied with its current functionalities and architecture, as it meets all my needs perfectly.
However, I'm regularly using this library in my own projects, and I'm always looking out for possible bugs and improvements, and thinking of new functionalities to implement.
-Therefore, while there's nothing currently in the works regarding HTML Artisan, its development is well alive and will continue in the future. Besides, it's always open to suggestions and pull requests, so if you want to collaborate, don't be shy!
+Therefore, while there's nothing currently in the works regarding HTML Artisan, its development is still alive and will continue in the future. Besides, it's always open to suggestions and pull requests, so if you want to collaborate, don't be shy!
-
-### Bug reporting and contact
-If you experience any kind of trouble while using this library, please feel free to contact me to report any bugs or problems.
-You can contact me by:
+
-- Opening an **issue on GitHub**
-- Sending me an e-mail to **contact@gabrielrf.dev**
-- DM me on Twitter: **Gabri239**
+# Bug reporting
+
+If you experience any kind of trouble while using this library, please feel free to contact me to report any bugs or problems.
+
+The preferred way to report bugs is by opening an **issue on GitHub**.
@@ -572,7 +672,6 @@ You can contact me by:
www.gabrielrf.dev
-
diff --git a/eslint.config.js b/eslint.config.js
new file mode 100644
index 0000000..5f2d103
--- /dev/null
+++ b/eslint.config.js
@@ -0,0 +1,31 @@
+import { defineConfig, globalIgnores } from 'eslint/config';
+import globals from 'globals';
+import js from '@eslint/js';
+
+export default defineConfig([
+ globalIgnores(['**/*.min.js']),
+ {
+ files: ['**/*.js'],
+ languageOptions: { globals: globals.browser },
+ plugins: {
+ js,
+ },
+ extends: ['js/recommended'],
+ rules: {
+ 'no-unused-vars': 'warn',
+ },
+ },
+ {
+ files: ['**/*.test.{js,ts,jsx,tsx}'],
+ languageOptions: { globals: globals.browser },
+ rules: {
+ 'no-restricted-properties': [
+ 'error',
+ {
+ property: 'innerText',
+ message: 'Prefer the `textContent` property. `innerText` is always `undefined` in JSDOM. See https://github.com/jsdom/jsdom/issues/1245',
+ },
+ ],
+ },
+ },
+]);
diff --git a/examples/basic.html b/examples/basic.html
index 7b5d965..52c5012 100644
--- a/examples/basic.html
+++ b/examples/basic.html
@@ -6,10 +6,10 @@
HtmlArtisan - Basic Test
-
+
-