Skip to content

Commit

Permalink
feat(auto-init): Convert JS to TypeScript (#4395)
Browse files Browse the repository at this point in the history
Refs #4225
  • Loading branch information
acdvorak authored Feb 14, 2019
1 parent b8b1988 commit 4675c95
Show file tree
Hide file tree
Showing 4 changed files with 66 additions and 24 deletions.
68 changes: 45 additions & 23 deletions packages/mdc-auto-init/index.js → packages/mdc-auto-init/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,30 @@
* THE SOFTWARE.
*/

const registry = Object.create(null);
// tslint:disable:only-arrow-functions

const CONSOLE_WARN = console.warn.bind(console);
import {MDCComponent, MDCFoundation} from '@material/base/index';

function _emit(evtType, evtData, shouldBubble = false) {
interface ComponentClass {
// tslint:disable-next-line:no-any a component can pass in anything it needs to the constructor
new<F extends MDCFoundation>(root: Element, foundation?: F, ...args: any[]): MDCComponent<F>;
attachTo<F extends MDCFoundation>(root: Element): MDCComponent<F>;
}

interface Registry {
[key: string]: ComponentClass;
}

const registry: Registry = {};

const CONSOLE_WARN = console.warn.bind(console); // tslint:disable-line:no-console

function _emit<T extends object>(evtType: string, evtData: T, shouldBubble = false) {
let evt;
if (typeof CustomEvent === 'function') {
evt = new CustomEvent(evtType, {
detail: evtData,
evt = new CustomEvent<T>(evtType, {
bubbles: shouldBubble,
detail: evtData,
});
} else {
evt = document.createEvent('CustomEvent');
Expand All @@ -40,36 +54,39 @@ function _emit(evtType, evtData, shouldBubble = false) {
document.dispatchEvent(evt);
}

/* istanbul ignore next: optional argument is not a branch statement */
/**
* Auto-initializes all mdc components on a page.
* Auto-initializes all MDC components on a page.
*/
export default function mdcAutoInit(root = document, warn = CONSOLE_WARN) {
export function mdcAutoInit(root = document, warn = CONSOLE_WARN) {
const components = [];
const nodes = root.querySelectorAll('[data-mdc-auto-init]');
for (let i = 0, node; (node = nodes[i]); i++) {
const ctorName = node.dataset.mdcAutoInit;
const nodes: Element[] = [].slice.call(root.querySelectorAll('[data-mdc-auto-init]'));

for (const node of nodes) {
const ctorName = node.getAttribute('data-mdc-auto-init');
if (!ctorName) {
throw new Error('(mdc-auto-init) Constructor name must be given.');
}

const Ctor = registry[ctorName];
if (typeof Ctor !== 'function') {
const Constructor = registry[ctorName]; // tslint:disable-line:variable-name
if (typeof Constructor !== 'function') {
throw new Error(
`(mdc-auto-init) Could not find constructor in registry for ${ctorName}`);
}

if (node[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?
const component = Ctor.attachTo(node);
// See https://github.com/Microsoft/TypeScript/issues/14600 for discussion of static interface support in TS
const component = Constructor.attachTo(node);
Object.defineProperty(node, ctorName, {
configurable: true,
enumerable: false,
value: component,
writable: false,
enumerable: false,
configurable: true,
});
components.push(component);
}
Expand All @@ -78,22 +95,27 @@ export default function mdcAutoInit(root = document, warn = CONSOLE_WARN) {
return components;
}

mdcAutoInit.register = function(componentName, Ctor, warn = CONSOLE_WARN) {
if (typeof Ctor !== 'function') {
throw new Error(`(mdc-auto-init) Invalid Ctor value ${Ctor}. Expected function`);
// Constructor is PascalCased because it is a direct reference to a class, rather than an instance of a class.
// tslint:disable-next-line:variable-name
mdcAutoInit.register = function(componentName: string, Constructor: ComponentClass, warn = CONSOLE_WARN) {
if (typeof Constructor !== 'function') {
throw new Error(`(mdc-auto-init) Invalid Ctor value ${Constructor}. Expected function`);
}
if (registry[componentName]) {
warn(
`(mdc-auto-init) Overriding registration for ${componentName} with ${Ctor}. ` +
`(mdc-auto-init) Overriding registration for ${componentName} with ${Constructor}. ` +
`Was: ${registry[componentName]}`);
}
registry[componentName] = Ctor;
registry[componentName] = Constructor;
};

mdcAutoInit.deregister = function(componentName) {
mdcAutoInit.deregister = function(componentName: string) {
delete registry[componentName];
};

mdcAutoInit.deregisterAll = function() {
Object.keys(registry).forEach(this.deregister, this);
const keys = Object.keys(registry) as string[];
keys.forEach(this.deregister, this);
};

export default mdcAutoInit;
3 changes: 3 additions & 0 deletions packages/mdc-auto-init/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,8 @@
"repository": {
"type": "git",
"url": "https://github.com/material-components/material-components-web.git"
},
"dependencies": {
"@material/base": "^0.41.0"
}
}
2 changes: 1 addition & 1 deletion scripts/webpack/js-bundle-factory.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,7 +153,7 @@ class JsBundleFactory {
bundleName: 'main-js-a-la-carte',
chunks: {
animation: getAbsolutePath('/packages/mdc-animation/index.ts'),
autoInit: getAbsolutePath('/packages/mdc-auto-init/index.js'),
autoInit: getAbsolutePath('/packages/mdc-auto-init/index.ts'),
base: getAbsolutePath('/packages/mdc-base/index.ts'),
checkbox: getAbsolutePath('/packages/mdc-checkbox/index.ts'),
chips: getAbsolutePath('/packages/mdc-chips/index.ts'),
Expand Down
17 changes: 17 additions & 0 deletions test/unit/mdc-auto-init/mdc-auto-init.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,12 @@ class FakeComponent {
}
}

class InvalidComponent {
constructor(node) {
this.node = node;
}
}

const createFixture = () => bel`
<div id="root">
<p data-mdc-auto-init="FakeComponent" class="mdc-fake">Fake Element</p>
Expand All @@ -48,6 +54,12 @@ const setupTest = () => {
return createFixture();
};

const setupInvalidTest = () => {
mdcAutoInit.deregisterAll();
mdcAutoInit.register('InvalidComponent', InvalidComponent);
return createFixture();
};

suite('MDCAutoInit');

test('calls attachTo() on components registered for identifier on nodes w/ data-mdc-auto-init attr', () => {
Expand All @@ -57,6 +69,11 @@ test('calls attachTo() on components registered for identifier on nodes w/ data-
assert.isOk(root.querySelector('.mdc-fake').FakeComponent instanceof FakeComponent);
});

test('throws when attachTo() is missing', () => {
const root = setupInvalidTest();
assert.throws(() => mdcAutoInit(root));
});

test('passes the node where "data-mdc-auto-init" was found to attachTo()', () => {
const root = setupTest();
mdcAutoInit(root);
Expand Down

0 comments on commit 4675c95

Please sign in to comment.