neo.mjs 7.8.0 → 7.9.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.
@@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase {
20
20
  */
21
21
  singleton: true,
22
22
  /**
23
- * @member {String} version='7.8.0'
23
+ * @member {String} version='7.9.0'
24
24
  */
25
- version: '7.8.0'
25
+ version: '7.9.0'
26
26
  }
27
27
 
28
28
  /**
@@ -16,7 +16,7 @@
16
16
  "@type": "Organization",
17
17
  "name": "Neo.mjs"
18
18
  },
19
- "datePublished": "2024-09-21",
19
+ "datePublished": "2024-09-27",
20
20
  "publisher": {
21
21
  "@type": "Organization",
22
22
  "name": "Neo.mjs"
@@ -111,7 +111,7 @@ class FooterContainer extends Container {
111
111
  }, {
112
112
  module: Component,
113
113
  cls : ['neo-version'],
114
- html : 'v7.8.0'
114
+ html : 'v7.9.0'
115
115
  }]
116
116
  }],
117
117
  /**
@@ -141,7 +141,7 @@ class MainContainerController extends Controller {
141
141
  */
142
142
  onRouteDefault(data) {
143
143
  if (!this.getModel().data.currentPageRecord) {
144
- this.onRouteLearnItem({itemId: 'WhyNeo-Intro'})
144
+ this.onRouteLearnItem({itemId: 'benefits.Introduction'})
145
145
  }
146
146
  }
147
147
 
@@ -20,9 +20,9 @@ class ServiceWorker extends ServiceBase {
20
20
  */
21
21
  singleton: true,
22
22
  /**
23
- * @member {String} version='7.8.0'
23
+ * @member {String} version='7.9.0'
24
24
  */
25
- version: '7.8.0'
25
+ version: '7.9.0'
26
26
  }
27
27
 
28
28
  /**
@@ -0,0 +1,127 @@
1
+ import CheckBox from '../../../src/form/field/CheckBox.mjs';
2
+ import CountryField from '../../../src/form/field/Country.mjs';
3
+ import Dialog from '../../../src/dialog/Base.mjs';
4
+ import TextField from '../../../src/form/field/Text.mjs';
5
+
6
+ /**
7
+ * @class Neo.examples.table.nestedRecordFields.EditUserDialog
8
+ * @extends Neo.dialog.Base
9
+ */
10
+ class EditUserDialog extends Dialog {
11
+ static config = {
12
+ /**
13
+ * @member {String} className='Neo.examples.model.dialog.EditUserDialog'
14
+ * @protected
15
+ */
16
+ className: 'Neo.examples.model.dialog.EditUserDialog',
17
+ /**
18
+ * @member {String} closeAction='hide'
19
+ */
20
+ closeAction: 'hide',
21
+ /**
22
+ * @member {Object} containerConfig={style:{padding:'1em'}}
23
+ */
24
+ containerConfig: {
25
+ style: {
26
+ padding: '1em'
27
+ }
28
+ },
29
+ /**
30
+ * @member {Boolean} modal=true
31
+ */
32
+ modal: true,
33
+ /**
34
+ * @member {Record|null} record_=null
35
+ */
36
+ record_: null,
37
+ /**
38
+ * @member {String} title='Edit User'
39
+ */
40
+ title: 'Edit User',
41
+ /**
42
+ * @member {Object} itemDefaults
43
+ */
44
+ itemDefaults: {
45
+ flex : 'none',
46
+ labelWidth: 110
47
+ },
48
+ /**
49
+ * @member {Object[]} items
50
+ */
51
+ items: [{
52
+ module : TextField,
53
+ labelText: 'Firstname',
54
+ listeners: {change: 'up.onFirstnameFieldChange'},
55
+ reference: 'firstname-field'
56
+ }, {
57
+ module : TextField,
58
+ labelText: 'Lastname',
59
+ listeners: {change: 'up.onLastnameFieldChange'},
60
+ reference: 'lastname-field'
61
+ }, {
62
+ module : CountryField,
63
+ bind : {store: 'stores.countries'},
64
+ labelText : 'Country',
65
+ listeners : {change: 'up.onCountryFieldChange'},
66
+ reference : 'country-field',
67
+ valueField: 'code'
68
+ }, {
69
+ module : CheckBox,
70
+ labelText: 'Selected',
71
+ listeners: {change: 'up.onSelectedFieldChange'},
72
+ reference: 'selected-field',
73
+ style : {marginTop: '1em'}
74
+ }]
75
+ }
76
+
77
+ /**
78
+ * Triggered after the record config got changed
79
+ * @param {Record|null} value
80
+ * @param {Record|null} oldValue
81
+ * @protected
82
+ */
83
+ async afterSetRecord(value, oldValue) {
84
+ if (value) {
85
+ let me = this,
86
+ {record} = me;
87
+
88
+ // ensure the store has its data
89
+ await me.timeout(20);
90
+
91
+ me.getItem('country-field') .value = record.country;
92
+ me.getItem('firstname-field').value = record.user.firstname;
93
+ me.getItem('lastname-field') .value = record.user.lastname;
94
+ me.getItem('selected-field') .checked = record.annotations.selected
95
+ }
96
+ }
97
+
98
+ /**
99
+ * @param {Object} data
100
+ */
101
+ onCountryFieldChange(data) {
102
+ this.record.country = data.value.code
103
+ }
104
+
105
+ /**
106
+ * @param {Object} data
107
+ */
108
+ onFirstnameFieldChange(data) {
109
+ this.record.user.firstname = data.value
110
+ }
111
+
112
+ /**
113
+ * @param {Object} data
114
+ */
115
+ onLastnameFieldChange(data) {
116
+ this.record.user.lastname = data.value
117
+ }
118
+
119
+ /**
120
+ * @param {Object} data
121
+ */
122
+ onSelectedFieldChange(data) {
123
+ this.record.annotations.selected = data.value
124
+ }
125
+ }
126
+
127
+ export default Neo.setupClass(EditUserDialog);
@@ -0,0 +1,113 @@
1
+ import Button from '../../../src/button/Base.mjs';
2
+ import MainContainerModel from './MainContainerModel.mjs';
3
+ import TableContainer from '../../../src/table/Container.mjs';
4
+ import Viewport from '../../../src/container/Viewport.mjs';
5
+
6
+ /**
7
+ * @class Neo.examples.table.nestedRecordFields.MainContainer
8
+ * @extends Neo.container.Viewport
9
+ */
10
+ class MainContainer extends Viewport {
11
+ static config = {
12
+ /**
13
+ * @member {String} className='Neo.examples.table.nestedRecordFields.MainContainer'
14
+ * @protected
15
+ */
16
+ className: 'Neo.examples.table.nestedRecordFields.MainContainer',
17
+ /**
18
+ * @member {Object|String} layout='fit'
19
+ */
20
+ layout: 'fit',
21
+ /**
22
+ * @member {Neo.model.Component} model=MainContainerModel
23
+ */
24
+ model: MainContainerModel,
25
+ /**
26
+ * @member {Object} style={padding:'20px'}
27
+ */
28
+ style: {padding: '20px'},
29
+ /**
30
+ * @member {Object[]} items
31
+ */
32
+ items: [{
33
+ module: TableContainer,
34
+ bind : {store : 'stores.mainStore'},
35
+
36
+ columns: [
37
+ {dataField: 'user.firstname', text: 'Firstname'},
38
+ {dataField: 'user.lastname', text: 'Lastname'},
39
+ {dataField: 'githubId', text: 'Github Id'},
40
+ {dataField: 'country', text: 'Country', renderer: 'up.countryRenderer'},
41
+ {dataField: 'edit', text: 'Edit Action', renderer: 'up.editRenderer'}
42
+ ]
43
+ }]
44
+ }
45
+
46
+ /**
47
+ * @member {Neo.dialog.Base|null} dialog=null
48
+ */
49
+ dialog = null
50
+
51
+ /**
52
+ * @param {Object} data
53
+ */
54
+ countryRenderer({record}) {
55
+ let countryStore = this.getModel().getStore('countries');
56
+
57
+ if (countryStore.getCount() > 0) {
58
+ return countryStore.get(record.country).name
59
+ }
60
+
61
+ return ''
62
+ }
63
+
64
+ /**
65
+ * @param {Object} data
66
+ */
67
+ editButtonHandler(data) {
68
+ let me = this,
69
+ button = data.component,
70
+ {appName, dialog, windowId} = me,
71
+ {record} = button;
72
+
73
+ if (!dialog) {
74
+ import('./EditUserDialog.mjs').then(module => {
75
+ me.dialog = Neo.create({
76
+ module : module.default,
77
+ animateTargetId: button.id,
78
+ appName,
79
+ model : {parent: me.getModel()},
80
+ record,
81
+ windowId
82
+ })
83
+ })
84
+ } else {
85
+ dialog.animateTargetId = button.id;
86
+ dialog.record = record;
87
+
88
+ dialog.show()
89
+ }
90
+ }
91
+
92
+ /**
93
+ * @param {Object} data
94
+ */
95
+ editRenderer({column, index, record, tableContainer}) {
96
+ let me = this,
97
+ {appName, windowId} = me,
98
+ widgetId = `${column.id}-widget-${index}`,
99
+ button = (column.widgetMap || (column.widgetMap = {}))[widgetId] || (column.widgetMap[widgetId] = Neo.create({
100
+ module : Button,
101
+ appName,
102
+ handler : 'up.editButtonHandler',
103
+ parentId: tableContainer.id,
104
+ record,
105
+ text : 'Edit',
106
+ windowId
107
+ }));
108
+
109
+ return button.vdom
110
+ }
111
+ }
112
+
113
+ export default Neo.setupClass(MainContainer);
@@ -0,0 +1,61 @@
1
+ import MainStore from './MainStore.mjs';
2
+ import Model from '../../../src/model/Component.mjs';
3
+ import Store from '../../../src/data/Store.mjs';
4
+
5
+ const countrySymbol = Symbol.for('country');
6
+
7
+ /**
8
+ * @class Neo.examples.table.nestedRecordFields.MainContainerModel
9
+ * @extends Neo.model.Component
10
+ */
11
+ class MainContainerModel extends Model {
12
+ static config = {
13
+ /**
14
+ * @member {String} className='Neo.examples.table.nestedRecordFields.MainContainerModel'
15
+ * @protected
16
+ */
17
+ className: 'Neo.examples.table.nestedRecordFields.MainContainerModel',
18
+ /**
19
+ * @member {Object} stores
20
+ */
21
+ stores: {
22
+ countries: {
23
+ module : Store,
24
+ autoLoad : true,
25
+ keyProperty: 'code',
26
+ listeners : {load: 'onCountryStoreLoad'},
27
+ url : '../../../resources/examples/data/countries.json',
28
+
29
+ model: {
30
+ fields: [
31
+ {name: 'code'},
32
+ {name: 'name'}
33
+ ]},
34
+ },
35
+ mainStore: MainStore
36
+ }
37
+ }
38
+
39
+ /**
40
+ * @param {Record[]} items
41
+ */
42
+ onCountryStoreLoad(items) {
43
+ let me = this,
44
+ mainStore = me.getStore('mainStore'),
45
+ country;
46
+
47
+ // if the main table store is already loaded, the country field renderer had no data
48
+ if (mainStore.getCount() > 0) {
49
+ mainStore.items.forEach(record => {
50
+ country = record.country;
51
+
52
+ // hack resetting the current value to get a new record change
53
+ record[countrySymbol] = null;
54
+
55
+ record.country = country
56
+ })
57
+ }
58
+ }
59
+ }
60
+
61
+ export default Neo.setupClass(MainContainerModel);
@@ -0,0 +1,41 @@
1
+ import Model from '../../../src/data/Model.mjs';
2
+
3
+ /**
4
+ * @class Neo.examples.table.nestedRecordFields.MainModel
5
+ * @extends Neo.data.Model
6
+ */
7
+ class MainModel extends Model {
8
+ static config = {
9
+ className: 'Neo.examples.table.container.MainModel',
10
+
11
+ fields: [{
12
+ name: 'annotations',
13
+ type: 'Object',
14
+
15
+ fields: [{
16
+ name : 'annotations.selected',
17
+ type : 'Boolean',
18
+ defaultValue: false
19
+ }]
20
+ }, {
21
+ name: 'country',
22
+ type: 'String'
23
+ }, {
24
+ name: 'githubId',
25
+ type: 'String'
26
+ }, {
27
+ name: 'user',
28
+ type: 'Object',
29
+
30
+ fields: [{
31
+ name: 'user.firstname',
32
+ type: 'String'
33
+ }, {
34
+ name: 'user.lastname',
35
+ type: 'String'
36
+ }]
37
+ }]
38
+ }
39
+ }
40
+
41
+ export default Neo.setupClass(MainModel);
@@ -0,0 +1,54 @@
1
+ import Model from './MainModel.mjs';
2
+ import Store from '../../../src/data/Store.mjs';
3
+
4
+ /**
5
+ * @class Neo.examples.table.nestedRecordFields.MainStore
6
+ * @extends Neo.data.Store
7
+ */
8
+ class MainStore extends Store {
9
+ static config = {
10
+ className : 'Neo.examples.table.container.MainStore',
11
+ keyProperty: 'githubId',
12
+ model : Model,
13
+
14
+ data: [{
15
+ country : 'DE',
16
+ githubId: 'tobiu',
17
+
18
+ user: {
19
+ firstname: 'Tobias',
20
+ lastname : 'Uhlig'
21
+ }
22
+ }, {
23
+ annotations: {
24
+ selected: true
25
+ },
26
+
27
+ country : 'US',
28
+ githubId: 'rwaters',
29
+
30
+ user: {
31
+ firstname: 'Rich',
32
+ lastname : 'Waters'
33
+ }
34
+ }, {
35
+ country : 'DE',
36
+ githubId: 'mrsunshine',
37
+
38
+ user: {
39
+ firstname: 'Nils',
40
+ lastname : 'Dehl'
41
+ }
42
+ }, {
43
+ country : 'US',
44
+ githubId: 'camtnbikerrwc',
45
+
46
+ user: {
47
+ firstname: 'Gerard',
48
+ lastname : 'Horan'
49
+ }
50
+ }]
51
+ }
52
+ }
53
+
54
+ export default Neo.setupClass(MainStore);
@@ -0,0 +1,6 @@
1
+ import MainContainer from './MainContainer.mjs';
2
+
3
+ export const onStart = () => Neo.app({
4
+ mainView: MainContainer,
5
+ name : 'Neo.examples.table.nestedRecordFields'
6
+ });
@@ -0,0 +1,11 @@
1
+ <!DOCTYPE HTML>
2
+ <html>
3
+ <head>
4
+ <meta name="viewport" content="width=device-width, initial-scale=1">
5
+ <meta charset="UTF-8">
6
+ <title>Neo Table: Nested Record Fields</title>
7
+ </head>
8
+ <body>
9
+ <script src="../../../src/MicroLoader.mjs" type="module"></script>
10
+ </body>
11
+ </html>
@@ -0,0 +1,6 @@
1
+ {
2
+ "appPath" : "examples/table/nestedRecordFields/app.mjs",
3
+ "basePath" : "../../../",
4
+ "environment": "development",
5
+ "mainPath" : "./Main.mjs"
6
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "neo.mjs",
3
- "version": "7.8.0",
3
+ "version": "7.9.0",
4
4
  "description": "The webworkers driven UI framework",
5
5
  "type": "module",
6
6
  "repository": {
@@ -63,7 +63,7 @@
63
63
  "sass": "^1.79.3",
64
64
  "siesta-lite": "5.5.2",
65
65
  "url": "^0.11.4",
66
- "webpack": "^5.94.0",
66
+ "webpack": "^5.95.0",
67
67
  "webpack-cli": "^5.1.4",
68
68
  "webpack-dev-server": "^5.1.0",
69
69
  "webpack-hook-plugin": "^1.0.7",
@@ -14,6 +14,11 @@
14
14
  padding : 0 1rem;
15
15
  }
16
16
 
17
+
18
+ @media (min-width: 1297px) {
19
+ padding: 0 0 0 3rem;
20
+ }
21
+
17
22
  .neo-button {
18
23
  flex : 1 !important;
19
24
  height : 75px;
@@ -262,12 +262,12 @@ const DefaultConfig = {
262
262
  useVdomWorker: true,
263
263
  /**
264
264
  * buildScripts/injectPackageVersion.mjs will update this value
265
- * @default '7.8.0'
265
+ * @default '7.9.0'
266
266
  * @memberOf! module:Neo
267
267
  * @name config.version
268
268
  * @type String
269
269
  */
270
- version: '7.8.0'
270
+ version: '7.9.0'
271
271
  };
272
272
 
273
273
  Object.assign(DefaultConfig, {
@@ -38,13 +38,6 @@ class Model extends Base {
38
38
  trackModifiedFields: false
39
39
  }
40
40
 
41
- /**
42
- * A property set in all data records so that they are easily identifiable.
43
- * @property {Boolean} isRecord=true
44
- * @readonly
45
- */
46
- isRecord = true
47
-
48
41
  /**
49
42
  * Finds a field config by a given field name
50
43
  * @param {String} name
@@ -16,11 +16,6 @@ class RecordFactory extends Base {
16
16
  * @protected
17
17
  */
18
18
  className: 'Neo.data.RecordFactory',
19
- /**
20
- * @member {Boolean} singleton=true
21
- * @protected
22
- */
23
- singleton: true,
24
19
  /**
25
20
  * The internal record prefix for original field values.
26
21
  * Only used in case the model has trackModifiedFields set to true.
@@ -30,7 +25,84 @@ class RecordFactory extends Base {
30
25
  /**
31
26
  * @member {String} recordNamespace='Neo.data.record'
32
27
  */
33
- recordNamespace: 'Neo.data.record'
28
+ recordNamespace: 'Neo.data.record',
29
+ /**
30
+ * @member {Boolean} singleton=true
31
+ * @protected
32
+ */
33
+ singleton: true
34
+ }
35
+
36
+ /**
37
+ * @param {Object} data
38
+ * @param {Object} data.config
39
+ * @param {Object} data.field
40
+ * @param {Neo.data.RecordFactory} data.me
41
+ * @param {Neo.data.Model} data.model
42
+ * @param {String} data.path=''
43
+ */
44
+ createField({config, field, me, model, path=''}) {
45
+ let value = Neo.ns(field.mapping || field.name, false, config),
46
+ fieldName = field.name.split('.').pop(),
47
+ symbol = Symbol.for(fieldName),
48
+ fieldPath, parsedValue, properties;
49
+
50
+ if (field.fields) {
51
+ field.fields.forEach(childField => {
52
+ fieldPath = path.split('.');
53
+ fieldPath = fieldPath.filter(Boolean);
54
+ fieldPath.push(field.name);
55
+
56
+ this.createField({config, field: childField, me, model, path: fieldPath.join('.')})
57
+ })
58
+ } else {
59
+ if (value === undefined && Object.hasOwn(field, 'defaultValue')) {
60
+ value = field.defaultValue
61
+ }
62
+
63
+ parsedValue = instance.parseRecordValue(me, field, value, config);
64
+
65
+ properties = {
66
+ [symbol]: {
67
+ value : Neo.clone(parsedValue, true),
68
+ writable: true
69
+ },
70
+ [fieldName]: {
71
+ configurable: true,
72
+ enumerable : true,
73
+ get() {
74
+ return this[symbol]
75
+ },
76
+ set(value) {
77
+ let oldValue = this[symbol];
78
+
79
+ value = instance.parseRecordValue(me, field, value);
80
+
81
+ if (!Neo.isEqual(value, oldValue)) {
82
+ this[symbol] = value;
83
+
84
+ me._isModified = true;
85
+ me._isModified = instance.isModified(me, model.trackModifiedFields);
86
+
87
+ instance.onRecordChange({
88
+ fields: [{name: field.name, oldValue, value}],
89
+ model,
90
+ record: me
91
+ })
92
+ }
93
+ }
94
+ }
95
+ };
96
+
97
+ // adding the original value of each field
98
+ if (model.trackModifiedFields) {
99
+ properties[instance.ovPrefix + field.name] = {
100
+ value: parsedValue
101
+ }
102
+ }
103
+
104
+ Object.defineProperties(path ? Neo.ns(path, true, me) : me, properties)
105
+ }
34
106
  }
35
107
 
36
108
  /**
@@ -67,8 +139,7 @@ class RecordFactory extends Base {
67
139
  static name = 'Record'
68
140
 
69
141
  constructor(config) {
70
- let me = this,
71
- properties;
142
+ let me = this;
72
143
 
73
144
  Object.defineProperties(me, {
74
145
  _isModified: {
@@ -79,57 +150,7 @@ class RecordFactory extends Base {
79
150
 
80
151
  if (Array.isArray(model.fields)) {
81
152
  model.fields.forEach(field => {
82
- let value = config[field.mapping || field.name],
83
- symbol = Symbol.for(field.name),
84
- parsedValue;
85
-
86
- if (!Object.hasOwn(config, field.name) && Object.hasOwn(field, 'defaultValue')) {
87
- value = field.defaultValue
88
- }
89
-
90
- parsedValue = instance.parseRecordValue(me, field, value, config);
91
-
92
- properties = {
93
- [symbol]: {
94
- value : parsedValue,
95
- writable: true
96
- },
97
- [field.name]: {
98
- configurable: true,
99
- enumerable : true,
100
- get() {
101
- return this[symbol]
102
- },
103
- set(value) {
104
- let me = this,
105
- oldValue = me[symbol];
106
-
107
- value = instance.parseRecordValue(me, field, value);
108
-
109
- if (!Neo.isEqual(value, oldValue)) {
110
- me[symbol] = value;
111
-
112
- me._isModified = true;
113
- me._isModified = instance.isModified(me, model.trackModifiedFields);
114
-
115
- instance.onRecordChange({
116
- fields: [{name: field.name, oldValue, value}],
117
- model,
118
- record: me
119
- })
120
- }
121
- }
122
- }
123
- };
124
-
125
- // adding the original value of each field
126
- if (model.trackModifiedFields) {
127
- properties[instance.ovPrefix + field.name] = {
128
- value: parsedValue
129
- }
130
- }
131
-
132
- Object.defineProperties(me, properties)
153
+ instance.createField({config, field, me, model})
133
154
  })
134
155
  }
135
156
  }
@@ -151,8 +172,8 @@ class RecordFactory extends Base {
151
172
  }
152
173
  };
153
174
 
154
- Object.defineProperty(cls.prototype, 'isRecord', { value : true });
155
- Object.defineProperty(cls, 'isClass', { vale : true });
175
+ Object.defineProperty(cls.prototype, 'isRecord', {value: true});
176
+ Object.defineProperty(cls, 'isClass', {value: true});
156
177
 
157
178
  return ns[key]
158
179
  }
@@ -309,6 +309,13 @@ class Store extends Base {
309
309
  RecordFactory.createRecord(config)
310
310
  }
311
311
 
312
+ /**
313
+ * @returns {String}
314
+ */
315
+ getKeyProperty() {
316
+ return this.keyProperty || this.model.keyProperty
317
+ }
318
+
312
319
  /**
313
320
  * @param {Object} opts={}
314
321
  * @param {Object} opts.data
@@ -456,14 +456,19 @@ class CheckBox extends Base {
456
456
  */
457
457
  getGroupValue() {
458
458
  let form = this.getClosestForm(),
459
- fields = ComponentManager.find({path: this.getPath()}),
460
- value = [];
459
+ path = this.getPath(),
460
+ value = [],
461
+ fields;
461
462
 
462
- fields.forEach(field => {
463
- if (field.checked && field.getClosestForm() === form) {
464
- NeoArray.add(value, field.value)
465
- }
466
- });
463
+ if (path) {
464
+ fields = ComponentManager.find({path});
465
+
466
+ fields.forEach(field => {
467
+ if (field.checked && field.getClosestForm() === form) {
468
+ NeoArray.add(value, field.value)
469
+ }
470
+ })
471
+ }
467
472
 
468
473
  return value
469
474
  }
@@ -346,6 +346,7 @@ class ComboBox extends Picker {
346
346
  role : 'listbox',
347
347
  selectionModel: {stayInList: false},
348
348
  store : me.store,
349
+ windowId : me.windowId,
349
350
  ...me.listConfig
350
351
  });
351
352
 
@@ -580,18 +581,18 @@ class ComboBox extends Picker {
580
581
  }
581
582
 
582
583
  /**
583
- * @param {Object} record
584
+ * @param {Object} data
584
585
  * @protected
585
586
  */
586
- onListItemNavigate(record) {
587
- let {activeIndex} = record;
587
+ onListItemNavigate(data) {
588
+ let {activeIndex} = data;
588
589
 
589
590
  if (activeIndex >= 0) {
590
591
  let me = this,
591
592
  {store} = me;
592
593
 
593
- me.activeRecord = store.getAt(activeIndex);
594
- me.activeRecordId = me.activeRecord[store.keyProperty || model.keyProperty];
594
+ me.activeRecord = data.record || store.getAt(activeIndex);
595
+ me.activeRecordId = me.activeRecord[store.getKeyProperty()];
595
596
 
596
597
  // Update typeahead hint (which updates DOM), or update DOM
597
598
  me.typeAhead ? me.updateTypeAheadValue(me.lastManualInput) : me.update()
@@ -737,7 +738,7 @@ class ComboBox extends Picker {
737
738
  if (match && inputHintEl) {
738
739
  inputHintEl.value = value + match[displayField].substr(value.length);
739
740
  me.activeRecord = match;
740
- me.activeRecordId = match[store.keyProperty || store.model.keyProperty]
741
+ me.activeRecordId = match[store.getKeyProperty()]
741
742
  }
742
743
  }
743
744
 
@@ -771,7 +772,7 @@ class ComboBox extends Picker {
771
772
  * The select event fires when a list item gets selected
772
773
  * @event select
773
774
  * @param {Object} record
774
- * @param {value} record[store.keyProperty]
775
+ * @param {value} record[store.getKeyProperty()]
775
776
  * @returns {Object}
776
777
  */
777
778
 
package/src/grid/View.mjs CHANGED
@@ -95,9 +95,9 @@ class View extends Component {
95
95
 
96
96
  for (; j < colCount; j++) {
97
97
  column = columns[j];
98
- rendererValue = record[column.field];
98
+ rendererValue = Neo.ns(column.field, false, record);
99
99
 
100
- if (rendererValue === undefined) {
100
+ if (rendererValue === null || rendererValue === undefined) {
101
101
  rendererValue = ''
102
102
  }
103
103
 
package/src/list/Base.mjs CHANGED
@@ -689,7 +689,7 @@ class Base extends Component {
689
689
  getItemRecordId(vnodeId) {
690
690
  let itemId = vnodeId.split('__')[1],
691
691
  {model} = this.store,
692
- keyField = model?.getField(model.keyProperty),
692
+ keyField = model?.getField(this.getKeyProperty()),
693
693
  keyType = keyField?.type?.toLowerCase();
694
694
 
695
695
  if (keyType === 'int' || keyType === 'integer') {
@@ -155,10 +155,19 @@ class Component extends Base {
155
155
  */
156
156
  beforeSetStores(value, oldValue) {
157
157
  if (value) {
158
- let controller = this.component.getController();
158
+ let me = this,
159
+ controller = me.getController();
159
160
 
160
161
  Object.entries(value).forEach(([key, storeValue]) => {
161
162
  controller?.parseConfig(storeValue);
163
+
164
+ // support mapping string based listeners into the model instance
165
+ Object.entries(storeValue.listeners || {}).forEach(([listenerKey,listener]) => {
166
+ if (Neo.isString(listener) && Neo.isFunction(me[listener])) {
167
+ storeValue.listeners[listenerKey] = me[listener].bind(me)
168
+ }
169
+ })
170
+
162
171
  value[key] = ClassSystemUtil.beforeSetInstance(storeValue)
163
172
  })
164
173
  }
@@ -1,4 +1,5 @@
1
1
  import Component from '../component/Base.mjs';
2
+ import NeoArray from '../util/Array.mjs';
2
3
  import VDomUtil from '../util/VDom.mjs';
3
4
 
4
5
  /**
@@ -35,6 +36,10 @@ class View extends Component {
35
36
  * @member {Object} recordVnodeMap={}
36
37
  */
37
38
  recordVnodeMap: {},
39
+ /**
40
+ * @member {String} selectedRecordField='annotations.selected'
41
+ */
42
+ selectedRecordField: 'annotations.selected',
38
43
  /**
39
44
  * @member {Neo.data.Store|null} store=null
40
45
  */
@@ -86,7 +91,7 @@ class View extends Component {
86
91
  cellCls = ['neo-table-cell'],
87
92
  colspan = record[me.colspanField],
88
93
  {dataField} = column,
89
- fieldValue = record[dataField],
94
+ fieldValue = Neo.ns(dataField, false, record),
90
95
  hasStore = tableContainer.store?.model, // todo: remove as soon as all tables use stores (examples table)
91
96
  {vdom} = me,
92
97
  cellConfig, rendererOutput;
@@ -189,6 +194,10 @@ class View extends Component {
189
194
 
190
195
  trCls = me.getTrClass(record, i);
191
196
 
197
+ if (selectedRows && Neo.ns(me.selectedRecordField, false, record)) {
198
+ NeoArray.add(selectedRows, id)
199
+ }
200
+
192
201
  if (selectedRows?.includes(id)) {
193
202
  trCls.push('neo-selected');
194
203
 
@@ -432,11 +441,12 @@ class View extends Component {
432
441
  * @param {Object} opts.record
433
442
  */
434
443
  onStoreRecordChange(opts) {
435
- let me = this,
436
- fieldNames = opts.fields.map(field => field.name),
437
- needsUpdate = false,
438
- tableContainer = me.parent,
439
- {vdom} = me,
444
+ let me = this,
445
+ fieldNames = opts.fields.map(field => field.name),
446
+ needsUpdate = false,
447
+ tableContainer = me.parent,
448
+ {selectionModel} = tableContainer,
449
+ {vdom} = me,
440
450
  cellId, cellNode, column, index, scope;
441
451
 
442
452
  if (fieldNames.includes(me.colspanField)) {
@@ -444,17 +454,23 @@ class View extends Component {
444
454
  me.createViewData(me.store.items)
445
455
  } else {
446
456
  opts.fields.forEach(field => {
447
- cellId = me.getCellId(opts.record, field.name);
448
- cellNode = VDomUtil.findVdomChild(vdom, cellId);
457
+ if (field.name === me.selectedRecordField) {
458
+ if (selectionModel.ntype === 'selection-table-rowmodel') {
459
+ selectionModel[!field.value && selectionModel.singleSelect ? 'deselect' : 'select'](me.getRowId(opts.record))
460
+ }
461
+ } else {
462
+ cellId = me.getCellId(opts.record, field.name);
463
+ cellNode = VDomUtil.findVdomChild(vdom, cellId);
449
464
 
450
- // the vdom might not exist yet => nothing to do in this case
451
- if (cellNode?.vdom) {
452
- column = me.getColumn(field.name);
453
- index = cellNode.index;
454
- needsUpdate = true;
455
- scope = column.rendererScope || tableContainer;
465
+ // the vdom might not exist yet => nothing to do in this case
466
+ if (cellNode?.vdom) {
467
+ column = me.getColumn(field.name);
468
+ index = cellNode.index;
469
+ needsUpdate = true;
470
+ scope = column.rendererScope || tableContainer;
456
471
 
457
- cellNode.parentNode.cn[index] = me.applyRendererOutput({cellId, column, record: opts.record, index, tableContainer})
472
+ cellNode.parentNode.cn[index] = me.applyRendererOutput({cellId, column, record: opts.record, index, tableContainer})
473
+ }
458
474
  }
459
475
  })
460
476
  }
@@ -1,6 +1,7 @@
1
- import BaseButton from '../../button/Base.mjs';
2
- import NeoArray from '../../util/Array.mjs';
3
- import TextField from '../../form/field/Text.mjs';
1
+ import BaseButton from '../../button/Base.mjs';
2
+ import NeoArray from '../../util/Array.mjs';
3
+ import TextField from '../../form/field/Text.mjs';
4
+ import {resolveCallback} from '../../util/Function.mjs';
4
5
 
5
6
  /**
6
7
  * @class Neo.table.header.Button
@@ -75,6 +76,10 @@ class Button extends BaseButton {
75
76
  * @protected
76
77
  */
77
78
  isSorted_: null,
79
+ /**
80
+ * @member {Function|String|null} renderer_='cellRenderer'
81
+ */
82
+ renderer_: 'cellRenderer',
78
83
  /**
79
84
  * Scope to execute the column renderer.
80
85
  * Defaults to the matching table.Container
@@ -261,7 +266,31 @@ class Button extends BaseButton {
261
266
  * @protected
262
267
  */
263
268
  beforeSetCellAlign(value, oldValue) {
264
- return this.beforeSetEnumValue(value, oldValue, 'cellAlign', 'cellAlignValues');
269
+ return this.beforeSetEnumValue(value, oldValue, 'cellAlign', 'cellAlignValues')
270
+ }
271
+
272
+ /**
273
+ * Triggered before the renderer config gets changed
274
+ * @param {Function|String|null} value
275
+ * @param {Function|String|null} oldValue
276
+ * @protected
277
+ */
278
+ beforeSetRenderer(value, oldValue) {
279
+ return resolveCallback(value, this).fn
280
+ }
281
+
282
+ /**
283
+ * @param {Object} data
284
+ * @param {Neo.button.Base} data.column
285
+ * @param {String} data.dataField
286
+ * @param {Number} data.index
287
+ * @param {Object} data.record
288
+ * @param {Neo.table.Container} data.tableContainer
289
+ * @param {Number|String} data.value
290
+ * @returns {*}
291
+ */
292
+ cellRenderer(data) {
293
+ return data.value
265
294
  }
266
295
 
267
296
  /**
@@ -461,20 +490,6 @@ class Button extends BaseButton {
461
490
  me.cls = cls;
462
491
  me._isSorted = null
463
492
  }
464
-
465
- /**
466
- * @param {Object} data
467
- * @param {Neo.button.Base} data.column
468
- * @param {String} data.dataField
469
- * @param {Number} data.index
470
- * @param {Object} data.record
471
- * @param {Neo.table.Container} data.tableContainer
472
- * @param {Number|String} data.value
473
- * @returns {*}
474
- */
475
- renderer(data) {
476
- return data.value
477
- }
478
493
  }
479
494
 
480
495
  export default Neo.setupClass(Button);