als-layout 2.0.0 → 2.1.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/lib/build-root.js +3 -2
- package/lib/elements/script.js +1 -1
- package/lib/layout.js +8 -6
- package/lib/onload.js +9 -0
- package/lib/{build-app.js → render/build-app.js} +7 -4
- package/lib/{component-hierarchy.js → render/component-hierarchy.js} +8 -0
- package/package.json +2 -2
- package/readme.md +22 -3
- package/tests/build-app.test.js +16 -5
- package/tests/component-hierarchy.test.js +1 -1
- package/tests/elements.test.js +5 -6
- package/tests/layout.test.js +50 -0
package/lib/build-root.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
const { buildFromCache, Root, parseHTML, cacheDoc } = require('als-document')
|
|
1
|
+
const { buildFromCache, Root, parseHTML, cacheDoc, Document } = require('als-document')
|
|
2
2
|
|
|
3
3
|
function buildRoot(item) {
|
|
4
4
|
const root =
|
|
@@ -8,7 +8,8 @@ function buildRoot(item) {
|
|
|
8
8
|
: new Root()
|
|
9
9
|
|
|
10
10
|
const firstNode = root.childNodes[0]
|
|
11
|
-
if(!firstNode || firstNode.tagName !== '!DOCTYPE') root.insert(1,/*html*/`<!DOCTYPE html>`)
|
|
11
|
+
if(!firstNode || firstNode.tagName !== '!DOCTYPE') {root.insert(1,/*html*/`<!DOCTYPE html>`)}
|
|
12
|
+
|
|
12
13
|
return root
|
|
13
14
|
}
|
|
14
15
|
|
package/lib/elements/script.js
CHANGED
|
@@ -9,7 +9,7 @@ function addScript(attrs = {}, innerHTML = '', head = true, version, layout) {
|
|
|
9
9
|
if(Object.keys(attrs).length || innerHTML) {
|
|
10
10
|
const script = new Node('script', attrs)
|
|
11
11
|
if(innerHTML) script.innerHTML = innerHTML
|
|
12
|
-
if (head) layout.
|
|
12
|
+
if (head) layout.head.insert(2, script)
|
|
13
13
|
else layout.body.insert(3, script)
|
|
14
14
|
}
|
|
15
15
|
return layout
|
package/lib/layout.js
CHANGED
|
@@ -3,9 +3,9 @@ const {
|
|
|
3
3
|
addKeywords, addStyle, addDescription, addTitle, addImage,
|
|
4
4
|
addUrl, addFavicon, addScript, addLink, charset, viewport
|
|
5
5
|
} = require('./elements/index')
|
|
6
|
-
const build$App = require('./build-app')
|
|
6
|
+
const build$App = require('./render/build-app')
|
|
7
7
|
const buildRoot = require('./build-root')
|
|
8
|
-
|
|
8
|
+
const onload = require('./onload')
|
|
9
9
|
class Layout {
|
|
10
10
|
constructor(layout, options = {}) {
|
|
11
11
|
this.options = options
|
|
@@ -17,6 +17,8 @@ class Layout {
|
|
|
17
17
|
this.utils = {}
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
onload() {this.script({},onload); return this}
|
|
21
|
+
|
|
20
22
|
get html() {
|
|
21
23
|
const lang = this.options.lang || 'en'
|
|
22
24
|
if (!this.root.$('html')) this.root.insert(2, `<html></html>`)
|
|
@@ -27,12 +29,12 @@ class Layout {
|
|
|
27
29
|
return htmlElement
|
|
28
30
|
}
|
|
29
31
|
get body() {
|
|
30
|
-
if (!this.root
|
|
31
|
-
return this.root
|
|
32
|
+
if (!this.root.$('body')) this.html.insert(2, `<body></body>`)
|
|
33
|
+
return this.root.$('body')
|
|
32
34
|
}
|
|
33
35
|
get head() {
|
|
34
|
-
if (!this.root
|
|
35
|
-
return this.root
|
|
36
|
+
if (!this.root.$('head')) this.html.insert(1, `<head></head>`)
|
|
37
|
+
return this.root.$('head')
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
update(element, done = []) {
|
package/lib/onload.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
module.exports = `document.addEventListener('DOMContentLoaded', function() {
|
|
2
|
+
const elements = document.querySelectorAll('[onload]');
|
|
3
|
+
elements.forEach(element => {
|
|
4
|
+
const onloadCode = element.getAttribute('onload');
|
|
5
|
+
const func = Function('"use strict"; return function() { ' + onloadCode + ' }');
|
|
6
|
+
func().call(element);
|
|
7
|
+
element.removeAttribute('onload');
|
|
8
|
+
});
|
|
9
|
+
});`
|
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
const componentHierarchy = require('./component-hierarchy')
|
|
2
2
|
module.exports = function(done=[],layout) {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
let strComponents = 'components:{}', updateFn = 'update:()=>{}'
|
|
4
|
+
if(done.length) {
|
|
5
|
+
strComponents = 'components:{'+componentHierarchy(done).map(componentName => {
|
|
6
|
+
return `"${componentName}":${layout.components[componentName].toString()}`
|
|
7
|
+
}).join(',')+'}'
|
|
8
|
+
updateFn = `update:function ${layout.update.toString()}`
|
|
9
|
+
}
|
|
7
10
|
const strData = 'data:'+JSON.stringify(layout.data)
|
|
8
11
|
const $ = 'function $(selector,parent = document) {return parent.querySelector(selector)}'
|
|
9
12
|
const $$ = 'function $$(selector,parent = document) {return [...parent.querySelectorAll(selector)]}'
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
function componentHierarchy(elements) {
|
|
2
|
+
|
|
2
3
|
const entries = elements
|
|
3
4
|
.map(el => ([el, el.getAttribute('component'), el.ancestors]))
|
|
4
5
|
.sort((a, b) => a[2].length - b[2].length)
|
|
@@ -6,6 +7,13 @@ function componentHierarchy(elements) {
|
|
|
6
7
|
const result = []
|
|
7
8
|
while(entries.length > 0) {
|
|
8
9
|
const [element, componentName, ancestors] = entries.shift()
|
|
10
|
+
if(element.getAttribute('part') !== null) {
|
|
11
|
+
element.removeAttribute('component')
|
|
12
|
+
element.removeAttribute('part')
|
|
13
|
+
continue
|
|
14
|
+
}
|
|
15
|
+
const partsInside = element.$$('[component][part]')
|
|
16
|
+
partsInside.forEach(element => { element.removeAttribute('part') });
|
|
9
17
|
entries.forEach(entry => {
|
|
10
18
|
if(entry[1] !== componentName) return
|
|
11
19
|
if(entry[2].includes(element)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "als-layout",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Html layout constructor",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"directories": {
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
"license": "ISC",
|
|
16
16
|
"dependencies": {
|
|
17
17
|
"als-css-parser": "^0.5.0",
|
|
18
|
-
"als-document": "^1.1
|
|
18
|
+
"als-document": "^1.2.1",
|
|
19
19
|
"als-simple-css": "^9.1.0"
|
|
20
20
|
}
|
|
21
21
|
}
|
package/readme.md
CHANGED
|
@@ -2,10 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
`Als-layout` is an HTML layout constructor for Node.js that allows you to manage HTML elements, styles, and scripts through JavaScript. It's perfect for server-side HTML generation or dynamic page modifications before delivery to the client.
|
|
4
4
|
|
|
5
|
-
## What's New
|
|
5
|
+
## What's New in 2.1.0
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
|
|
7
|
+
* updated als-document version
|
|
8
|
+
* [part] attribute for static components
|
|
9
|
+
* onload() method
|
|
10
|
+
* bugs fixed
|
|
9
11
|
|
|
10
12
|
## Install
|
|
11
13
|
|
|
@@ -124,3 +126,20 @@ Each component gets a `componentIndex` which is available inside the component f
|
|
|
124
126
|
```js
|
|
125
127
|
element.componentIndex
|
|
126
128
|
```
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
### parts
|
|
132
|
+
|
|
133
|
+
By default render method adds all used components to `$App.components`.
|
|
134
|
+
By adding `part` attribute component will not be added, except cases, the component is part of another component.
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
## onload
|
|
138
|
+
|
|
139
|
+
By adding onload attribute, you can run scripts for each element after dom content has loaded.
|
|
140
|
+
|
|
141
|
+
Example how it works:
|
|
142
|
+
```js
|
|
143
|
+
const layout = new Layout().charset().viewport().title('On load').onload()
|
|
144
|
+
layout.body.innerHTML = /*html*/`<div onload="this.innerHTML = 'new content'">original content</div>`
|
|
145
|
+
```
|
package/tests/build-app.test.js
CHANGED
|
@@ -1,22 +1,33 @@
|
|
|
1
1
|
const assert = require('assert');
|
|
2
2
|
const { describe, it } = require('node:test');
|
|
3
|
-
const build$App = require('../lib/build-app');
|
|
3
|
+
const build$App = require('../lib/render/build-app');
|
|
4
4
|
|
|
5
5
|
describe('build-$App Functionality', () => {
|
|
6
|
+
|
|
6
7
|
it('should handle empty input arrays correctly', () => {
|
|
7
8
|
const done = [];
|
|
8
9
|
const layout = {
|
|
9
10
|
components: {},
|
|
10
|
-
update: () => { }
|
|
11
11
|
};
|
|
12
12
|
const result = build$App(done, layout);
|
|
13
|
-
assert.strictEqual(result, 'window.$App = {browser:true,data:undefined,components:{},actions:{},utils:{},update:
|
|
13
|
+
assert.strictEqual(result, 'window.$App = {browser:true,data:undefined,components:{},actions:{},utils:{},update:()=>{},$:function $(selector,parent = document) {return parent.querySelector(selector)},$$:function $$(selector,parent = document) {return [...parent.querySelectorAll(selector)]}}');
|
|
14
14
|
});
|
|
15
15
|
|
|
16
16
|
it('should process multiple components correctly', () => {
|
|
17
|
+
const called = []
|
|
17
18
|
const done = [
|
|
18
|
-
{
|
|
19
|
-
|
|
19
|
+
{
|
|
20
|
+
getAttribute: (attr) => attr === 'component' ? 'comp1' : null,
|
|
21
|
+
removeAttribute(att) {called.push(att)},
|
|
22
|
+
$$(){return []},
|
|
23
|
+
ancestors:2
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
getAttribute: (attr) => attr === 'component' ? 'comp2' : null,
|
|
27
|
+
removeAttribute(att) {called.push(att)},
|
|
28
|
+
$$(){return []},
|
|
29
|
+
ancestors:2
|
|
30
|
+
}
|
|
20
31
|
];
|
|
21
32
|
const layout = {
|
|
22
33
|
components: {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
const { parseHTML } = require('als-document')
|
|
2
2
|
const assert = require('assert');
|
|
3
3
|
const { describe, it } = require('node:test')
|
|
4
|
-
const componentHierarchy = require('../lib/component-hierarchy')
|
|
4
|
+
const componentHierarchy = require('../lib/render/component-hierarchy')
|
|
5
5
|
|
|
6
6
|
function shuffleArray(array) {
|
|
7
7
|
for (let i = array.length - 1; i > 0; i--) {
|
package/tests/elements.test.js
CHANGED
|
@@ -18,11 +18,10 @@ describe('Charset tests', () => {
|
|
|
18
18
|
});
|
|
19
19
|
|
|
20
20
|
it('should insert charset at second position if not present', () => {
|
|
21
|
-
layout.head.insert(1, new SingleNode('title', {}));
|
|
22
21
|
const charset = 'UTF-8';
|
|
23
22
|
layout.charset(charset);
|
|
24
|
-
assert.strictEqual(layout.head.childNodes[
|
|
25
|
-
assert.strictEqual(layout.head.childNodes[
|
|
23
|
+
assert.strictEqual(layout.head.childNodes[0].tagName, 'meta', 'Meta should be at second position');
|
|
24
|
+
assert.strictEqual(layout.head.childNodes[0].getAttribute('charset'), charset, 'Charset not set correctly');
|
|
26
25
|
});
|
|
27
26
|
|
|
28
27
|
it('should add charset correctly', () => {
|
|
@@ -37,7 +36,7 @@ describe('Favicon tests', () => {
|
|
|
37
36
|
|
|
38
37
|
beforeEach(() => {
|
|
39
38
|
layout = new Layout();
|
|
40
|
-
layout.head.insert(1, new SingleNode('title', {})); // Добавляем элемент title для проверки позиции
|
|
39
|
+
// layout.head.insert(1, new SingleNode('title', {})); // Добавляем элемент title для проверки позиции
|
|
41
40
|
});
|
|
42
41
|
|
|
43
42
|
it('should update favicon href if link[rel="icon"] already exists', () => {
|
|
@@ -51,8 +50,8 @@ describe('Favicon tests', () => {
|
|
|
51
50
|
it('should insert favicon at second position if not present', () => {
|
|
52
51
|
const faviconHref = 'favicon.ico';
|
|
53
52
|
layout.favicon(faviconHref);
|
|
54
|
-
assert.strictEqual(layout.head.childNodes[
|
|
55
|
-
assert.strictEqual(layout.head.childNodes[
|
|
53
|
+
assert.strictEqual(layout.head.childNodes[0].tagName, 'link', 'Favicon link should be at second position');
|
|
54
|
+
assert.strictEqual(layout.head.childNodes[0].getAttribute('href'), faviconHref, 'Favicon href not set correctly');
|
|
56
55
|
});
|
|
57
56
|
|
|
58
57
|
it('should add favicon correctly', () => {
|
package/tests/layout.test.js
CHANGED
|
@@ -31,6 +31,12 @@ describe('Layout Integrative tests', () => {
|
|
|
31
31
|
assert(layout.root.$('title') === null, 'Title element was not removed correctly');
|
|
32
32
|
});
|
|
33
33
|
|
|
34
|
+
it('onload', () => {
|
|
35
|
+
layout.onload()
|
|
36
|
+
const script = layout.root.$('script')
|
|
37
|
+
assert(script.innerHTML.includes(`document.addEventListener('DOMContentLoaded'`))
|
|
38
|
+
})
|
|
39
|
+
|
|
34
40
|
});
|
|
35
41
|
|
|
36
42
|
describe('HTML Structure Initialization', () => {
|
|
@@ -124,6 +130,49 @@ describe('Component Updating', () => {
|
|
|
124
130
|
assert(data.test === 'hello')
|
|
125
131
|
})
|
|
126
132
|
|
|
133
|
+
it('should add part to $App if it is component`s descendant', () => {
|
|
134
|
+
layout.body.innerHTML = /*html*/`
|
|
135
|
+
<div component="parent">
|
|
136
|
+
<div component="child" part></div>
|
|
137
|
+
</div>`;
|
|
138
|
+
layout.components.parent = (element, $App) => {};
|
|
139
|
+
layout.components.child = (element, $App) => {};
|
|
140
|
+
|
|
141
|
+
const rawHtml = layout.render();
|
|
142
|
+
const script = rawHtml.match(/<[^>]*?script.*?>[^<]*<\/[^>]*?script.*?>/g)[0]
|
|
143
|
+
.replace('<script>window.$App = ', '')
|
|
144
|
+
.replace('</script>', '')
|
|
145
|
+
const fn = new Function(`return ${script}`)
|
|
146
|
+
|
|
147
|
+
const {components} = fn()
|
|
148
|
+
assert(components.parent !== undefined)
|
|
149
|
+
assert(components.child !== undefined)
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
it('should not add part to $App if it has no component ancestor', () => {
|
|
153
|
+
layout.body.innerHTML = /*html*/`
|
|
154
|
+
<div component="comp1"></div>
|
|
155
|
+
<div component="comp2" part></div>
|
|
156
|
+
`;
|
|
157
|
+
layout.components.comp1 = (element, $App) => {};
|
|
158
|
+
layout.components.comp2 = (element, $App) => {};
|
|
159
|
+
|
|
160
|
+
const rawHtml = layout.render();
|
|
161
|
+
const script = rawHtml.match(/<[^>]*?script.*?>[^<]*<\/[^>]*?script.*?>/g)[0]
|
|
162
|
+
.replace('<script>window.$App = ', '')
|
|
163
|
+
.replace('</script>', '')
|
|
164
|
+
const fn = new Function(`return ${script}`)
|
|
165
|
+
|
|
166
|
+
const {components} = fn()
|
|
167
|
+
assert(components.comp1 !== undefined)
|
|
168
|
+
assert(components.comp2 === undefined)
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it('If no components, should add empty update function to $App', () => {
|
|
172
|
+
const rawHtml = layout.render();
|
|
173
|
+
assert( rawHtml.includes('update:()=>{}'));
|
|
174
|
+
});
|
|
175
|
+
|
|
127
176
|
});
|
|
128
177
|
|
|
129
178
|
describe('Cache and Clone Testing', () => {
|
|
@@ -158,3 +207,4 @@ describe('Version method tests', () => {
|
|
|
158
207
|
assert.strictEqual(layout.v, version, 'Version should be set correctly');
|
|
159
208
|
});
|
|
160
209
|
});
|
|
210
|
+
|