Skip to content
This repository has been archived by the owner on Jan 13, 2025. It is now read-only.

Commit

Permalink
feat(auto-init): initialize components once with multiple mdc.autoIni…
Browse files Browse the repository at this point in the history
…t() calls (#4691)
  • Loading branch information
Matt Goo authored May 17, 2019
1 parent 63ef3e2 commit 218d2e5
Show file tree
Hide file tree
Showing 4 changed files with 64 additions and 19 deletions.
10 changes: 10 additions & 0 deletions packages/mdc-auto-init/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
<div class="mdc-text-field" data-mdc-auto-init="MDCTextField" data-mdc-auto-init-state="initialized">
...
</div>
```

### Using as a standalone module

#### Registering Components
Expand Down
28 changes: 28 additions & 0 deletions packages/mdc-auto-init/constants.ts
Original file line number Diff line number Diff line change
@@ -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',
};
17 changes: 9 additions & 8 deletions packages/mdc-auto-init/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<F extends MDCFoundation>(root: Element, foundation?: F, ...args: Array<unknown>): MDCComponent<F>;

Expand Down Expand Up @@ -60,12 +64,13 @@ function emit<T extends object>(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.');
}
Expand All @@ -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);
Expand All @@ -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', {});
Expand Down
28 changes: 17 additions & 11 deletions test/unit/mdc-auto-init/mdc-auto-init.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'));
});
Expand Down Expand Up @@ -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);
});

0 comments on commit 218d2e5

Please sign in to comment.