From d1c436c4d533ebbd2a1cf03e82a6470d5292ca63 Mon Sep 17 00:00:00 2001 From: Toby Zerner Date: Fri, 11 Mar 2016 13:18:16 +1030 Subject: [PATCH] Dramatically improve performance when typing in a modal Since Mithril doesn't really offer granular redraw control, typing in a text input on a modal would trigger a redraw for the whole page (including the page content behind the modal) on every keystroke. This commit allows components to be "paused" so that their vdom subtree will be retained instead of reconstructed on subsequent redraws. When a modal is opened, we pause the main page component, and when it's closed, we unpause it. This means that while a modal is visible, only the content inside of the modal will be redrawn, dramatically improving performance. --- js/admin/dist/app.js | 145 ++++++++++++++++----- js/admin/src/components/AppearancePage.js | 4 +- js/admin/src/components/BasicsPage.js | 4 +- js/admin/src/components/DashboardPage.js | 4 +- js/admin/src/components/ExtensionsPage.js | 4 +- js/admin/src/components/Page.js | 32 +++++ js/admin/src/components/PermissionsPage.js | 4 +- js/forum/dist/app.js | 14 +- js/lib/Component.js | 10 +- js/lib/components/ModalManager.js | 4 + 10 files changed, 177 insertions(+), 48 deletions(-) create mode 100644 js/admin/src/components/Page.js diff --git a/js/admin/dist/app.js b/js/admin/dist/app.js index 25aeed458..7faf8900c 100644 --- a/js/admin/dist/app.js +++ b/js/admin/dist/app.js @@ -17111,6 +17111,14 @@ System.register('flarum/Component', [], function (_export) { */ this.element = null; + /** + * Whether or not to retain the component's subtree on redraw. + * + * @type {boolean} + * @public + */ + this.retain = false; + this.init(); } @@ -17154,7 +17162,7 @@ System.register('flarum/Component', [], function (_export) { value: function render() { var _this = this; - var vdom = this.view(); + var vdom = this.retain ? { subtree: 'retain' } : this.view(); // Override the root element's config attribute with our own function, which // will set the component instance's element property to the root DOM @@ -17713,13 +17721,13 @@ System.register('flarum/components/AlertManager', ['flarum/Component', 'flarum/c } }; });; -System.register('flarum/components/AppearancePage', ['flarum/Component', 'flarum/components/Button', 'flarum/components/Switch', 'flarum/components/EditCustomCssModal', 'flarum/utils/saveSettings'], function (_export) { +System.register('flarum/components/AppearancePage', ['flarum/components/Page', 'flarum/components/Button', 'flarum/components/Switch', 'flarum/components/EditCustomCssModal', 'flarum/utils/saveSettings'], function (_export) { 'use strict'; - var Component, Button, Switch, EditCustomCssModal, saveSettings, AppearancePage; + var Page, Button, Switch, EditCustomCssModal, saveSettings, AppearancePage; return { - setters: [function (_flarumComponent) { - Component = _flarumComponent['default']; + setters: [function (_flarumComponentsPage) { + Page = _flarumComponentsPage['default']; }, function (_flarumComponentsButton) { Button = _flarumComponentsButton['default']; }, function (_flarumComponentsSwitch) { @@ -17730,8 +17738,8 @@ System.register('flarum/components/AppearancePage', ['flarum/Component', 'flarum saveSettings = _flarumUtilsSaveSettings['default']; }], execute: function () { - AppearancePage = (function (_Component) { - babelHelpers.inherits(AppearancePage, _Component); + AppearancePage = (function (_Page) { + babelHelpers.inherits(AppearancePage, _Page); function AppearancePage() { babelHelpers.classCallCheck(this, AppearancePage); @@ -17844,7 +17852,7 @@ System.register('flarum/components/AppearancePage', ['flarum/Component', 'flarum } }]); return AppearancePage; - })(Component); + })(Page); _export('default', AppearancePage); } @@ -17916,13 +17924,13 @@ System.register('flarum/components/Badge', ['flarum/Component', 'flarum/helpers/ } }; });; -System.register('flarum/components/BasicsPage', ['flarum/Component', 'flarum/components/FieldSet', 'flarum/components/Select', 'flarum/components/Button', 'flarum/components/Alert', 'flarum/utils/saveSettings', 'flarum/utils/ItemList'], function (_export) { +System.register('flarum/components/BasicsPage', ['flarum/components/Page', 'flarum/components/FieldSet', 'flarum/components/Select', 'flarum/components/Button', 'flarum/components/Alert', 'flarum/utils/saveSettings', 'flarum/utils/ItemList'], function (_export) { 'use strict'; - var Component, FieldSet, Select, Button, Alert, saveSettings, ItemList, BasicsPage; + var Page, FieldSet, Select, Button, Alert, saveSettings, ItemList, BasicsPage; return { - setters: [function (_flarumComponent) { - Component = _flarumComponent['default']; + setters: [function (_flarumComponentsPage) { + Page = _flarumComponentsPage['default']; }, function (_flarumComponentsFieldSet) { FieldSet = _flarumComponentsFieldSet['default']; }, function (_flarumComponentsSelect) { @@ -17937,8 +17945,8 @@ System.register('flarum/components/BasicsPage', ['flarum/Component', 'flarum/com ItemList = _flarumUtilsItemList['default']; }], execute: function () { - BasicsPage = (function (_Component) { - babelHelpers.inherits(BasicsPage, _Component); + BasicsPage = (function (_Page) { + babelHelpers.inherits(BasicsPage, _Page); function BasicsPage() { babelHelpers.classCallCheck(this, BasicsPage); @@ -18098,7 +18106,7 @@ System.register('flarum/components/BasicsPage', ['flarum/Component', 'flarum/com } }]); return BasicsPage; - })(Component); + })(Page); _export('default', BasicsPage); } @@ -18293,17 +18301,17 @@ System.register('flarum/components/Checkbox', ['flarum/Component', 'flarum/compo } }; });; -System.register("flarum/components/DashboardPage", ["flarum/Component"], function (_export) { +System.register("flarum/components/DashboardPage", ["flarum/components/Page"], function (_export) { "use strict"; - var Component, DashboardPage; + var Page, DashboardPage; return { - setters: [function (_flarumComponent) { - Component = _flarumComponent["default"]; + setters: [function (_flarumComponentsPage) { + Page = _flarumComponentsPage["default"]; }], execute: function () { - DashboardPage = (function (_Component) { - babelHelpers.inherits(DashboardPage, _Component); + DashboardPage = (function (_Page) { + babelHelpers.inherits(DashboardPage, _Page); function DashboardPage() { babelHelpers.classCallCheck(this, DashboardPage); @@ -18372,7 +18380,7 @@ System.register("flarum/components/DashboardPage", ["flarum/Component"], functio } }]); return DashboardPage; - })(Component); + })(Page); _export("default", DashboardPage); } @@ -18767,13 +18775,13 @@ System.register('flarum/components/EditGroupModal', ['flarum/components/Modal', } }; });; -System.register('flarum/components/ExtensionsPage', ['flarum/Component', 'flarum/components/LinkButton', 'flarum/components/Button', 'flarum/components/Dropdown', 'flarum/components/Separator', 'flarum/components/AddExtensionModal', 'flarum/components/LoadingModal', 'flarum/utils/ItemList', 'flarum/helpers/icon', 'flarum/helpers/listItems'], function (_export) { +System.register('flarum/components/ExtensionsPage', ['flarum/components/Page', 'flarum/components/LinkButton', 'flarum/components/Button', 'flarum/components/Dropdown', 'flarum/components/Separator', 'flarum/components/AddExtensionModal', 'flarum/components/LoadingModal', 'flarum/utils/ItemList', 'flarum/helpers/icon', 'flarum/helpers/listItems'], function (_export) { 'use strict'; - var Component, LinkButton, Button, Dropdown, Separator, AddExtensionModal, LoadingModal, ItemList, icon, listItems, ExtensionsPage; + var Page, LinkButton, Button, Dropdown, Separator, AddExtensionModal, LoadingModal, ItemList, icon, listItems, ExtensionsPage; return { - setters: [function (_flarumComponent) { - Component = _flarumComponent['default']; + setters: [function (_flarumComponentsPage) { + Page = _flarumComponentsPage['default']; }, function (_flarumComponentsLinkButton) { LinkButton = _flarumComponentsLinkButton['default']; }, function (_flarumComponentsButton) { @@ -18794,8 +18802,8 @@ System.register('flarum/components/ExtensionsPage', ['flarum/Component', 'flarum listItems = _flarumHelpersListItems['default']; }], execute: function () { - ExtensionsPage = (function (_Component) { - babelHelpers.inherits(ExtensionsPage, _Component); + ExtensionsPage = (function (_Page) { + babelHelpers.inherits(ExtensionsPage, _Page); function ExtensionsPage() { babelHelpers.classCallCheck(this, ExtensionsPage); @@ -18938,7 +18946,7 @@ System.register('flarum/components/ExtensionsPage', ['flarum/Component', 'flarum } }]); return ExtensionsPage; - })(Component); + })(Page); _export('default', ExtensionsPage); } @@ -19576,6 +19584,8 @@ System.register('flarum/components/ModalManager', ['flarum/Component', 'flarum/c this.showing = true; this.component = component; + app.current.retain = true; + m.redraw(true); this.$().modal({ backdrop: this.component.isDismissible() ? true : 'static' }).modal('show'); @@ -19619,6 +19629,8 @@ System.register('flarum/components/ModalManager', ['flarum/Component', 'flarum/c this.component = null; + app.current.retain = false; + m.lazyRedraw(); } @@ -19785,6 +19797,67 @@ System.register('flarum/components/Navigation', ['flarum/Component', 'flarum/com } }; });; +System.register('flarum/components/Page', ['flarum/Component'], function (_export) { + + /** + * The `Page` component + * + * @abstract + */ + 'use strict'; + + var Component, Page; + return { + setters: [function (_flarumComponent) { + Component = _flarumComponent['default']; + }], + execute: function () { + Page = (function (_Component) { + babelHelpers.inherits(Page, _Component); + + function Page() { + babelHelpers.classCallCheck(this, Page); + babelHelpers.get(Object.getPrototypeOf(Page.prototype), 'constructor', this).apply(this, arguments); + } + + babelHelpers.createClass(Page, [{ + key: 'init', + value: function init() { + app.previous = app.current; + app.current = this; + + app.modal.close(); + + /** + * A class name to apply to the body while the route is active. + * + * @type {String} + */ + this.bodyClass = ''; + } + }, { + key: 'config', + value: function config(isInitialized, context) { + var _this = this; + + if (isInitialized) return; + + if (this.bodyClass) { + $('#app').addClass(this.bodyClass); + + context.onunload = function () { + return $('#app').removeClass(_this.bodyClass); + }; + } + } + }]); + return Page; + })(Component); + + _export('default', Page); + } + }; +});; System.register('flarum/components/PermissionDropdown', ['flarum/components/Dropdown', 'flarum/components/Button', 'flarum/components/Separator', 'flarum/models/Group', 'flarum/components/GroupBadge'], function (_export) { 'use strict'; @@ -20212,13 +20285,13 @@ System.register('flarum/components/PermissionGrid', ['flarum/Component', 'flarum } }; });; -System.register('flarum/components/PermissionsPage', ['flarum/Component', 'flarum/components/GroupBadge', 'flarum/components/EditGroupModal', 'flarum/models/Group', 'flarum/helpers/icon', 'flarum/components/PermissionGrid'], function (_export) { +System.register('flarum/components/PermissionsPage', ['flarum/components/Page', 'flarum/components/GroupBadge', 'flarum/components/EditGroupModal', 'flarum/models/Group', 'flarum/helpers/icon', 'flarum/components/PermissionGrid'], function (_export) { 'use strict'; - var Component, GroupBadge, EditGroupModal, Group, icon, PermissionGrid, PermissionsPage; + var Page, GroupBadge, EditGroupModal, Group, icon, PermissionGrid, PermissionsPage; return { - setters: [function (_flarumComponent) { - Component = _flarumComponent['default']; + setters: [function (_flarumComponentsPage) { + Page = _flarumComponentsPage['default']; }, function (_flarumComponentsGroupBadge) { GroupBadge = _flarumComponentsGroupBadge['default']; }, function (_flarumComponentsEditGroupModal) { @@ -20231,8 +20304,8 @@ System.register('flarum/components/PermissionsPage', ['flarum/Component', 'flaru PermissionGrid = _flarumComponentsPermissionGrid['default']; }], execute: function () { - PermissionsPage = (function (_Component) { - babelHelpers.inherits(PermissionsPage, _Component); + PermissionsPage = (function (_Page) { + babelHelpers.inherits(PermissionsPage, _Page); function PermissionsPage() { babelHelpers.classCallCheck(this, PermissionsPage); @@ -20298,7 +20371,7 @@ System.register('flarum/components/PermissionsPage', ['flarum/Component', 'flaru } }]); return PermissionsPage; - })(Component); + })(Page); _export('default', PermissionsPage); } diff --git a/js/admin/src/components/AppearancePage.js b/js/admin/src/components/AppearancePage.js index 8485ee44d..7e2893db4 100644 --- a/js/admin/src/components/AppearancePage.js +++ b/js/admin/src/components/AppearancePage.js @@ -1,10 +1,10 @@ -import Component from 'flarum/Component'; +import Page from 'flarum/components/Page'; import Button from 'flarum/components/Button'; import Switch from 'flarum/components/Switch'; import EditCustomCssModal from 'flarum/components/EditCustomCssModal'; import saveSettings from 'flarum/utils/saveSettings'; -export default class AppearancePage extends Component { +export default class AppearancePage extends Page { init() { this.primaryColor = m.prop(app.settings.theme_primary_color); this.secondaryColor = m.prop(app.settings.theme_secondary_color); diff --git a/js/admin/src/components/BasicsPage.js b/js/admin/src/components/BasicsPage.js index 31e064762..a03f5ddbc 100644 --- a/js/admin/src/components/BasicsPage.js +++ b/js/admin/src/components/BasicsPage.js @@ -1,4 +1,4 @@ -import Component from 'flarum/Component'; +import Page from 'flarum/components/Page'; import FieldSet from 'flarum/components/FieldSet'; import Select from 'flarum/components/Select'; import Button from 'flarum/components/Button'; @@ -6,7 +6,7 @@ import Alert from 'flarum/components/Alert'; import saveSettings from 'flarum/utils/saveSettings'; import ItemList from 'flarum/utils/ItemList'; -export default class BasicsPage extends Component { +export default class BasicsPage extends Page { init() { this.loading = false; diff --git a/js/admin/src/components/DashboardPage.js b/js/admin/src/components/DashboardPage.js index 29b6ac541..0c7207d26 100644 --- a/js/admin/src/components/DashboardPage.js +++ b/js/admin/src/components/DashboardPage.js @@ -1,6 +1,6 @@ -import Component from 'flarum/Component'; +import Page from 'flarum/components/Page'; -export default class DashboardPage extends Component { +export default class DashboardPage extends Page { view() { return (
diff --git a/js/admin/src/components/ExtensionsPage.js b/js/admin/src/components/ExtensionsPage.js index 22ebe0850..5b31d3f36 100644 --- a/js/admin/src/components/ExtensionsPage.js +++ b/js/admin/src/components/ExtensionsPage.js @@ -1,4 +1,4 @@ -import Component from 'flarum/Component'; +import Page from 'flarum/components/Page'; import LinkButton from 'flarum/components/LinkButton'; import Button from 'flarum/components/Button'; import Dropdown from 'flarum/components/Dropdown'; @@ -9,7 +9,7 @@ import ItemList from 'flarum/utils/ItemList'; import icon from 'flarum/helpers/icon'; import listItems from 'flarum/helpers/listItems'; -export default class ExtensionsPage extends Component { +export default class ExtensionsPage extends Page { view() { return ( diff --git a/js/admin/src/components/Page.js b/js/admin/src/components/Page.js new file mode 100644 index 000000000..1cc141895 --- /dev/null +++ b/js/admin/src/components/Page.js @@ -0,0 +1,32 @@ +import Component from 'flarum/Component'; + +/** + * The `Page` component + * + * @abstract + */ +export default class Page extends Component { + init() { + app.previous = app.current; + app.current = this; + + app.modal.close(); + + /** + * A class name to apply to the body while the route is active. + * + * @type {String} + */ + this.bodyClass = ''; + } + + config(isInitialized, context) { + if (isInitialized) return; + + if (this.bodyClass) { + $('#app').addClass(this.bodyClass); + + context.onunload = () => $('#app').removeClass(this.bodyClass); + } + } +} diff --git a/js/admin/src/components/PermissionsPage.js b/js/admin/src/components/PermissionsPage.js index dc228d619..6855558c3 100644 --- a/js/admin/src/components/PermissionsPage.js +++ b/js/admin/src/components/PermissionsPage.js @@ -1,11 +1,11 @@ -import Component from 'flarum/Component'; +import Page from 'flarum/components/Page'; import GroupBadge from 'flarum/components/GroupBadge'; import EditGroupModal from 'flarum/components/EditGroupModal'; import Group from 'flarum/models/Group'; import icon from 'flarum/helpers/icon'; import PermissionGrid from 'flarum/components/PermissionGrid'; -export default class PermissionsPage extends Component { +export default class PermissionsPage extends Page { view() { return (
diff --git a/js/forum/dist/app.js b/js/forum/dist/app.js index 2ce4a1acc..069ae66e5 100644 --- a/js/forum/dist/app.js +++ b/js/forum/dist/app.js @@ -18768,6 +18768,14 @@ System.register('flarum/Component', [], function (_export) { */ this.element = null; + /** + * Whether or not to retain the component's subtree on redraw. + * + * @type {boolean} + * @public + */ + this.retain = false; + this.init(); } @@ -18811,7 +18819,7 @@ System.register('flarum/Component', [], function (_export) { value: function render() { var _this = this; - var vdom = this.view(); + var vdom = this.retain ? { subtree: 'retain' } : this.view(); // Override the root element's config attribute with our own function, which // will set the component instance's element property to the root DOM @@ -24367,6 +24375,8 @@ System.register('flarum/components/ModalManager', ['flarum/Component', 'flarum/c this.showing = true; this.component = component; + app.current.retain = true; + m.redraw(true); this.$().modal({ backdrop: this.component.isDismissible() ? true : 'static' }).modal('show'); @@ -24410,6 +24420,8 @@ System.register('flarum/components/ModalManager', ['flarum/Component', 'flarum/c this.component = null; + app.current.retain = false; + m.lazyRedraw(); } diff --git a/js/lib/Component.js b/js/lib/Component.js index 1de36640f..185b47184 100644 --- a/js/lib/Component.js +++ b/js/lib/Component.js @@ -54,6 +54,14 @@ export default class Component { */ this.element = null; + /** + * Whether or not to retain the component's subtree on redraw. + * + * @type {boolean} + * @public + */ + this.retain = false; + this.init(); } @@ -91,7 +99,7 @@ export default class Component { * @public */ render() { - const vdom = this.view(); + const vdom = this.retain ? {subtree: 'retain'} : this.view(); // Override the root element's config attribute with our own function, which // will set the component instance's element property to the root DOM diff --git a/js/lib/components/ModalManager.js b/js/lib/components/ModalManager.js index 6277b23ba..575fee2e9 100644 --- a/js/lib/components/ModalManager.js +++ b/js/lib/components/ModalManager.js @@ -46,6 +46,8 @@ export default class ModalManager extends Component { this.showing = true; this.component = component; + app.current.retain = true; + m.redraw(true); this.$().modal({backdrop: this.component.isDismissible() ? true : 'static'}).modal('show'); @@ -83,6 +85,8 @@ export default class ModalManager extends Component { this.component = null; + app.current.retain = false; + m.lazyRedraw(); }