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 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
 
@@ -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.root.head.insert(2, script)
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.body) this.html.insert(2, `<body></body>`)
31
- return this.root.body
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.head) this.html.insert(1, `<head></head>`)
35
- return this.root.head
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
- const strComponents = 'components:{'+componentHierarchy(done).map(componentName => {
4
- return `"${componentName}":${layout.components[componentName].toString()}`
5
- }).join(',')+'}'
6
- const updateFn = `update:function ${layout.update.toString()}`
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.0.0",
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.2",
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
- - **Rendering Capability**: Added the ability to render HTML dynamically, updating elements based on component attributes.
8
- - **Bug Fixes**: Various bug fixes improving stability and performance.
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
+ ```
@@ -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:function () => { },$:function $(selector,parent = document) {return parent.querySelector(selector)},$$:function $$(selector,parent = document) {return [...parent.querySelectorAll(selector)]}}');
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
- { getAttribute: () => 'comp1',ancestors:2 },
19
- { getAttribute: () => 'comp2',ancestors:2 }
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--) {
@@ -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[1].tagName, 'meta', 'Meta should be at second position');
25
- assert.strictEqual(layout.head.childNodes[1].getAttribute('charset'), charset, 'Charset not set correctly');
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[1].tagName, 'link', 'Favicon link should be at second position');
55
- assert.strictEqual(layout.head.childNodes[1].getAttribute('href'), faviconHref, 'Favicon href not set correctly');
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', () => {
@@ -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
+