neo.mjs 6.9.6 → 6.9.7
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/apps/ServiceWorker.mjs +2 -2
- package/apps/learnneo/neo-config.json +1 -0
- package/apps/learnneo/store/Content.mjs +52 -2
- package/apps/learnneo/view/home/ContentTreeList.mjs +34 -4
- package/apps/learnneo/view/home/MainContainer.mjs +17 -29
- package/apps/learnneo/view/home/MainContainerController.mjs +15 -25
- package/apps/learnneo/view/home/MainContainerModel.mjs +35 -0
- package/examples/ServiceWorker.mjs +2 -2
- package/examples/form/field/textarea/MainContainer.mjs +7 -1
- package/package.json +1 -1
- package/resources/data/learnneo/p/2023-10-01T18-29-19-158Z.md +96 -0
- package/resources/data/learnneo/p/2023-10-07T19-18-28-517Z.md +102 -0
- package/resources/data/learnneo/p/2023-10-08T20-20-07-934Z.md +75 -0
- package/resources/data/learnneo/p/2023-10-08T20-20-37-336Z.md +29 -0
- package/resources/data/learnneo/p/2023-10-08T20-37-30-658Z.md +0 -0
- package/resources/data/learnneo/p/2023-10-08T21-58-25-809Z.md +68 -0
- package/resources/data/learnneo/p/2023-10-08T22-22-11-013Z.md +0 -0
- package/resources/data/learnneo/p/2023-10-14T19-25-08-153Z.md +121 -0
- package/resources/data/learnneo/pages/whyneo.md +4 -0
- package/resources/data/learnneo/t.json +130 -0
- package/resources/deck/whyneo.md +80 -0
- package/resources/scss/src/apps/learnneo/Viewport.scss +57 -0
- package/src/DefaultConfig.mjs +2 -2
- package/src/form/field/TextArea.mjs +52 -0
- package/src/main/DomAccess.mjs +23 -0
- /package/resources/data/learnneo/{content.json → tree.json} +0 -0
package/apps/ServiceWorker.mjs
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
import ContentModel from '../model/Content.mjs';
|
2
|
-
import Store
|
2
|
+
import Store from '../../../src/data/Store.mjs';
|
3
3
|
|
4
4
|
/**
|
5
5
|
* @class LearnNeo.store.Content
|
@@ -15,7 +15,57 @@ class Content extends Store {
|
|
15
15
|
/**
|
16
16
|
* @member {Neo.data.Model} model=ContentModel
|
17
17
|
*/
|
18
|
-
model: ContentModel
|
18
|
+
model: ContentModel,
|
19
|
+
// autoLoad: true
|
20
|
+
|
21
|
+
|
22
|
+
}
|
23
|
+
|
24
|
+
xonConstructed() {
|
25
|
+
super.onConstructed();
|
26
|
+
|
27
|
+
let me = this;
|
28
|
+
|
29
|
+
// Neo.Main.getByPath({path: 'location.search'})
|
30
|
+
// .then(data => {
|
31
|
+
// const searchString = data?.substr(1) || '';
|
32
|
+
// const search = searchString ? JSON.parse(`{"${decodeURI(searchString.replace(/&/g, "\",\"").replace(/=/g, "\":\""))}"}`) : {};
|
33
|
+
// this.deck = search.deck || 'learnneo';
|
34
|
+
// this.url = `${this.contentPath}/tree.json`;
|
35
|
+
// this.load();
|
36
|
+
// // this.doLoadStore();
|
37
|
+
// console.log(search);
|
38
|
+
// });
|
39
|
+
|
40
|
+
}
|
41
|
+
xload() {
|
42
|
+
this.model = ContentModel;
|
43
|
+
Neo.Main.getByPath({path: 'location.search'})
|
44
|
+
.then(data => {
|
45
|
+
const searchString = data?.substr(1) || '';
|
46
|
+
const search = searchString ? JSON.parse(`{"${decodeURI(searchString.replace(/&/g, "\",\"").replace(/=/g, "\":\""))}"}`) : {};
|
47
|
+
this.deck = search.deck || 'learnneo';
|
48
|
+
this.url = `${this.contentPath}/tree.json`;
|
49
|
+
// this.load();
|
50
|
+
super.load();
|
51
|
+
console.log(search);
|
52
|
+
});
|
53
|
+
}
|
54
|
+
|
55
|
+
get contentPath() {
|
56
|
+
return `../../../resources/data/${this.deck}`;
|
57
|
+
}
|
58
|
+
doLoadStore() {
|
59
|
+
debugger;
|
60
|
+
const me = this;
|
61
|
+
Neo.Xhr.promiseJson({
|
62
|
+
url: `${this.contentPath}/tree.json`
|
63
|
+
}).then(data => {
|
64
|
+
// TODO: Tree lists should do this themselves when their store is loaded.
|
65
|
+
me.data = data.json.data;
|
66
|
+
// me.createItems(null, me.getListItemsRoot(), 0);
|
67
|
+
// me.update();
|
68
|
+
})
|
19
69
|
}
|
20
70
|
}
|
21
71
|
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import ContentStore from '../../store/Content.mjs'
|
2
|
-
import TreeList
|
2
|
+
import TreeList from '../../../../src/tree/List.mjs';
|
3
3
|
|
4
4
|
/**
|
5
5
|
* @class LearnNeo.view.home.ContentTreeList
|
@@ -23,17 +23,47 @@ class ContentTreeList extends TreeList {
|
|
23
23
|
*/
|
24
24
|
onConstructed() {
|
25
25
|
super.onConstructed();
|
26
|
-
|
27
26
|
let me = this;
|
28
|
-
|
27
|
+
Neo.Main.getByPath({path: 'location.search'})
|
28
|
+
.then(data => {
|
29
|
+
const searchString = data?.substr(1) || '';
|
30
|
+
const search = searchString ? JSON.parse(`{"${decodeURI(searchString.replace(/&/g, "\",\"").replace(/=/g, "\":\""))}"}`) : {};
|
31
|
+
me.deck = search.deck || 'learnneo';
|
32
|
+
me.doLoadStore();
|
33
|
+
console.log(search);
|
34
|
+
});
|
35
|
+
}
|
36
|
+
get contentPath() {
|
37
|
+
return `../../../resources/data/${this.deck}`;
|
38
|
+
}
|
39
|
+
doLoadStore() {
|
40
|
+
const me = this;
|
29
41
|
Neo.Xhr.promiseJson({
|
30
|
-
url:
|
42
|
+
url: `${this.contentPath}/t.json`
|
31
43
|
}).then(data => {
|
44
|
+
// TODO: Tree lists should do this themselves when their store is loaded.
|
32
45
|
me.store.data = data.json.data;
|
33
46
|
me.createItems(null, me.getListItemsRoot(), 0);
|
34
47
|
me.update();
|
35
48
|
})
|
36
49
|
}
|
50
|
+
onLeafItemClick(record) {
|
51
|
+
super.onLeafItemClick(record);
|
52
|
+
this.doFetchContent(record);
|
53
|
+
}
|
54
|
+
async doFetchContent(record) {
|
55
|
+
let path = `${this.contentPath}`;
|
56
|
+
path += record.path ? `/pages/${record.path}` : `/p/${record.id}.md`;
|
57
|
+
|
58
|
+
if (record.isLeaf && path) {
|
59
|
+
const data = await fetch(path);
|
60
|
+
const content = await data.text();
|
61
|
+
Neo.main.addon.Markdown.markdownToHtml(content)
|
62
|
+
.then(
|
63
|
+
html => this.fire('contentChange', {component: this, html}),
|
64
|
+
() => this.fire('contentChange', {component: this}));
|
65
|
+
}
|
66
|
+
}
|
37
67
|
}
|
38
68
|
|
39
69
|
Neo.applyClassConfig(ContentTreeList);
|
@@ -1,47 +1,35 @@
|
|
1
|
-
import Container
|
2
|
-
import
|
1
|
+
import Container from '../../../../src/container/Base.mjs';
|
2
|
+
import Component from '../../../../src/component/Base.mjs';
|
3
|
+
import ContentTreeList from './ContentTreeList.mjs';
|
3
4
|
import MainContainerController from './MainContainerController.mjs';
|
4
|
-
import
|
5
|
+
import MainContainerModel from './MainContainerModel.mjs';
|
6
|
+
import Splitter from '../../../../src/component/Splitter.mjs';
|
5
7
|
|
6
|
-
/**
|
7
|
-
* @class LearnNeo.view.home.MainContainer
|
8
|
-
* @extends Neo.container.Base
|
9
|
-
*/
|
10
8
|
class MainContainer extends Container {
|
11
9
|
static config = {
|
12
|
-
/**
|
13
|
-
* @member {String} className='LearnNeo.view.home.MainContainer'
|
14
|
-
* @protected
|
15
|
-
*/
|
16
10
|
className: 'LearnNeo.view.home.MainContainer',
|
17
|
-
|
18
|
-
* @member {Neo.controller.Component} controller=MainContainerController
|
19
|
-
*/
|
11
|
+
model: MainContainerModel,
|
20
12
|
controller: MainContainerController,
|
21
|
-
/**
|
22
|
-
* @member {Object[]} items
|
23
|
-
*/
|
24
13
|
items: [{
|
25
|
-
module
|
26
|
-
layout
|
14
|
+
module: Container,
|
15
|
+
layout: 'fit',
|
27
16
|
minWidth: 350,
|
28
|
-
width
|
17
|
+
width: 350,
|
29
18
|
|
30
19
|
items: [{
|
31
|
-
module
|
32
|
-
|
20
|
+
module: ContentTreeList,
|
21
|
+
reference: 'tree',
|
22
|
+
listeners: {contentChange: 'onContentListLeafClick'},
|
33
23
|
}]
|
34
24
|
}, {
|
35
|
-
module
|
25
|
+
module: Splitter,
|
36
26
|
resizeTarget: 'previous'
|
37
27
|
}, {
|
38
|
-
module
|
39
|
-
layout
|
40
|
-
|
28
|
+
module: Component,
|
29
|
+
layout: {ntype: 'card', activeIndex: null},
|
30
|
+
cls: 'learn-content',
|
31
|
+
reference: 'content'
|
41
32
|
}],
|
42
|
-
/**
|
43
|
-
* @member {Object} layout={ntype:'hbox',align:'stretch'}
|
44
|
-
*/
|
45
33
|
layout: {ntype: 'hbox', align: 'stretch'}
|
46
34
|
}
|
47
35
|
}
|
@@ -1,5 +1,6 @@
|
|
1
1
|
import Component from '../../../../src/controller/Component.mjs';
|
2
2
|
|
3
|
+
|
3
4
|
/**
|
4
5
|
* @class LearnNeo.view.home.MainContainerController
|
5
6
|
* @extends Neo.controller.Component
|
@@ -16,33 +17,22 @@ class MainContainerController extends Component {
|
|
16
17
|
/**
|
17
18
|
* @param {Object} record
|
18
19
|
*/
|
19
|
-
|
20
|
-
const
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
contentContainer.removeAll();
|
32
|
-
|
33
|
-
await this.timeout(50);
|
34
|
-
|
35
|
-
contentContainer.add({
|
36
|
-
ntype: 'component',
|
37
|
-
html : content
|
38
|
-
});
|
39
|
-
|
40
|
-
await this.timeout(50);
|
41
|
-
|
20
|
+
onContentListLeafClick(data) {
|
21
|
+
const content = this.getReference('content');
|
22
|
+
content.html = data.html;
|
23
|
+
|
24
|
+
// contentContainer.removeAll();
|
25
|
+
// contentContainer.add({
|
26
|
+
// ntype: 'component',
|
27
|
+
// html: data.html
|
28
|
+
// });
|
29
|
+
// contentContainer.layout.activeIndex = 0;
|
30
|
+
}
|
42
31
|
|
43
|
-
|
44
|
-
}
|
32
|
+
get contentPath() {
|
33
|
+
return `../../../resources/data/${this.deck}`;
|
45
34
|
}
|
35
|
+
|
46
36
|
}
|
47
37
|
|
48
38
|
Neo.applyClassConfig(MainContainerController);
|
@@ -0,0 +1,35 @@
|
|
1
|
+
import Component from '../../../../src/model/Component.mjs';
|
2
|
+
import Tree from '../../store/Content.mjs';
|
3
|
+
import Store from '../../../../src/data/Store.mjs';
|
4
|
+
|
5
|
+
/**
|
6
|
+
* @class LearnNeo.view.home.MainContainerModel
|
7
|
+
* @extends Neo.model.Component
|
8
|
+
*/
|
9
|
+
class MainContainerModel extends Component {
|
10
|
+
static config = {
|
11
|
+
/**
|
12
|
+
* @member {String} className='LearnNeo.view.home.MainContainerModel'
|
13
|
+
* @protected
|
14
|
+
*/
|
15
|
+
className: 'LearnNeo.view.home.MainContainerModel',
|
16
|
+
/**
|
17
|
+
* @member {Object} data
|
18
|
+
*/
|
19
|
+
data: {},
|
20
|
+
/**
|
21
|
+
* @member {Object} stores
|
22
|
+
*/
|
23
|
+
stores: {
|
24
|
+
tree: {
|
25
|
+
// module: Tree,
|
26
|
+
module: Store,
|
27
|
+
responseRoot: "data",
|
28
|
+
}
|
29
|
+
}
|
30
|
+
}
|
31
|
+
}
|
32
|
+
|
33
|
+
Neo.applyClassConfig(MainContainerModel);
|
34
|
+
|
35
|
+
export default MainContainerModel;
|
@@ -113,6 +113,12 @@ class MainContainer extends ConfigurationViewport {
|
|
113
113
|
labelText: 'resizable',
|
114
114
|
listeners: {change: me.onConfigChange.bind(me, 'resizable')},
|
115
115
|
style : {marginTop: '10px'}
|
116
|
+
}, {
|
117
|
+
module : CheckBox,
|
118
|
+
checked : me.exampleComponent.autoGrow,
|
119
|
+
labelText: 'autoGrow',
|
120
|
+
listeners: {change: me.onConfigChange.bind(me, 'autoGrow')},
|
121
|
+
style : {marginTop: '10px'}
|
116
122
|
}, {
|
117
123
|
module : CheckBox,
|
118
124
|
checked : me.exampleComponent.required,
|
@@ -175,7 +181,7 @@ class MainContainer extends ConfigurationViewport {
|
|
175
181
|
createExampleComponent() {
|
176
182
|
return Neo.create(TextAreaField, {
|
177
183
|
clearable : true,
|
178
|
-
height :
|
184
|
+
height : 60,
|
179
185
|
labelText : 'Label',
|
180
186
|
labelWidth: 70,
|
181
187
|
value : 'Hello World',
|
package/package.json
CHANGED
@@ -0,0 +1,96 @@
|
|
1
|
+
Neo.mjs is a framework used to create browser-based applications.
|
2
|
+
|
3
|
+
Some key features and benefits of Neo.mjs are:
|
4
|
+
|
5
|
+
<details>
|
6
|
+
<summary>Multi-Threaded</summary>
|
7
|
+
<p>
|
8
|
+
When a Neo.mjs application starts, the framework spawns three web-workers, in addition
|
9
|
+
to the main browser thread, resulting in:
|
10
|
+
<ol>
|
11
|
+
<li>The <b>main</b> browser thread, where DOM updates are applied
|
12
|
+
<li>An <b>application</b> web-worker where normal application locic is run
|
13
|
+
<li>A <b>data</b> web-worker were HTTP and socket calls are run
|
14
|
+
<li>A <b>view</b> web-worker that manages delta updates
|
15
|
+
</ol>
|
16
|
+
</details>
|
17
|
+
<details>
|
18
|
+
<summary>Extreme Speed</summary>
|
19
|
+
<p>
|
20
|
+
Web-worker proccesses are automatically run in parallel, on separate CPU cores.
|
21
|
+
</p>
|
22
|
+
<p>
|
23
|
+
By contrast, other JavaScript frameworks run in a single thread. That means
|
24
|
+
in a typical framework all business logic, data handling, and DOM rendering compete for
|
25
|
+
CPU reasources.
|
26
|
+
</p>
|
27
|
+
<p>
|
28
|
+
This means Neo.mjs applications run and render faster. This is
|
29
|
+
particularly beneficial for processor- and data-intensive applications,
|
30
|
+
and applications that need to rapidly update what's viewed. In testing, Neo.mjs applications
|
31
|
+
easily apply over 20,000 DOM updates per second.
|
32
|
+
</p>
|
33
|
+
<p>
|
34
|
+
If the default four threads aren't enough, you're free to launch additional web-worker threads
|
35
|
+
to run other specialized logic.
|
36
|
+
</p>
|
37
|
+
</details>
|
38
|
+
<details>
|
39
|
+
<summary>Quick Application Development</summary>
|
40
|
+
<p>
|
41
|
+
Neo.js classes let you specify properties in a way that allows code to detect "before" and "after"
|
42
|
+
changes. This makes it easy to handle value validation and transformation, and react to changes.
|
43
|
+
</p>
|
44
|
+
<p>
|
45
|
+
Neo.mjs also has elegant yet powerful state management features that make it easy to create shared,
|
46
|
+
bindable data. For example, if two components are bound to the same propery, a change to the
|
47
|
+
property will automatically be applied to both components.
|
48
|
+
</p>
|
49
|
+
<p>
|
50
|
+
Debugging is also easy because Neo.mjs uses standard JavaScript, the Neo.mjs class
|
51
|
+
config system, and built-in debugging tools. For example, while developing an application
|
52
|
+
you can click on a component, and in the debugger easily inspect the component and
|
53
|
+
update its properties ‐ these updates are immediately reflected in the running application.
|
54
|
+
</p>
|
55
|
+
</details>
|
56
|
+
<details>
|
57
|
+
<summary>Multi-Window Applications</summary>
|
58
|
+
<p>
|
59
|
+
Neo.mjs applications can also launch as <i>shared web workers</i>, which allows you to have a single
|
60
|
+
application run in multiple browser windows; those windows could be moved to multiple monitors.
|
61
|
+
</p>
|
62
|
+
<p>
|
63
|
+
For example, you can have a data analysis application with a control panel on one monitor,
|
64
|
+
tabular data in another, and charts on another — all sharing the same data, handling events
|
65
|
+
across windows, running seamlessly as a single application.
|
66
|
+
</p>
|
67
|
+
</details>
|
68
|
+
<details>
|
69
|
+
<summary>Open-Source and Standards-Based</summary>
|
70
|
+
<p>
|
71
|
+
Neo.mjs is an open-source library. Features needed for the community can be added to the
|
72
|
+
library via pull-requests. And since Neo.mjs uses the standard JavaScript class system,
|
73
|
+
all Neo.mjs classes can be extended.
|
74
|
+
</p>
|
75
|
+
<p>
|
76
|
+
Neo.mjs uses standard modular JavaScript, so developers don't need to learn non-standard language
|
77
|
+
syntax, and there's no need for special pre-compilers or WebPack modules.
|
78
|
+
That means fewer dependencies and easier configuration. Furthermore, the use of
|
79
|
+
standard JavaScript makes debugging easier: any statement you write while developing your
|
80
|
+
applcation can also be run in the debugging console.
|
81
|
+
</p>
|
82
|
+
</details>
|
83
|
+
<details>
|
84
|
+
<summary>Scalable</summary>
|
85
|
+
<p>
|
86
|
+
Applications can become exponentially difficult to implement as application
|
87
|
+
complexity increases. In contrast, the effort to code applications in Neo.mjs
|
88
|
+
is fairly linear. This is because of the Neo.mjs approach to binding, separation
|
89
|
+
of concerns, extensibility, and being standards based
|
90
|
+
</p>
|
91
|
+
<p>
|
92
|
+
Neo.mjs isn't just architecturally scalable — because of its multi-threaded
|
93
|
+
nature, it's designed to handles rapid DOM updates, data traffic, and processor-
|
94
|
+
intensive business logic.
|
95
|
+
</p>
|
96
|
+
</details>
|
@@ -0,0 +1,102 @@
|
|
1
|
+
Neo.mjs classes are standard JavaSript classes. Every source file
|
2
|
+
you write will be a class definition, extending some Neo.mjs
|
3
|
+
class.
|
4
|
+
|
5
|
+
<pre class="neo">
|
6
|
+
import Base from '../../../node_modules/neo.mjs/src/core/Base.mjs';
|
7
|
+
|
8
|
+
class Mammal extends Base {
|
9
|
+
static config = {
|
10
|
+
className: 'Simple.example.Mammal'
|
11
|
+
}
|
12
|
+
}
|
13
|
+
|
14
|
+
const myMammal = Neo.create(Mammal);
|
15
|
+
|
16
|
+
Neo.applyClassConfig(Mammal); // Where Neo.mjs initialializes the class config.
|
17
|
+
export default Mammal; // Makes the class available elsewhere.
|
18
|
+
</pre>
|
19
|
+
|
20
|
+
In the example above, we're extending the Neo.mjs base class. The static
|
21
|
+
config block describes the class we're defining.
|
22
|
+
|
23
|
+
All classes specify `className`; you'll add other config properties to describe the class.
|
24
|
+
|
25
|
+
The `const myMammal = Neo.create(Mammal);` statement creates an instance of
|
26
|
+
our class. For the sake of our discussion we're putting that statement in the same source
|
27
|
+
file where the class is defined, but normally your code would import the class elsewhere,
|
28
|
+
and create instances as needed.
|
29
|
+
|
30
|
+
Let's add a `name` propery to the class.
|
31
|
+
|
32
|
+
<pre class="neo" style="color:gray">
|
33
|
+
import Base from '../../../node_modules/neo.mjs/src/core/Base.mjs';
|
34
|
+
|
35
|
+
class Mammal extends Base {
|
36
|
+
static config = {
|
37
|
+
className: 'Simple.example.Mammal',
|
38
|
+
|
39
|
+
<span style="color:#b91010">name: 'Anonymous'</span>
|
40
|
+
}
|
41
|
+
}
|
42
|
+
|
43
|
+
const myMammal = Neo.create(Mammal);
|
44
|
+
console.log(<span style="color:#b91010">myMammal.name</span>); // Logs "Anonymous"
|
45
|
+
<span style="color:#b91010">myMammal.name</span> = 'Herbert';
|
46
|
+
console.log(<span style="color:#b91010">myMammal.name</span>); // Logs "Herbert"
|
47
|
+
|
48
|
+
Neo.applyClassConfig(Mammal);
|
49
|
+
|
50
|
+
export default Mammal;
|
51
|
+
</pre>
|
52
|
+
|
53
|
+
In Neo.mjs, instance properties are usually added in the `static config` block.
|
54
|
+
The `static config` block does two things:
|
55
|
+
- It formally describes the properties API for your class.
|
56
|
+
- It lets Neo.mjs manage the initialization and lifecycle of those properties.
|
57
|
+
|
58
|
+
Think of the `static config` block as "these are the properties
|
59
|
+
that can be set as instances are created." Config properties can be introduced
|
60
|
+
anywhere in the class hierarchy.
|
61
|
+
|
62
|
+
Since our class defines a `name` property, we can specify that when creating
|
63
|
+
the instance, using the second argument to the `create` method.
|
64
|
+
|
65
|
+
<pre class="neo" style="color:gray">
|
66
|
+
const myMammal = Neo.create(Mammal, <span style="color:#b91010">{
|
67
|
+
name: 'Creature'
|
68
|
+
}</span>);
|
69
|
+
console.log(myMammal.name); // Logs "Creature"
|
70
|
+
</pre>
|
71
|
+
|
72
|
+
|
73
|
+
Since _you_ define those properties, you can
|
74
|
+
look for them in class methods and use them as needed.
|
75
|
+
Let's add a `speak()` method that uses the `name` property.
|
76
|
+
|
77
|
+
<pre class="neo" style="color:gray">
|
78
|
+
import Base from '../../../node_modules/neo.mjs/src/core/Base.mjs';
|
79
|
+
|
80
|
+
class Mammal extends Base {
|
81
|
+
static config = {
|
82
|
+
className: 'Simple.example.Mammal',
|
83
|
+
|
84
|
+
name: 'Anonymous'
|
85
|
+
}
|
86
|
+
<span style="color:#b91010">speak(){
|
87
|
+
console.log(`${this.name} is grunting`);
|
88
|
+
}</span>
|
89
|
+
}
|
90
|
+
|
91
|
+
const myMammal = Neo.create(Mammal, {
|
92
|
+
name: 'Creature'
|
93
|
+
});
|
94
|
+
<span style="color:#b91010">myMammal.speak();</span> // Logs "Creature is grunting."
|
95
|
+
|
96
|
+
Neo.applyClassConfig(Mammal);
|
97
|
+
|
98
|
+
export default Mammal;
|
99
|
+
</pre>
|
100
|
+
|
101
|
+
|
102
|
+
|
@@ -0,0 +1,75 @@
|
|
1
|
+
In Neo.mjs you sub-class and override methods in the usual way.
|
2
|
+
|
3
|
+
Here, we'll extend `Mammal` and override the `speak()` method.
|
4
|
+
(For brevity, we'll exclude `export` and `import` statements.)
|
5
|
+
|
6
|
+
<pre class="neo">
|
7
|
+
class Mammal extends Base {
|
8
|
+
static config = {
|
9
|
+
className: 'Simple.example.Mammal',
|
10
|
+
|
11
|
+
name: 'Anonymous'
|
12
|
+
}
|
13
|
+
speak(){
|
14
|
+
console.log(`(${this.name} is grunting)`);
|
15
|
+
}
|
16
|
+
}
|
17
|
+
Neo.applyClassConfig(Mammal);
|
18
|
+
</pre>
|
19
|
+
<pre class="neo">
|
20
|
+
class Human extends Mammal {
|
21
|
+
static config = {
|
22
|
+
className: 'Simple.example.Human',
|
23
|
+
}
|
24
|
+
speak(){
|
25
|
+
console.log(`Hello! My name is ${this.name}. I am ${this.married?'':'not'} married.`);
|
26
|
+
}
|
27
|
+
}
|
28
|
+
|
29
|
+
const myMammal = Neo.create(Human, {
|
30
|
+
name: 'Herbert'
|
31
|
+
});
|
32
|
+
myMammal.speak(); // Logs "Hello! My name is Herbert. I am not married."
|
33
|
+
|
34
|
+
Neo.applyClassConfig(Mammal);
|
35
|
+
</pre>
|
36
|
+
|
37
|
+
Any class in the hierarchy is free to add new properties and methods. Let's add
|
38
|
+
a property and behavior (method) to the Human class.
|
39
|
+
|
40
|
+
<pre class="neo">
|
41
|
+
import Base from '../../../node_modules/neo.mjs/src/core/Base.mjs';
|
42
|
+
|
43
|
+
class Mammal extends Base {
|
44
|
+
static config = {
|
45
|
+
className: 'Simple.example.Mammal',
|
46
|
+
|
47
|
+
name: 'Anonymous'
|
48
|
+
}
|
49
|
+
speak(){
|
50
|
+
console.log(`(${this.name} is grunting)`);
|
51
|
+
}
|
52
|
+
}
|
53
|
+
</pre>
|
54
|
+
<pre class="neo">
|
55
|
+
class Human extends Mammal {
|
56
|
+
static config = {
|
57
|
+
className: 'Simple.example.Human',
|
58
|
+
name: 'J. Doe',
|
59
|
+
married: false
|
60
|
+
}
|
61
|
+
speak(){
|
62
|
+
console.log(`Hello! My name is ${this.name}. I am ${this.married?'':'not'} married.`);
|
63
|
+
},
|
64
|
+
yodel(){
|
65
|
+
console.log('Yodelay hee hoo!');
|
66
|
+
}}
|
67
|
+
|
68
|
+
const myPerson = Neo.create(Human, {
|
69
|
+
name: 'Herbert'
|
70
|
+
});
|
71
|
+
myPerson.speak(); // Logs "Hello! My name is Herbert. I am not married."
|
72
|
+
myPerson.yodel(); // Logs "Yedelay hee hoo!"
|
73
|
+
|
74
|
+
Neo.applyClassConfig(Human);
|
75
|
+
</pre>
|
@@ -0,0 +1,29 @@
|
|
1
|
+
Neo.mjs uses standard modular JavaScript, so you're free to use other class
|
2
|
+
features, like private members.
|
3
|
+
<pre class="neo">
|
4
|
+
class Human extends Mammal {
|
5
|
+
static config = {
|
6
|
+
className: 'Simple.example.Human',
|
7
|
+
name: 'J. Doe',
|
8
|
+
married: false,
|
9
|
+
}
|
10
|
+
static #privateStaticField = 'foo'
|
11
|
+
#privateInstanceField = 'bar'
|
12
|
+
#privateInstanceMethod(){
|
13
|
+
console.log(`Psst. Don't tell anyone, but ${this.#privateInstanceField} and ${Human.#privateStaticField}`);
|
14
|
+
}
|
15
|
+
speak(tellSecret){
|
16
|
+
console.log(`Hello! My name is ${this.name}. I am ${this.married?'':'not'} married.`);
|
17
|
+
if (tellSecret) this.#privateInstanceMethod();
|
18
|
+
}
|
19
|
+
yodel(){
|
20
|
+
console.log('Yodelay hee hoo!');
|
21
|
+
}}
|
22
|
+
|
23
|
+
const myPerson = Neo.create(Human, {
|
24
|
+
name: 'Herbert'
|
25
|
+
});
|
26
|
+
myPerson.speak(true);
|
27
|
+
|
28
|
+
Neo.applyClassConfig(Human);
|
29
|
+
</pre>
|
File without changes
|
@@ -0,0 +1,68 @@
|
|
1
|
+
To call a super-class method use the `super` keyword.
|
2
|
+
|
3
|
+
<pre class="neo" style="color:gray">
|
4
|
+
class Mammal extends Base {
|
5
|
+
static config = {
|
6
|
+
className: 'Simple.example.Mammal',
|
7
|
+
|
8
|
+
name: 'Anonymous'
|
9
|
+
}
|
10
|
+
doSomething(){
|
11
|
+
console.log(`${this.name} is doing something mammals do`)
|
12
|
+
}
|
13
|
+
}
|
14
|
+
Neo.applyClassConfig(Mammal);
|
15
|
+
</pre>
|
16
|
+
<pre class="neo" style="color:gray">
|
17
|
+
class Human extends Mammal {
|
18
|
+
static config = {
|
19
|
+
className: 'Simple.example.Human',
|
20
|
+
}
|
21
|
+
doSomething(){
|
22
|
+
<span style="color:#b91010">super.doSomething();</span>
|
23
|
+
console.log(`${this.name} is doing something humans do`)
|
24
|
+
}
|
25
|
+
}
|
26
|
+
|
27
|
+
const myPerson = Neo.create(Human, {
|
28
|
+
name: 'Herbert'
|
29
|
+
});
|
30
|
+
myPerson.doSomething();
|
31
|
+
|
32
|
+
Neo.applyClassConfig(Mammal);
|
33
|
+
</pre>
|
34
|
+
|
35
|
+
Sometimes you aren't sure if a super class has a method. In that case use the
|
36
|
+
conditional chaining operator — `?.`
|
37
|
+
|
38
|
+
<pre class="neo" style="color:gray">
|
39
|
+
class Mammal extends Base {
|
40
|
+
static config = {
|
41
|
+
className: 'Simple.example.Mammal',
|
42
|
+
|
43
|
+
name: 'Anonymous'
|
44
|
+
}
|
45
|
+
doSomething(){
|
46
|
+
console.log(`${this.name} is doing something mammals do`)
|
47
|
+
}
|
48
|
+
}
|
49
|
+
Neo.applyClassConfig(Mammal);
|
50
|
+
</pre>
|
51
|
+
<pre class="neo" style="color:gray">
|
52
|
+
class Human extends Mammal {
|
53
|
+
static config = {
|
54
|
+
className: 'Simple.example.Human',
|
55
|
+
}
|
56
|
+
doSomething(){
|
57
|
+
<span style="color:#b91010">super?.doSomething();</span>
|
58
|
+
console.log(`${this.name} is doing something humans do`)
|
59
|
+
}
|
60
|
+
}
|
61
|
+
|
62
|
+
const myPerson = Neo.create(Human, {
|
63
|
+
name: 'Herbert'
|
64
|
+
});
|
65
|
+
myPerson.doSomething();
|
66
|
+
|
67
|
+
Neo.applyClassConfig(Mammal);
|
68
|
+
</pre>
|
File without changes
|
@@ -0,0 +1,121 @@
|
|
1
|
+
The purpose of this tutorial is to let you see the structure of a Neo.mjs workspace,
|
2
|
+
and the strucure of an application.
|
3
|
+
|
4
|
+
If you haven't already done it, run `npx neo-app` to create a Neo.mjs workspace.
|
5
|
+
|
6
|
+
<img src="https://raw.githubusercontent.com/neomjs/pages/main/resources/images/learnneo/NeoWorkspace.png" style="height: 400px;">
|
7
|
+
|
8
|
+
As you can see, a Neo.mjs workspace is a conventional npm workspace. If you run
|
9
|
+
the script `npm run server-start` from the workspace, Neo.mjs launches a web
|
10
|
+
serve whose doc root is the workspace.
|
11
|
+
|
12
|
+
<img src="https://raw.githubusercontent.com/neomjs/pages/main/resources/images/learnneo/ServerRoot.png" style="height: 400px;">
|
13
|
+
|
14
|
+
If you drill down into the `src` directory you'll see your applications.
|
15
|
+
If you drill down into the `docs` directory you'll see the Neo.mjs API docs.
|
16
|
+
|
17
|
+
In order to discuss the structure of an app, we'll create a simple starter
|
18
|
+
app. To do that, run this command from the workspace.
|
19
|
+
|
20
|
+
`npm run create-app-empty`
|
21
|
+
|
22
|
+
At the first prompt, name the app `Foo`, and accept the default for everything else.
|
23
|
+
The script creates an application structured as follows.
|
24
|
+
|
25
|
+
<img src="https://raw.githubusercontent.com/neomjs/pages/main/resources/images/learnneo/FooFolder.png" style="height: 400px;">
|
26
|
+
|
27
|
+
These files are part of the typical structure of a Neo.mjs application. The files `index.html`, `app.mjs`, `neo-config.json`, `view/Viewport.mjs` are rarely modified.
|
28
|
+
You will, however, edit the main container, and its associated "controller" and "model",
|
29
|
+
as well as create new views, new controllers, etc.
|
30
|
+
|
31
|
+
Now let's look at a source file. This is the contents of `MainView.mjs`.
|
32
|
+
|
33
|
+
<pre class="neo">
|
34
|
+
import Base from '../../../node_modules/neo.mjs/src/container/Base.mjs';
|
35
|
+
import Controller from './MainViewController.mjs';
|
36
|
+
import ViewModel from './MainViewModel.mjs';
|
37
|
+
|
38
|
+
class MainView extends Base {
|
39
|
+
static config = {
|
40
|
+
className: 'Foo.view.MainView',
|
41
|
+
|
42
|
+
controller: {module: Controller},
|
43
|
+
model: {module: ViewModel},
|
44
|
+
|
45
|
+
layout: {ntype: 'fit'},
|
46
|
+
items: [],
|
47
|
+
}
|
48
|
+
}
|
49
|
+
|
50
|
+
Neo.applyClassConfig(MainView);
|
51
|
+
|
52
|
+
export default MainView;
|
53
|
+
</pre>
|
54
|
+
|
55
|
+
Neo.mjs views are composed of components. A component can be a "container", which means it
|
56
|
+
visually holds or groups other components, or a regular component, like a button. The main
|
57
|
+
view is a container, which you can see because `MainView extends Base`, and `Base` is
|
58
|
+
the container base class. The items in a container are configured in the `items:[]`, which
|
59
|
+
is empty in this starter code.
|
60
|
+
|
61
|
+
This view also has a "controller" and "model". We'll talk about those later, but in a nutshell,
|
62
|
+
a controller is where event handling and app logic goes, and a model is where you set up shared
|
63
|
+
bindable data.
|
64
|
+
|
65
|
+
We'll go into a lot more depth about view, controllers, and models in other topics, but to let
|
66
|
+
you see how a component is configured let's put a button in the container.
|
67
|
+
|
68
|
+
First, we need to import the class that defines buttons. Then we'll describe the new button in the
|
69
|
+
`items:[].`
|
70
|
+
|
71
|
+
<pre class="neo">
|
72
|
+
|
73
|
+
import Base from '../../../node_modules/neo.mjs/src/container/Base.mjs';
|
74
|
+
import Controller from './MainViewController.mjs';
|
75
|
+
import ViewModel from './MainViewModel.mjs';
|
76
|
+
import Button from '../../../node_modules/neo.mjs/src/button/Base.mjs';
|
77
|
+
|
78
|
+
class MainView extends Base {
|
79
|
+
static config = {
|
80
|
+
className: 'Foo.view.MainView',
|
81
|
+
|
82
|
+
controller: {module: Controller},
|
83
|
+
model: {module: ViewModel},
|
84
|
+
|
85
|
+
layout: {ntype: 'fit'},
|
86
|
+
items: [{
|
87
|
+
module: Button,
|
88
|
+
text: 'Button'
|
89
|
+
}],
|
90
|
+
}
|
91
|
+
}
|
92
|
+
|
93
|
+
Neo.applyClassConfig(MainView);
|
94
|
+
|
95
|
+
export default MainView;
|
96
|
+
</pre>
|
97
|
+
|
98
|
+
Note the entry in +items:[]+. That's a description of the button that'll be created as the single
|
99
|
+
item in our container. In Neo.mjs terms we're _configuring_ the button. Neo.mjs is _declarative_,
|
100
|
+
which components and objects are _described_. It's an abstraction. In other words we're saying
|
101
|
+
what we want, but not how it's done. We want a button with some text — how that's done
|
102
|
+
isn't important here in the container. A non-declarative approach would be more focused on _how_,
|
103
|
+
where you might way "I want a <button> HTML element with its innerHTML set to some value."
|
104
|
+
|
105
|
+
In another topic you'll learn about the Neo.mjs config system and declaratively describing
|
106
|
+
views, controllers, and other things.
|
107
|
+
|
108
|
+
If you run the +foo+ app you'll see one huge button. That's because the container is configured to
|
109
|
+
use the "fit" layout, which means the container is designed to hold one and only one component,
|
110
|
+
and that component will take up all available space. We could get a more normal looking button
|
111
|
+
by changing the layout.
|
112
|
+
|
113
|
+
`layout: {type:'vbox', align:'start},`
|
114
|
+
|
115
|
+
Change that line and look at the running application.
|
116
|
+
|
117
|
+
Neo.mjs has scores of component classes.
|
118
|
+
You can extend them to create your own reusable components and sets of components.
|
119
|
+
|
120
|
+
Other topics discuss coding applications, how the config system works, controllers,
|
121
|
+
and bindable data. However, as long as we're here
|
@@ -0,0 +1,130 @@
|
|
1
|
+
{
|
2
|
+
"data": [
|
3
|
+
{
|
4
|
+
"id": "2023-10-01T18-29-19-158Z",
|
5
|
+
"parentId": null,
|
6
|
+
"isLeaf": true,
|
7
|
+
"name": "Why Neo.mjs?"
|
8
|
+
},
|
9
|
+
{
|
10
|
+
"id": "2023-09-26T17-26-15-104Z",
|
11
|
+
"parentId": null,
|
12
|
+
"isLeaf": false,
|
13
|
+
"name": "Getting Started"
|
14
|
+
},
|
15
|
+
{
|
16
|
+
"id": "2023-10-12T12-59-22-082Z",
|
17
|
+
"parentId": null,
|
18
|
+
"isLeaf": false,
|
19
|
+
"name": "Source Files"
|
20
|
+
},
|
21
|
+
{
|
22
|
+
"id": "2023-10-14T19-25-08-153Z",
|
23
|
+
"parentId": "2023-10-12T12-59-22-082Z",
|
24
|
+
"isLeaf": true,
|
25
|
+
"name": "Neo.mjs workspaces"
|
26
|
+
},
|
27
|
+
{
|
28
|
+
"id": "2023-10-08T18-26-31-357Z",
|
29
|
+
"parentId": null,
|
30
|
+
"isLeaf": false,
|
31
|
+
"name": "Class Definitions and Config"
|
32
|
+
},
|
33
|
+
{
|
34
|
+
"id": "2023-10-08T20-39-23-708Z",
|
35
|
+
"parentId": "2023-10-08T18-26-31-357Z",
|
36
|
+
"isLeaf": false,
|
37
|
+
"name": "Class Basics"
|
38
|
+
},
|
39
|
+
{
|
40
|
+
"id": "2023-10-07T19-18-28-517Z",
|
41
|
+
"parentId": "2023-10-08T20-39-23-708Z",
|
42
|
+
"isLeaf": true,
|
43
|
+
"name": "Classes, Properties, and Methods"
|
44
|
+
},
|
45
|
+
{
|
46
|
+
"id": "2023-10-08T20-20-07-934Z",
|
47
|
+
"parentId": "2023-10-08T20-39-23-708Z",
|
48
|
+
"isLeaf": true,
|
49
|
+
"name": "Overriding Methods"
|
50
|
+
},
|
51
|
+
{
|
52
|
+
"id": "2023-10-08T20-20-37-336Z",
|
53
|
+
"parentId": "2023-10-08T20-39-23-708Z",
|
54
|
+
"isLeaf": true,
|
55
|
+
"name": "Other JavaScript Class Features"
|
56
|
+
},
|
57
|
+
{
|
58
|
+
"id": "2023-10-08T21-58-25-809Z",
|
59
|
+
"parentId": "2023-10-08T20-39-23-708Z",
|
60
|
+
"isLeaf": true,
|
61
|
+
"name": "Super"
|
62
|
+
},
|
63
|
+
{
|
64
|
+
"id": "2023-10-08T20-37-30-658Z",
|
65
|
+
"parentId": "2023-10-08T18-26-31-357Z",
|
66
|
+
"isLeaf": true,
|
67
|
+
"name": "The Constructor"
|
68
|
+
},
|
69
|
+
{
|
70
|
+
"id": "2023-10-08T22-22-11-013Z",
|
71
|
+
"parentId": "2023-10-08T18-26-31-357Z",
|
72
|
+
"isLeaf": true,
|
73
|
+
"name": "super"
|
74
|
+
},
|
75
|
+
{
|
76
|
+
"id": "2023-09-25T20-27-55-600Z",
|
77
|
+
"parentId": null,
|
78
|
+
"isLeaf": false,
|
79
|
+
"name": "Config"
|
80
|
+
},
|
81
|
+
{
|
82
|
+
"id": "2023-09-25T20-27-38-129Z",
|
83
|
+
"parentId": null,
|
84
|
+
"isLeaf": false,
|
85
|
+
"name": "Component Basics"
|
86
|
+
},
|
87
|
+
{
|
88
|
+
"id": "2023-09-25T20-28-12-355Z",
|
89
|
+
"parentId": null,
|
90
|
+
"isLeaf": false,
|
91
|
+
"name": "User Input (Forms)"
|
92
|
+
},
|
93
|
+
{
|
94
|
+
"id": "2023-09-25T20-28-33-035Z",
|
95
|
+
"parentId": null,
|
96
|
+
"isLeaf": false,
|
97
|
+
"name": "Data"
|
98
|
+
},
|
99
|
+
{
|
100
|
+
"id": "2023-09-25T20-28-51-807Z",
|
101
|
+
"parentId": null,
|
102
|
+
"isLeaf": false,
|
103
|
+
"name": "Component Models and Binding"
|
104
|
+
},
|
105
|
+
{
|
106
|
+
"id": "2023-09-25T20-29-19-770Z",
|
107
|
+
"parentId": null,
|
108
|
+
"isLeaf": false,
|
109
|
+
"name": "Multi-Window"
|
110
|
+
},
|
111
|
+
{
|
112
|
+
"id": "2023-09-25T20-29-28-307Z",
|
113
|
+
"parentId": null,
|
114
|
+
"isLeaf": false,
|
115
|
+
"name": "Main Thread Add-onss"
|
116
|
+
},
|
117
|
+
{
|
118
|
+
"id": "2023-09-26T17-31-48-754Z",
|
119
|
+
"parentId": null,
|
120
|
+
"isLeaf": false,
|
121
|
+
"name": "Custom Components and vdom"
|
122
|
+
},
|
123
|
+
{
|
124
|
+
"id": "2023-10-16T19-38-29-590Z",
|
125
|
+
"parentId": null,
|
126
|
+
"isLeaf": false,
|
127
|
+
"name": "Mixins"
|
128
|
+
}
|
129
|
+
]
|
130
|
+
}
|
@@ -0,0 +1,80 @@
|
|
1
|
+
|
2
|
+
#hi
|
3
|
+
|
4
|
+
|
5
|
+
Neo.mjs is a framework used to create browser-based applications.
|
6
|
+
|
7
|
+
Some key features and benefits of Neo.mjs are:
|
8
|
+
|
9
|
+
<div type="expander" caption="Multi-Threaded">
|
10
|
+
<p>
|
11
|
+
When a Neo.mjs application starts, the framework spawns three web-workers.
|
12
|
+
Web-workers are each run in their own thread. As a result, a typical Neo.mjs application
|
13
|
+
has four threads:
|
14
|
+
<ol>
|
15
|
+
<li>The _main_ thread, where DOM updates are applied
|
16
|
+
<li>An _application_ web-worker where normal application locic is run
|
17
|
+
<li>A _data_ web-worker were HTTP and socket calls are run
|
18
|
+
<li>A _view_ web-worker that manages delta updates
|
19
|
+
</ol>
|
20
|
+
</div>
|
21
|
+
|
22
|
+
<div type="expander" caption="Extreme Speed">
|
23
|
+
<p>
|
24
|
+
The Neo.mjs web-worker proccesses are automatically run in parallel, on separate CPU cores.
|
25
|
+
</p>
|
26
|
+
<p>
|
27
|
+
By contrast, other JavaScript frameworks run in a single thread. That means
|
28
|
+
in a typical framework all business logic, data handling, and DOM rendering compete for
|
29
|
+
CPU reasources.
|
30
|
+
</p>
|
31
|
+
<p>
|
32
|
+
This means Neo.mjs applications run and render faster. This is
|
33
|
+
particularly beneficial for processor- and data-intensive applications,
|
34
|
+
and applications that need to rapidly update what's viewed. In testing, Neo.mjs applications
|
35
|
+
easily apply over 20,000 DOM updates per second.
|
36
|
+
</p>
|
37
|
+
<p>
|
38
|
+
If the default four threads aren't enough, you're free to launch additional web-worker threads
|
39
|
+
to run other specialized logic.
|
40
|
+
</p>
|
41
|
+
</div>
|
42
|
+
|
43
|
+
<div type="expander" caption="Quick Application Development">
|
44
|
+
<p>
|
45
|
+
Neo.js classes let you specify properties in a way that allows code to detect "before" and "after"
|
46
|
+
changes. This makes it easy to handle value validation and transformation, and react to changes.
|
47
|
+
</p>
|
48
|
+
<p>
|
49
|
+
Neo.mjs also has elegant yet powerful state management features that make it easy to create shared,
|
50
|
+
bindable data. For example, if two components are bound to the same propery, a change to the
|
51
|
+
property will automatically be applied to both components.
|
52
|
+
</p>
|
53
|
+
</div>
|
54
|
+
|
55
|
+
<div type="expander" caption="Multi-Window Applications">
|
56
|
+
<p>
|
57
|
+
Neo.mjs applications can also launch as _shared web workers_, which allows you to have a single
|
58
|
+
application run in multiple browser windows; those windows could be moved to multiple monitors.
|
59
|
+
</p>
|
60
|
+
<p>
|
61
|
+
For example, you can have a data analysis application with a control panel on one monitor,
|
62
|
+
tabular data in another, and charts on another — all sharing the same data, handling events
|
63
|
+
across windows, running seamlessly as a single application.
|
64
|
+
</p>
|
65
|
+
</div>
|
66
|
+
|
67
|
+
<div type="expander" caption="Open-Source and Standards-Based">
|
68
|
+
<p>
|
69
|
+
Neo.mjs is an open-source library. Features needed for the community can be added to the
|
70
|
+
library via pull-requests. And since Neo.mjs uses the standard JavaScript class system,
|
71
|
+
all Neo.mjs classes can be extended.
|
72
|
+
</p>
|
73
|
+
<p>
|
74
|
+
Neo.mjs uses standard modular JavaScript, so developers don't need to learn non-standard language
|
75
|
+
syntax, and there's no need for special pre-compilers or WebPack modules.
|
76
|
+
That means fewer dependencies and easier configuration. Furthermore, the use of
|
77
|
+
standard JavaScript makes debugging easier: any statement you write while developing your
|
78
|
+
applcation can also be run in the debugging console.
|
79
|
+
</p>
|
80
|
+
</div>
|
@@ -0,0 +1,57 @@
|
|
1
|
+
.learn-content {
|
2
|
+
|
3
|
+
font-size: 13pt;
|
4
|
+
letter-spacing: 1px;
|
5
|
+
|
6
|
+
em,
|
7
|
+
i {
|
8
|
+
font-family: Palatino, "Times New Roman", serif;
|
9
|
+
font-size: 1.07em;
|
10
|
+
}
|
11
|
+
|
12
|
+
p {
|
13
|
+
margin: 0em 0.5em .7em 0.5em;
|
14
|
+
}
|
15
|
+
|
16
|
+
padding: 0.5em;
|
17
|
+
|
18
|
+
overflow: scroll;
|
19
|
+
|
20
|
+
details summary {
|
21
|
+
cursor: pointer;
|
22
|
+
transition: margin 300ms ease-out;
|
23
|
+
}
|
24
|
+
|
25
|
+
details[open] {
|
26
|
+
margin-bottom: 2em;
|
27
|
+
}
|
28
|
+
|
29
|
+
details summary {
|
30
|
+
list-style: none;
|
31
|
+
color: #555;
|
32
|
+
display: flex;
|
33
|
+
/* also removes the list marker */
|
34
|
+
align-items: center;
|
35
|
+
font-weight: bold;
|
36
|
+
}
|
37
|
+
|
38
|
+
details summary::before {
|
39
|
+
content: "\f055";
|
40
|
+
}
|
41
|
+
|
42
|
+
details[open] summary::before {
|
43
|
+
content: "\f056";
|
44
|
+
}
|
45
|
+
|
46
|
+
details summary::before {
|
47
|
+
font-family: var(--fa-style-family, "Font Awesome 6 Free");
|
48
|
+
font-weight: var(--fa-style, 900);
|
49
|
+
font-size: 1.3em;
|
50
|
+
margin: .4em;
|
51
|
+
color: #c4c4c4;
|
52
|
+
}
|
53
|
+
|
54
|
+
summary::-webkit-details-marker {
|
55
|
+
display: none;
|
56
|
+
}
|
57
|
+
}
|
package/src/DefaultConfig.mjs
CHANGED
@@ -236,12 +236,12 @@ const DefaultConfig = {
|
|
236
236
|
useVdomWorker: true,
|
237
237
|
/**
|
238
238
|
* buildScripts/injectPackageVersion.mjs will update this value
|
239
|
-
* @default '6.9.
|
239
|
+
* @default '6.9.7'
|
240
240
|
* @memberOf! module:Neo
|
241
241
|
* @name config.version
|
242
242
|
* @type String
|
243
243
|
*/
|
244
|
-
version: '6.9.
|
244
|
+
version: '6.9.7'
|
245
245
|
};
|
246
246
|
|
247
247
|
Object.assign(DefaultConfig, {
|
@@ -27,6 +27,12 @@ class TextArea extends Text {
|
|
27
27
|
* @protected
|
28
28
|
*/
|
29
29
|
ntype: 'textarea',
|
30
|
+
/**
|
31
|
+
* Set this to `true` to have the text area grow and shrink to accommodate
|
32
|
+
* any height of text. Bounds can be set using the `minHeight` and `maxHeight` settings.
|
33
|
+
* @member {Boolean} autoGrow=false
|
34
|
+
*/
|
35
|
+
autoGrow : false,
|
30
36
|
/**
|
31
37
|
* @member {String[]} baseCls=['neo-textarea','neo-textfield']
|
32
38
|
*/
|
@@ -88,6 +94,17 @@ class TextArea extends Text {
|
|
88
94
|
this.changeInputElKey('tag', value);
|
89
95
|
}
|
90
96
|
|
97
|
+
/**
|
98
|
+
* Triggered after the mounted config got changed
|
99
|
+
* @param {Boolean} value
|
100
|
+
* @param {Boolean} oldValue
|
101
|
+
* @protected
|
102
|
+
*/
|
103
|
+
afterSetMounted(value, oldValue) {
|
104
|
+
super.afterSetMounted(value, oldValue);
|
105
|
+
this.syncAutoGrowHeight();
|
106
|
+
}
|
107
|
+
|
91
108
|
/**
|
92
109
|
* Triggered after the resizable config got changed
|
93
110
|
* @param {Boolean} value
|
@@ -126,6 +143,7 @@ class TextArea extends Text {
|
|
126
143
|
}
|
127
144
|
|
128
145
|
super.afterSetValue(value, oldValue);
|
146
|
+
this.syncAutoGrowHeight();
|
129
147
|
}
|
130
148
|
|
131
149
|
/**
|
@@ -148,6 +166,40 @@ class TextArea extends Text {
|
|
148
166
|
beforeSetWrap(value, oldValue) {
|
149
167
|
return this.beforeSetEnumValue(value, oldValue, 'wrap', 'wrapValues');
|
150
168
|
}
|
169
|
+
|
170
|
+
/**
|
171
|
+
* @param {Object} data
|
172
|
+
* @protected
|
173
|
+
*/
|
174
|
+
onInputValueChange(data) {
|
175
|
+
this.syncAutoGrowHeight();
|
176
|
+
super.onInputValueChange(data);
|
177
|
+
}
|
178
|
+
|
179
|
+
/**
|
180
|
+
* @protected
|
181
|
+
*/
|
182
|
+
async syncAutoGrowHeight() {
|
183
|
+
let me = this;
|
184
|
+
|
185
|
+
if (me.mounted && me.autoGrow) {
|
186
|
+
const
|
187
|
+
inputEl = me.getInputEl(),
|
188
|
+
dims = await Neo.main.DomAccess.getScrollingDimensions({
|
189
|
+
appName : me.appName,
|
190
|
+
id : me.getInputElId()
|
191
|
+
});
|
192
|
+
|
193
|
+
// We must not show the scrollbar when autoGrowing
|
194
|
+
inputEl.style.overflowY = 'hidden';
|
195
|
+
|
196
|
+
if (dims.scrollHeight > dims.clientHeight - 5) {
|
197
|
+
inputEl.height = dims.scrollHeight;
|
198
|
+
}
|
199
|
+
|
200
|
+
me.update();
|
201
|
+
}
|
202
|
+
}
|
151
203
|
}
|
152
204
|
|
153
205
|
Neo.applyClassConfig(TextArea);
|
package/src/main/DomAccess.mjs
CHANGED
@@ -100,6 +100,7 @@ class DomAccess extends Base {
|
|
100
100
|
'focus',
|
101
101
|
'getAttributes',
|
102
102
|
'getBoundingClientRect',
|
103
|
+
'getScrollingDimensions',
|
103
104
|
'measure',
|
104
105
|
'scrollBy',
|
105
106
|
'scrollIntoView',
|
@@ -459,6 +460,28 @@ class DomAccess extends Base {
|
|
459
460
|
return nodeId.nodeType ? nodeId : (nodeId === 'body' || nodeId === 'document.body') ? document.body : this.getElement(nodeId);
|
460
461
|
}
|
461
462
|
|
463
|
+
/**
|
464
|
+
* @param {HTMLElement|Object} data
|
465
|
+
* @param {String|String[]} data.id
|
466
|
+
* @returns {Object}
|
467
|
+
*/
|
468
|
+
getScrollingDimensions(data) {
|
469
|
+
const me = this;
|
470
|
+
|
471
|
+
if (Array.isArray(data.id)) {
|
472
|
+
return data.id.map(id => me.getScrollingDimensions({ id }));
|
473
|
+
} else {
|
474
|
+
const node = data.nodeType ? data : me.getElementOrBody(data.id);
|
475
|
+
|
476
|
+
return {
|
477
|
+
clientWidth : node?.clientWidth,
|
478
|
+
clientHeight: node?.clientHeight,
|
479
|
+
scrollWidth : node?.scrollWidth,
|
480
|
+
scrollHeight: node?.scrollHeight
|
481
|
+
};
|
482
|
+
}
|
483
|
+
}
|
484
|
+
|
462
485
|
/**
|
463
486
|
* @param {HTMLElement} el
|
464
487
|
* @returns {Boolean}
|
File without changes
|