From f22f0a0a7ff543d6dcba758fac4b169ad14b9f1d Mon Sep 17 00:00:00 2001 From: Max Rahder Date: Thu, 28 Dec 2023 16:01:10 -0600 Subject: [PATCH] Learning section --- .../data/deck/learnneo/data/theBeatles.json | 22 +++ .../data/deck/learnneo/p/AddingProperties.md | 1 - .../data/deck/learnneo/p/ComponentState.md | 1 - resources/data/deck/learnneo/p/Config.md | 132 ++++++++++++++++++ resources/data/deck/learnneo/p/Events.md | 2 +- resources/data/deck/learnneo/p/Extending.md | 118 +++++++++++++++- resources/data/deck/learnneo/t.json | 9 +- 7 files changed, 276 insertions(+), 9 deletions(-) create mode 100644 resources/data/deck/learnneo/data/theBeatles.json delete mode 100644 resources/data/deck/learnneo/p/AddingProperties.md delete mode 100644 resources/data/deck/learnneo/p/ComponentState.md create mode 100644 resources/data/deck/learnneo/p/Config.md diff --git a/resources/data/deck/learnneo/data/theBeatles.json b/resources/data/deck/learnneo/data/theBeatles.json new file mode 100644 index 000000000..5176a35b1 --- /dev/null +++ b/resources/data/deck/learnneo/data/theBeatles.json @@ -0,0 +1,22 @@ +[ + { + "first": "John", + "last": "Lennon", + "dob": "1940/10/09" + }, + { + "first": "Paul", + "last": "McCartney", + "dob": "1942/06/18" + }, + { + "first": "George", + "last": "Harrison", + "dob": "1943/02/25" + }, + { + "first": "Ringo", + "last": "Starr", + "dob": "1940/07/07" + } +] \ No newline at end of file diff --git a/resources/data/deck/learnneo/p/AddingProperties.md b/resources/data/deck/learnneo/p/AddingProperties.md deleted file mode 100644 index 135333744..000000000 --- a/resources/data/deck/learnneo/p/AddingProperties.md +++ /dev/null @@ -1 +0,0 @@ -### todo: Adding Properties diff --git a/resources/data/deck/learnneo/p/ComponentState.md b/resources/data/deck/learnneo/p/ComponentState.md deleted file mode 100644 index 7d2a4bb78..000000000 --- a/resources/data/deck/learnneo/p/ComponentState.md +++ /dev/null @@ -1 +0,0 @@ -### todo: Updating View State diff --git a/resources/data/deck/learnneo/p/Config.md b/resources/data/deck/learnneo/p/Config.md new file mode 100644 index 000000000..9b69d0289 --- /dev/null +++ b/resources/data/deck/learnneo/p/Config.md @@ -0,0 +1,132 @@ +As you've probably noticed, Neo.mjs classes have a `static config` property. + +The `config` specifies properties that are applied to instances as they are +created. In addition, Neo.mjs uses that information to set up propoerty lifecycle +methods. + +Here's an example of a new component class `Simple` with three config properties: + +1. `className` — used by Neo.mjs to keep track of every class +2. `foo` — an instance property +2. `bar_` — another instance property + +
+import Component  from '../../../../src/component/Base.mjs';
+import Container  from '../../../../src/container/Base.mjs';
+
+class Simple extends Component {
+    static config = {
+        className: 'Example.view.Simple',
+
+        foo: 1,        // An instance field and its initial (default) value
+        bar_: ''       // Another instance field -- note the underscore at the end
+
+    }
+
+}
+Neo.applyClassConfig(Simple);
+
+
+class MainView extends Container {
+    static config = {
+        className: 'Example.view.MainView',
+
+        items: [{
+            module: Simple,
+
+            foo: 17          // This is applied to the instance
+            bar: 'hi there'  // This is applied to the instance
+
+        }]
+
+    }
+}
+Neo.applyClassConfig(MainView);
+
+ +The `Simple` class doesn't have any content, so if you run the code you won't see anything. +But if you were to open the debugger in the `neomjs-app-worker` context you could see the values +via `Neo.findFirst('[foo]').foo`. or `Neo.findFirst('[foo]').bar` We'll talk more about `findFirst()` +and the console below. + +Note that the `bar` property was defined with an underscore at the end. That tags the property as +a _lifecyle property_. A lifecycle property provides methods that are run as the property is +updated or accessed. You're free to implment the methods to implement business rules, normalize +values, or have side-effects, such as updating a view or firing an event. + +
+import Component  from '../../../../src/component/Base.mjs';
+import Container  from '../../../../src/container/Base.mjs';
+
+class Simple extends Component {
+    static config = {
+        className: 'Example.view.Simple',
+
+        foo: 1,        // An instance field and its initial (default) value
+        bar_: null     // Another instance field -- note the underscore at the end
+
+    }
+
+    beforeGetBar(){
+
+    }
+    beforeSetBar(value, oldValue){
+        // Use value if it's not empty
+        if (value) return value; 
+    }
+    afterSetBar(value, oldValue){
+        this.html = value;
+        this.fire('barChange', {component: this, value, oldValue});
+    }
+
+}
+Neo.applyClassConfig(Simple);
+
+
+class MainView extends Container {
+    static config = {
+        className: 'Example.view.MainView',
+
+        items: [{
+            module: Simple,
+
+            foo: 17 ,        // This is applied to the instance
+            bar: 'hi there'  // This is applied to the instance
+
+        }]
+
+    }
+}
+Neo.applyClassConfig(MainView);
+
+ + +
+items: [{
+    module: Simple,
+    html: 'simple', // This property is introduced in Neo.component.Base
+
+    foo: 17         // This is applied to the instance
+
+}]
+
+ +The config `{ module: Simple, html: 'Simple', foo: 17 }` is an object literal +_describing_ — or _configuring_ — the item in our main view. It's saying we want an instance +of `Simple`, with two properties applied to the new instance: + +- `html`, this isn't used very often, but it's handy for stubbing out views +- `foo`, which is the property introduced in `Examples.view.Simple` + + +Before moving on, let's break down that console statement. + +First, Neo.mjs is multi-threaded. Your app logic runs in the `neomjs-app-worker` context. +In Chrome devtools you need to switch to +that context via the dropdown at the top of of console panel. (We'll talk more about debugging +and the console in a later topic.) + +The statement `Neo.findFirst('[foo]')` finds the first component with a `foo` property. The `Neo.findFirst()` +method is handy for getting component references when debugging. + +When we add `.foo` to that, we're simply inspecting the value of that property at runtime — 17. \ No newline at end of file diff --git a/resources/data/deck/learnneo/p/Events.md b/resources/data/deck/learnneo/p/Events.md index f186bda53..3b5fd4176 100644 --- a/resources/data/deck/learnneo/p/Events.md +++ b/resources/data/deck/learnneo/p/Events.md @@ -9,7 +9,7 @@ To specify an event handler, use `listeners: {}`, specifying in as many event/ha pairs as you need. The code below shows two text fields, with `listeners` for `change` and `focusEnter`. -(The events for any component is documened in the API docs.) +(The events for any component are documened in the API docs.)
 import Base from '../../../../src/container/Base.mjs';
diff --git a/resources/data/deck/learnneo/p/Extending.md b/resources/data/deck/learnneo/p/Extending.md
index 4272c5409..9e0915ed9 100644
--- a/resources/data/deck/learnneo/p/Extending.md
+++ b/resources/data/deck/learnneo/p/Extending.md
@@ -1 +1,117 @@
-### todo: Extending Components
+
+In theory, a Neo.mjs app could be defined in a single `.mjs` source file. But that would be very hard to 
+maintain, and any reusable configs would have to be duplicated. Instead, each of your views and reusable 
+widgets will be defined as its own class. The result is simpler views which are inherently reusable and easier 
+to test.
+
+Consider this code. It's a panel with a header and a table. The table has a store. 
+
+
+import Base        from  '../../../../src/container/Panel.mjs';
+import Button      from  '../../../../src/button/Base.mjs';
+import Table       from  '../../../../src/table/Container.mjs';
+
+class MainView extends Base {
+    static config = {
+        className : 'Example.view.MainView',
+        headers: [{
+            dock: 'top',
+            items: [{
+                module: Button,
+                text: 'She loves me...',
+                handler: () => Neo.Main.alert({message: 'Yeah, yeah yeah!'})
+            }]
+        }],
+        items : [{
+            module: Table,
+            store: {
+                autoLoad: true,
+                url: '../../resources/data/deck/learnneo/data/theBeatles.json',
+                model: {
+                    fields: [{name: 'first'}, {name: 'last'}, {name: 'dob', type: 'date'}]
+                }
+            },
+            columns: [{
+                text: 'First',
+                dataField: 'first',
+            }, {
+                text: 'Last',
+                dataField: 'last',
+            }]
+        }]
+    }
+}
+Neo.applyClassConfig(MainView);
+
+ +If you wanted, any of the configs can be refactored into their own class. Here, the button, store, and table +have been refactored into their own classes, and the main view is using them. The main view is simpler and +more abstract, and each class can be reused, tested, and maintained independently. + +
+import Base        from  '../../../../src/container/Panel.mjs';
+import Button      from  '../../../../src/button/Base.mjs';
+import Table       from  '../../../../src/table/Container.mjs';
+import Store       from  '../../../../src/data/Store.mjs';
+
+class BeatlesButton extends Button {
+    static config = {
+        className: 'Example.view.BeatlesButton',
+        text: 'She loves me...',
+        handler: () => Neo.Main.alert({message: 'Yeah, yeah yeah!'})
+    }
+}
+Neo.applyClassConfig(BeatlesButton);
+
+class BeatlesStore extends Store {
+    static config = {
+        className: 'Example.view.BeatlesStore',
+        autoLoad: true,
+        url: '../../resources/data/deck/learnneo/data/theBeatles.json',
+        model: {
+            fields: [{name: 'first'}, {name: 'last'}, {name: 'dob', type: 'date'}]
+        }
+    }
+}
+Neo.applyClassConfig(BeatlesStore);
+
+class BeatlesTable extends Table {
+    static config = {
+        className: 'Example.view.BeatlesTable',
+        columns: [{
+            text: 'First',
+            dataField: 'first',
+        }, {
+            text: 'Last',
+            dataField: 'last',
+        }]
+   }
+}
+Neo.applyClassConfig(BeatlesTable);
+
+class MainView extends Base {
+    static config = {
+        className : 'Example.view.MainView',
+        headers: [{
+            dock: 'top',
+            items: [{
+                module: BeatlesButton,
+            }]
+        }],
+        items : [{
+            module: BeatlesTable,
+            store: {
+                module: BeatlesStore
+            },
+        }]
+    }
+}
+Neo.applyClassConfig(MainView);
+
+ +There are several use-cases for creating your own classes: + +- For reuse +- To isolate complexity +- To add events or methods to the new class +- To test the component independently diff --git a/resources/data/deck/learnneo/t.json b/resources/data/deck/learnneo/t.json index 1ae4a3130..910589a28 100644 --- a/resources/data/deck/learnneo/t.json +++ b/resources/data/deck/learnneo/t.json @@ -5,11 +5,10 @@ {"name": "Workspaces and Applications", "parentId": "GettingStarted", "isLeaf": true, "id": "2023-10-14T19-25-08-153Z"}, {"name": "Describing a View", "parentId": "GettingStarted", "isLeaf": true, "id": "DescribingTheUI"}, {"name": "Events", "parentId": "GettingStarted", "isLeaf": true, "id": "Events"}, - {"name": "Component References", "parentId": "GettingStarted", "isLeaf": true, "id": "References"}, - {"name": "Updating View State", "parentId": "GettingStarted", "isLeaf": true, "id": "ComponentState"}, - {"name": "Extending Components", "parentId": "GettingStarted", "isLeaf": true, "id": "Extending"}, - {"name": "Adding Properties", "parentId": "GettingStarted", "isLeaf": true, "id": "AddingProperties"}, - {"name": "Component Models and Binding", "parentId": "GettingStarted", "isLeaf": true, "id": "ComponentModels"}, + {"name": "Component References", "parentId": "GettingStarted", "isLeaf": true, "id": "References"}, + {"name": "Extending Classes", "parentId": "GettingStarted", "isLeaf": true, "id": "Extending"}, + {"name": "Config", "parentId": "GettingStarted", "isLeaf": true, "id": "Config"}, + {"name": "Shared Binable Data", "parentId": "GettingStarted", "isLeaf": true, "id": "ComponentModels"}, {"name": "What about HTML tags?", "parentId": "GettingStarted", "isLeaf": true, "id": "WhatAboutHTML"}, {"name": "Tutorials", "parentId": null, "isLeaf": false, "expanded": false, "id": "Tutorials"}, {"name": "Rock Scissors Paper", "parentId": "Tutorials", "isLeaf": true, "expanded": false, "id": "RSP"},