diff options
author | Matthias Villiger | 2021-05-20 12:04:18 +0000 |
---|---|---|
committer | Matthias Villiger | 2021-05-27 09:14:24 +0000 |
commit | 39192bb0e5bc5c5e03f174c31765e543d33f52ef (patch) | |
tree | 14b82ff57b0ae2886e5a93200846ad4763df84a7 | |
parent | 19f968c5fed17c669ac90c94c9cf395ae90d780a (diff) | |
download | org.eclipse.scout.rt-39192bb0e5bc5c5e03f174c31765e543d33f52ef.tar.gz org.eclipse.scout.rt-39192bb0e5bc5c5e03f174c31765e543d33f52ef.tar.xz org.eclipse.scout.rt-39192bb0e5bc5c5e03f174c31765e543d33f52ef.zip |
Lazily create detailForm and detailTable for Scout JS pages
245158
Change-Id: I9ddfd00a847c1eb527c52f23858b57072f3b9337
9 files changed, 383 insertions, 77 deletions
diff --git a/eclipse-scout-core/src/desktop/outline/Outline.js b/eclipse-scout-core/src/desktop/outline/Outline.js index c08c3851bf..71b58cd2a7 100644 --- a/eclipse-scout-core/src/desktop/outline/Outline.js +++ b/eclipse-scout-core/src/desktop/outline/Outline.js @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2018 BSI Business Systems Integration AG. + * Copyright (c) 2010-2021 BSI Business Systems Integration AG. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -602,8 +602,10 @@ export default class Outline extends Tree { } _appendNavigateButtonsForDetailForm(node) { - let menus = this._createNavigateButtons(node, node.detailForm.rootGroupBox); - node.detailForm.rootGroupBox.setStaticMenus(menus); + if (node.detailForm.rootGroupBox) { + let menus = this._createNavigateButtons(node, node.detailForm.rootGroupBox); + node.detailForm.rootGroupBox.setStaticMenus(menus); + } } _appendNavigateButtonsForDetailTable(node) { @@ -612,10 +614,12 @@ export default class Outline extends Tree { } _removeNavigateButtonsForDetailForm(node) { - let staticMenus = node.detailForm.rootGroupBox.staticMenus.filter(menu => { - return !(menu instanceof NavigateButton); - }); - node.detailForm.rootGroupBox.setStaticMenus(staticMenus); + if (node.detailForm.rootGroupBox) { + let staticMenus = node.detailForm.rootGroupBox.staticMenus.filter(menu => { + return !(menu instanceof NavigateButton); + }); + node.detailForm.rootGroupBox.setStaticMenus(staticMenus); + } } _removeNavigateButtonsForDetailTable(node) { @@ -885,9 +889,11 @@ export default class Outline extends Tree { if (this.detailContent && this.detailContent instanceof Form) { // Get menus from detail form let rootGroupBox = this.detailContent.rootGroupBox; - menuItems = rootGroupBox.processMenus.concat(rootGroupBox.menus); - rootGroupBox.setMenuBarVisible(false); - this._attachDetailMenusListener(rootGroupBox); + if (rootGroupBox) { + menuItems = rootGroupBox.processMenus.concat(rootGroupBox.menus); + rootGroupBox.setMenuBarVisible(false); + this._attachDetailMenusListener(rootGroupBox); + } } else if (selectedPage && !(this.detailContent instanceof OutlineOverview)) { // Get empty space menus and table controls from detail table // DetailContent can be null or it is the tableRowDetail. Don't show menus on OutlineOverview. diff --git a/eclipse-scout-core/src/desktop/outline/OutlineAdapter.js b/eclipse-scout-core/src/desktop/outline/OutlineAdapter.js index 99b2977c2b..9d42ec0852 100644 --- a/eclipse-scout-core/src/desktop/outline/OutlineAdapter.js +++ b/eclipse-scout-core/src/desktop/outline/OutlineAdapter.js @@ -27,9 +27,7 @@ export default class OutlineAdapter extends TreeAdapter { page.detailFormVisible = event.detailFormVisible; let detailForm = this.session.getOrCreateWidget(event.detailForm, this.widget); - if (detailForm !== page.detailForm) { - page.setDetailForm(detailForm); - } + page.setDetailForm(detailForm); page.navigateButtonsVisible = event.navigateButtonsVisible; page.detailTableVisible = event.detailTableVisible; diff --git a/eclipse-scout-core/src/desktop/outline/pages/Page.js b/eclipse-scout-core/src/desktop/outline/pages/Page.js index 0bf0799a26..f9f89481d3 100644 --- a/eclipse-scout-core/src/desktop/outline/pages/Page.js +++ b/eclipse-scout-core/src/desktop/outline/pages/Page.js @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2020 BSI Business Systems Integration AG. + * Copyright (c) 2010-2021 BSI Business Systems Integration AG. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -8,7 +8,7 @@ * Contributors: * BSI Business Systems Integration AG - initial API and implementation */ -import {icons, inspector, MenuBar, Outline, TableRow, TileOutlineOverview, TreeNode} from '../../../index'; +import {Event, EventSupport, Form, icons, inspector, MenuBar, Outline, TableRow, scout, TileOutlineOverview, TileOverviewForm, TreeNode, Widget} from '../../../index'; import $ from 'jquery'; /** @@ -38,10 +38,6 @@ export default class Page extends TreeNode { this.detailFormVisibleByUi = true; this.navigateButtonsVisible = true; - /** - * This property contains the class-name of the form to be instantiated, when createDetailForm() is called. - */ - this.detailFormType = null; this.tableStatusVisible = true; /** * True to select the page linked with the selected row when the row was selected. May be useful on touch devices. @@ -52,6 +48,14 @@ export default class Page extends TreeNode { */ this.overviewIconId = null; this.showTileOverview = false; + this.events = new EventSupport(); + this.events.registerSubTypePredicate('propertyChange', (event, propertyName) => { + return event.propertyName === propertyName; + }); + this._tableFilterHandler = this._onTableFilter.bind(this); + this._tableRowClickHandler = this._onTableRowClick.bind(this); + this._detailTableModel = null; + this._detailFormModel = null; } /** @@ -67,23 +71,36 @@ export default class Page extends TreeNode { }; /** - * Override this function to return a detail form which is displayed in the outline when this page is selected. - * The default impl. returns null. - */ - createDetailForm() { - return null; - } - - /** * @override TreeNode.js */ _init(model) { + this._detailTableModel = Page._removePropertyIfLazyLoading(model, 'detailTable'); + this._detailFormModel = Page._removePropertyIfLazyLoading(model, 'detailForm'); + super._init(model); icons.resolveIconProperty(this, 'overviewIconId'); + + // init necessary if the properties are still available (e.g. Scout classic) this._internalInitTable(); this._internalInitDetailForm(); } + static _removePropertyIfLazyLoading(object, name) { + let prop = object[name]; + if (typeof prop === 'string') { + // Scout Classic: it is an object id -> do not remove it. directly create the widget. lazy loading is done on backend + return null; + } + if (prop instanceof Widget) { + // it already is a widget. directly use it. + return null; + } + + // otherwise: remove the property and return it + delete object[name]; + return prop; + } + /** * @override TreeNode.js */ @@ -98,55 +115,139 @@ export default class Page extends TreeNode { } _internalInitTable() { - let table = this.detailTable; - if (table) { + let tableModel = this.detailTable; + if (tableModel) { // this case is used for Scout classic - table = this.getOutline()._createChild(table); - } else { - table = this._createTable(); + let newDetailTable = this.getOutline()._createChild(tableModel); + this._setDetailTable(newDetailTable); } - - this.setDetailTable(table); } _internalInitDetailForm() { - let detailForm = this.detailForm; - if (detailForm) { - detailForm = this.getOutline()._createChild(detailForm); + let formModel = this.detailForm; + if (formModel) { + let newDetailForm = this.getOutline()._createChild(formModel); + this._setDetailForm(newDetailForm); } + } - this.setDetailForm(detailForm); + ensureDetailTable() { + if (this.detailTable) { + return; + } + this.setDetailTable(this.createDetailTable()); + } + + /** + * Creates the detail table + * @returns {Table} the created table or null + */ + createDetailTable() { + let detailTable = this._createDetailTable(); + if (!detailTable && this._detailTableModel) { + detailTable = this.getOutline()._createChild(this._detailTableModel); + this._detailTableModel = null; // no longer needed + } + return detailTable; } /** * Override this function to create the internal table. Default impl. returns null. + * + * @returns {Table} + */ + _createDetailTable() { + return null; + } + + ensureDetailForm() { + if (this.detailForm) { + return; + } + this.setDetailForm(this.createDetailForm()); + } + + /** + * Creates the detail form + * @returns {Form|*} the created form or null + */ + createDetailForm() { + let detailForm = this._createDetailForm(); + if (!detailForm && this._detailFormModel) { + detailForm = this.getOutline()._createChild(this._detailFormModel); + this._detailFormModel = null; // no longer needed + } + return detailForm; + } + + /** + * Override this function to return a detail form which is displayed in the outline when this page is selected. + * The default implementation returns null. + * + * @returns {Form|*} */ - _createTable() { + _createDetailForm() { return null; } /** - * Override this function to initialize the internal (detail) table. Default impl. delegates - * <code>filter</code> events to the outline mediator. + * Override this function to initialize the internal detail form. + * @param {Form} form the form to initialize. + */ + _initDetailForm(form) { + if (form instanceof Form) { + form.setModal(false); + } + if (!form.displayParent && form.setDisplayParent) { + form.setDisplayParent(this.getOutline()); + } + if (form instanceof TileOverviewForm) { + form.setPage(this); + } + } + + /** + * Override this function to destroy the internal (detail) form. + * @param {Form} form the form to destroy. */ - _initTable(table) { + _destroyDetailForm(form) { + if (form instanceof TileOverviewForm) { + form.setPage(null); + } + if (form.owner === this.getOutline()) { + // in Scout classic the owner is not an outline but the NullWidget. + // Then the destroy is controlled by the backend + form.destroy(); + } + } + + /** + * Override this function to initialize the internal (detail) table. + * Default impl. delegates filter events to the outline mediator. + * @param {Table} table The table to initialize. + */ + _initDetailTable(table) { table.menuBar.setPosition(MenuBar.Position.TOP); - table.on('filter', this._onTableFilter.bind(this)); + table.on('filter', this._tableFilterHandler); if (this.drillDownOnRowClick) { - table.on('rowClick', this._onTableRowClick.bind(this)); + table.on('rowClick', this._tableRowClickHandler); table.setMultiSelect(false); } + table.setTableStatusVisible(this.tableStatusVisible); } - _ensureDetailForm() { - if (this.detailForm) { - return; - } - let form = this.createDetailForm(); - if (form && !form.displayParent) { - form.setDisplayParent(this.getOutline()); + /** + * Override this function to destroy the internal (detail) table. + * @param {Table} table the table to destroy. + */ + _destroyDetailTable(table) { + table.off('filter', this._tableFilterHandler); + table.off('rowClick', this._tableRowClickHandler); + if (table.owner === this.getOutline()) { + // in Scout classic the owner is not an outline but the NullWidget. + // Then the destroy is controlled by the backend + table.destroy(); } - this.setDetailForm(form); } /** @@ -167,7 +268,8 @@ export default class Page extends TreeNode { // see Java: AbstractPage#pageActivatedNotify activate() { - this._ensureDetailForm(); + this.ensureDetailTable(); + this.ensureDetailForm(); } // see Java: AbstractPage#pageDeactivatedNotify @@ -197,22 +299,50 @@ export default class Page extends TreeNode { return row.page; } + /** + * @param {Form} form The new form + */ setDetailForm(form) { - this.detailForm = form; - if (this.detailForm) { - this.detailForm.setModal(false); + if (form === this.detailForm) { + return; } - if (this.detailForm instanceof scout.TileOverviewForm) { - this.detailForm.setPage(this); + this._setDetailForm(form); + } + + _setDetailForm(form) { + let oldDetailForm = this.detailForm; + if (oldDetailForm !== form && oldDetailForm instanceof Widget) { + // must be a widget to be destroyed. At startup in Scout Classic it might be a string (the widget id) + this._destroyDetailForm(oldDetailForm); } + this.detailForm = form; + if (form) { + this._initDetailForm(form); + } + this.triggerPropertyChange('detailForm', oldDetailForm, form); } + /** + * @param {Table} table The new table + */ setDetailTable(table) { - if (table) { - this._initTable(table); - table.setTableStatusVisible(this.tableStatusVisible); + if (table === this.detailTable) { + return; + } + this._setDetailTable(table); + } + + _setDetailTable(table) { + let oldDetailTable = this.detailTable; + if (oldDetailTable !== table && oldDetailTable instanceof Widget) { + // must be a widget to be destroyed. At startup in Scout Classic it might be a string (the widget id) + this._destroyDetailTable(oldDetailTable); } this.detailTable = table; + if (table) { + this._initDetailTable(table); + } + this.triggerPropertyChange('detailTable', oldDetailTable, table); } /** @@ -306,4 +436,36 @@ export default class Page extends TreeNode { this.getOutline().selectNode(drillNode); this.detailTable.deselectRow(row); } + + /** + * Triggers a property change for a single property. + */ + triggerPropertyChange(propertyName, oldValue, newValue) { + scout.assertParameter('propertyName', propertyName); + let event = new Event({ + propertyName: propertyName, + oldValue: oldValue, + newValue: newValue + }); + this.trigger('propertyChange', event); + return event; + } + + trigger(type, event) { + event = event || {}; + event.source = this; + this.events.trigger(type, event); + } + + one(type, func) { + this.events.one(type, func); + } + + on(type, func) { + return this.events.on(type, func); + } + + off(type, func) { + this.events.off(type, func); + } } diff --git a/eclipse-scout-core/src/desktop/outline/pages/PageWithNodes.js b/eclipse-scout-core/src/desktop/outline/pages/PageWithNodes.js index 121678aa3d..eb4c7faf38 100644 --- a/eclipse-scout-core/src/desktop/outline/pages/PageWithNodes.js +++ b/eclipse-scout-core/src/desktop/outline/pages/PageWithNodes.js @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2017 BSI Business Systems Integration AG. + * Copyright (c) 2010-2021 BSI Business Systems Integration AG. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -26,7 +26,7 @@ export default class PageWithNodes extends Page { /** * @override Page.js */ - _createTable() { + _createDetailTable() { let nodeColumn = scout.create('Column', { id: 'NodeColumn', session: this.session @@ -49,6 +49,9 @@ export default class PageWithNodes extends Page { _rebuildDetailTable(childPages) { let table = this.detailTable; + if (!table) { + return; + } this._unlinkAllTableRows(table.rows); table.deleteAllRows(); let rows = this._createTableRowsForChildPages(childPages); diff --git a/eclipse-scout-core/src/desktop/outline/pages/PageWithTable.js b/eclipse-scout-core/src/desktop/outline/pages/PageWithTable.js index 35260040b1..504bd6e58d 100644 --- a/eclipse-scout-core/src/desktop/outline/pages/PageWithTable.js +++ b/eclipse-scout-core/src/desktop/outline/pages/PageWithTable.js @@ -1,5 +1,5 @@ /* - * Copyright (c) 2014-2017 BSI Business Systems Integration AG. + * Copyright (c) 2010-2021 BSI Business Systems Integration AG. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -22,22 +22,42 @@ export default class PageWithTable extends Page { this.nodeType = Page.NodeType.TABLE; this.alwaysCreateChildPage = false; + + this._tableRowDeleteHandler = this._onTableRowsDeleted.bind(this); + this._tableRowInsertHandler = this._onTableRowsInserted.bind(this); + this._tableRowUpdateHandler = this._onTableRowsUpdated.bind(this); + this._tableRowActionHandler = this._onTableRowAction.bind(this); + this._tableRowOrderChangeHandler = this._onTableRowOrderChanged.bind(this); + this._tableDataLoadHandler = this.loadTableData.bind(this); } /** * @override Page */ - _initTable(table) { - super._initTable(table); - table.on('rowsDeleted allRowsDeleted', this._onTableRowsDeleted.bind(this)); - table.on('rowsInserted', this._onTableRowsInserted.bind(this)); - table.on('rowsUpdated', this._onTableRowsUpdated.bind(this)); - table.on('rowAction', this._onTableRowAction.bind(this)); - table.on('rowOrderChanged', this._onTableRowOrderChanged.bind(this)); - table.on('reload', this.loadTableData.bind(this)); + _initDetailTable(table) { + super._initDetailTable(table); + table.on('rowsDeleted allRowsDeleted', this._tableRowDeleteHandler); + table.on('rowsInserted', this._tableRowInsertHandler); + table.on('rowsUpdated', this._tableRowUpdateHandler); + table.on('rowAction', this._tableRowActionHandler); + table.on('rowOrderChanged', this._tableRowOrderChangeHandler); + table.on('reload', this._tableDataLoadHandler); table.hasReloadHandler = true; } + /** + * @override Page + */ + _destroyDetailTable(table) { + table.off('rowsDeleted allRowsDeleted', this._tableRowDeleteHandler); + table.off('rowsInserted', this._tableRowInsertHandler); + table.off('rowsUpdated', this._tableRowUpdateHandler); + table.off('rowAction', this._tableRowActionHandler); + table.off('rowOrderChanged', this._tableRowOrderChangeHandler); + table.off('reload', this._tableDataLoadHandler); + super._destroyDetailTable(table); + } + _onTableRowsDeleted(event) { if (this.leaf) { // when page is a leaf we do nothing at all return; @@ -135,6 +155,7 @@ export default class PageWithTable extends Page { * @returns {$.Deferred} */ loadTableData() { + this.ensureDetailTable(); this.detailTable.deleteAllRows(); this.detailTable.setLoading(true); return this._loadTableData(this._createSearchFilter()) diff --git a/eclipse-scout-core/src/widget/Widget.js b/eclipse-scout-core/src/widget/Widget.js index bf012174c7..63096d040b 100644 --- a/eclipse-scout-core/src/widget/Widget.js +++ b/eclipse-scout-core/src/widget/Widget.js @@ -1388,8 +1388,7 @@ export default class Widget { } /** - * Triggers a property change for a single property. The event is only triggered when - * old and new value are the same. + * Triggers a property change for a single property. */ triggerPropertyChange(propertyName, oldValue, newValue) { scout.assertParameter('propertyName', propertyName); diff --git a/eclipse-scout-core/test/desktop/outline/pages/PageSpec.js b/eclipse-scout-core/test/desktop/outline/pages/PageSpec.js new file mode 100644 index 0000000000..da835d99c6 --- /dev/null +++ b/eclipse-scout-core/test/desktop/outline/pages/PageSpec.js @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2010-2021 BSI Business Systems Integration AG. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * BSI Business Systems Integration AG - initial API and implementation + */ +import {Form, Page, PageWithTable, scout, Table, Widget} from '../../../../src'; +import {OutlineSpecHelper} from '../../../../src/testing'; + +describe('Page', () => { + + let session; + /** @type {Outline} */ + let outline; + + beforeEach(() => { + setFixtures(sandbox()); + session = sandboxSession(); + outline = new OutlineSpecHelper(session).createOutline(); + }); + + it('detailTable and detailForm are created lazily on page activation when created as object', () => { + // test with object + let page = createAndInsertPage({ + objectType: 'Table' + }, { + objectType: 'Form' + }); + expect(page.detailTable).toBeFalsy(); + expect(page.detailForm).toBeFalsy(); + expectListenersToBeExecuted(0, page); // not created yet + outline.selectNode(page); // page is activated + expect(page.detailTable).toBeInstanceOf(Table); + expect(page.detailForm).toBeInstanceOf(Widget); + expectListenersToBeExecuted(2, page); // both listeners executed + }); + + it('detailTable and detailForm are initialized when passed as widget', () => { + // if form or table is directly provided as widget: it is available right from the start + let page = createAndInsertPage( + scout.create('Table', {parent: outline}), + scout.create('Form', {parent: outline})); + expect(page.detailTable).toBeInstanceOf(Table); + expect(page.detailForm).toBeInstanceOf(Widget); + expectListenersToBeExecuted(2, page); // both listeners already executed without selecting the page + }); + + it('detailTable and detailForm are destroyed when overwritten', () => { + let page = createAndInsertPage({ + objectType: 'Table' + }, { + objectType: 'Form' + }); + outline.selectNode(page); + expect(page.detailTable).toBeInstanceOf(Table); + expect(page.detailForm).toBeInstanceOf(Widget); + + let oldForm = page.detailForm; + let newForm = scout.create('Form', {parent: outline}); + page.setDetailForm(newForm); + expect(oldForm.destroyed).toBe(true); + expect(newForm.destroyed).toBe(false); + + let oldTable = page.detailTable; + let newTable = scout.create('Table', {parent: outline}); + page.setDetailTable(newTable); + expect(oldTable.destroyed).toBe(true); + expect(newTable.destroyed).toBe(false); + }); + + function createAndInsertPage(detailTable, detailForm) { + let page = new PageWithLazyCreationCounter(); + page.on('propertyChange:detailForm', e => e.source.numFormCreated++); + page.on('propertyChange:detailTable', e => e.source.numTableCreated++); + page.init({ + parent: outline, + detailTable: detailTable, + detailForm: detailForm + }); + outline.insertNodes([page], null); + return page; + } + + function expectListenersToBeExecuted(expectation, page) { + expect(page.numTableCreated).toBe(expectation); + expect(page.numFormCreated).toBe(expectation); + } + + class PageWithLazyCreationCounter extends PageWithTable { + + constructor() { + super(); + this.numTableCreated = 0; + this.numFormCreated = 0; + } + + _initDetailForm(form) { + super._initDetailForm(form); + expect(form).toBeInstanceOf(Form); + this.numFormCreated++; + } + + _initDetailTable(table) { + super._initDetailTable(table); + expect(table).toBeInstanceOf(Table); + this.numTableCreated++; + } + } +}); diff --git a/eclipse-scout-core/test/desktop/outline/pages/PageWithTableSpec.js b/eclipse-scout-core/test/desktop/outline/pages/PageWithTableSpec.js index 9cf6991609..6bb553f038 100644 --- a/eclipse-scout-core/test/desktop/outline/pages/PageWithTableSpec.js +++ b/eclipse-scout-core/test/desktop/outline/pages/PageWithTableSpec.js @@ -1,5 +1,5 @@ /* - * Copyright (c) 2010-2020 BSI Business Systems Integration AG. + * Copyright (c) 2010-2021 BSI Business Systems Integration AG. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at @@ -35,6 +35,7 @@ describe('PageWithTable', () => { }); outline.insertNodes([page], null); outline.render(); + outline.selectNode(page); page.detailTable.render(); jasmine.clock().install(); diff --git a/scout-hellojs-app/src/main/resources/archetype-resources/__rootArtifactId__.ui/src/main/js/person/PersonTablePage.js b/scout-hellojs-app/src/main/resources/archetype-resources/__rootArtifactId__.ui/src/main/js/person/PersonTablePage.js index d16b1c16e8..72b7401087 100644 --- a/scout-hellojs-app/src/main/resources/archetype-resources/__rootArtifactId__.ui/src/main/js/person/PersonTablePage.js +++ b/scout-hellojs-app/src/main/resources/archetype-resources/__rootArtifactId__.ui/src/main/js/person/PersonTablePage.js @@ -18,6 +18,10 @@ export default class PersonTablePage extends PageWithTable { _init(model) { let m = ${symbol_dollar}.extend({}, this._jsonModel(), model); super._init(m); + } + + _initDetailTable(table) { + super._initDetailTable(table); this._initListeners(); } @@ -82,10 +86,9 @@ export default class PersonTablePage extends PageWithTable { _createPersonForm() { let outline = this.getOutline(); - let personForm = scout.create('${simpleArtifactName}.PersonForm', { + return scout.create('${simpleArtifactName}.PersonForm', { parent: outline }); - return personForm; } _onEditPersonMenuAction(event) { |