From 218d2e552e019b29b0587442dc8bafa5107b680e Mon Sep 17 00:00:00 2001 From: Matt Goo Date: Fri, 17 May 2019 14:50:05 -0700 Subject: [PATCH] feat(auto-init): initialize components once with multiple mdc.autoInit() calls (#4691) --- packages/mdc-auto-init/README.md | 10 +++++++ packages/mdc-auto-init/constants.ts | 28 +++++++++++++++++++ packages/mdc-auto-init/index.ts | 17 +++++------ test/unit/mdc-auto-init/mdc-auto-init.test.js | 28 +++++++++++-------- 4 files changed, 64 insertions(+), 19 deletions(-) create mode 100644 packages/mdc-auto-init/constants.ts diff --git a/packages/mdc-auto-init/README.md b/packages/mdc-auto-init/README.md index 5283d964c47..ac51e9c4506 100644 --- a/packages/mdc-auto-init/README.md +++ b/packages/mdc-auto-init/README.md @@ -66,6 +66,16 @@ property on that element. document.querySelector('.mdc-text-field').MDCTextField.disabled = true; ``` +#### Calling subsequent `mdc.autoInit()` + +If you decide to add new components into the DOM after the initial `mdc.autoInit()`, you can make subsequent calls to `mdc.autoInit()`. This will not reinitialize existing components. This works since mdc-auto-init will add the `data-mdc-auto-init-state="initialized"` attribute, which tracks if the component has already been initialized. After calling `mdc.autoInit()` your component will then look like: + +```html +
+ ... +
+``` + ### Using as a standalone module #### Registering Components diff --git a/packages/mdc-auto-init/constants.ts b/packages/mdc-auto-init/constants.ts new file mode 100644 index 00000000000..7147addc1a4 --- /dev/null +++ b/packages/mdc-auto-init/constants.ts @@ -0,0 +1,28 @@ +/** + * @license + * Copyright 2019 Google Inc. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +export const strings = { + AUTO_INIT_ATTR: 'data-mdc-auto-init', + AUTO_INIT_STATE_ATTR: 'data-mdc-auto-init-state', + INITIALIZED_STATE: 'initialized', +}; diff --git a/packages/mdc-auto-init/index.ts b/packages/mdc-auto-init/index.ts index 8494af562e8..deb9e866ae1 100644 --- a/packages/mdc-auto-init/index.ts +++ b/packages/mdc-auto-init/index.ts @@ -26,6 +26,10 @@ import {MDCComponent} from '@material/base/component'; import {MDCFoundation} from '@material/base/foundation'; +import {strings} from './constants'; + +const {AUTO_INIT_ATTR, AUTO_INIT_STATE_ATTR, INITIALIZED_STATE} = strings; + export interface MDCAttachable { new(root: Element, foundation?: F, ...args: Array): MDCComponent; @@ -60,12 +64,13 @@ function emit(evtType: string, evtData: T, shouldBubble = fals /** * Auto-initializes all MDC components on a page. */ -export function mdcAutoInit(root = document, warn = CONSOLE_WARN) { +export function mdcAutoInit(root = document) { const components = []; - const nodes: Element[] = [].slice.call(root.querySelectorAll('[data-mdc-auto-init]')); + let nodes: Element[] = [].slice.call(root.querySelectorAll(`[${AUTO_INIT_ATTR}]`)); + nodes = nodes.filter((node) => node.getAttribute(AUTO_INIT_STATE_ATTR) !== INITIALIZED_STATE); for (const node of nodes) { - const ctorName = node.getAttribute('data-mdc-auto-init'); + const ctorName = node.getAttribute(AUTO_INIT_ATTR); if (!ctorName) { throw new Error('(mdc-auto-init) Constructor name must be given.'); } @@ -76,11 +81,6 @@ export function mdcAutoInit(root = document, warn = CONSOLE_WARN) { `(mdc-auto-init) Could not find constructor in registry for ${ctorName}`); } - if (Object.getOwnPropertyDescriptor(node, ctorName)) { - warn(`(mdc-auto-init) Component already initialized for ${node}. Skipping...`); - continue; - } - // TODO: Should we make an eslint rule for an attachTo() static method? // See https://github.com/Microsoft/TypeScript/issues/14600 for discussion of static interface support in TS const component = Constructor.attachTo(node); @@ -91,6 +91,7 @@ export function mdcAutoInit(root = document, warn = CONSOLE_WARN) { writable: false, }); components.push(component); + node.setAttribute(AUTO_INIT_STATE_ATTR, INITIALIZED_STATE); } emit('MDCAutoInit:End', {}); diff --git a/test/unit/mdc-auto-init/mdc-auto-init.test.js b/test/unit/mdc-auto-init/mdc-auto-init.test.js index 9347f078a4b..e8b45ccbb3d 100644 --- a/test/unit/mdc-auto-init/mdc-auto-init.test.js +++ b/test/unit/mdc-auto-init/mdc-auto-init.test.js @@ -96,17 +96,6 @@ test('throws when constructor is not registered', () => { assert.throws(() => mdcAutoInit(root)); }); -test('warns when autoInit called multiple times on a node', () => { - const root = setupTest(); - const warn = td.func('warn'); - const {contains} = td.matchers; - - mdcAutoInit(root, warn); - mdcAutoInit(root, warn); - - td.verify(warn(contains('(mdc-auto-init) Component already initialized'))); -}); - test('#register throws when Ctor is not a function', () => { assert.throws(() => mdcAutoInit.register('not-a-function', 'Not a function')); }); @@ -170,3 +159,20 @@ test('#returns the initialized components', () => { assert.equal(components.length, 1); assert.isOk(components[0] instanceof FakeComponent); }); + +test('does not register any components if element has data-mdc-auto-init-state="initialized"', () => { + const root = setupTest(); + root.querySelector('[data-mdc-auto-init]').setAttribute('data-mdc-auto-init-state', 'initialized'); + mdcAutoInit(root); + + assert.isFalse(root.querySelector('.mdc-fake').FakeComponent instanceof FakeComponent); +}); + +test('does not return any new components after calling autoInit a second time', () => { + const root = setupTest(); + + let components = mdcAutoInit(root); + assert.equal(components.length, 1); + components = mdcAutoInit(root); + assert.equal(components.length, 0); +});