UNPKG

10.6 kBMarkdownView Raw
1<!--docs:
2title: "Base"
3layout: detail
4section: components
5excerpt: "Base foundation and component classes."
6path: /catalog/base/
7-->
8
9# Base
10
11MDC Base contains core foundation and component classes that serve as the base classes for all of MDC Web's foundation classes and components (respectively).
12
13Most of the time, you shouldn't need to depend on `mdc-base` directly. It is useful however if you'd like to write custom components that follow MDC Web's pattern and elegantly integrate with the MDC Web ecosystem.
14
15## Installation
16
17First install the module:
18
19```
20npm install @material/base
21```
22
23Then include it in your code in one of the following ways:
24
25#### ES Module syntax
26
27```javascript
28import {MDCComponent, MDCFoundation} from '@material/base';
29```
30#### CommonJS
31
32```javascript
33const MDCComponent = require('mdc-base').MDCComponent;
34const MDCFoundation = require('mdc-base').MDCFoundation;
35```
36
37#### AMD
38
39```javascript
40require(['path/to/mdc-base'], function(mdcBase) {
41 const MDCComponent = mdcBase.MDCComponent;
42 const MDCFoundation = mdcBase.MDCFoundation;
43});
44```
45
46#### Vanilla
47
48```javascript
49const MDCComponent = mdc.base.MDCComponent;
50const MDCFoundation = mdc.base.MDCFoundation;
51```
52
53## Usage
54
55mdc-base exposes two classes: `MDCComponent` (the default export) which all components extend from, and `MDCFoundation`, which all foundation classes extend from. To learn more about foundation classes vs. components, check out our overview on [architecture and best practices](../../docs/code).
56
57### MDCFoundation
58
59MDCFoundation provides the basic mechanisms for implementing foundation classes. Subclasses are expected to:
60
61- Provide implementations of the proper static getters where necessary.
62- Provide `init()` and `destroy()` lifecycle methods
63
64```javascript
65import {MDCFoundation} from '@material/base/foundation';
66
67export default class MyFoundation extends MDCFoundation {
68 static get cssClasses() {
69 return {
70 ROOT: 'my-component',
71 MESSAGE: 'my-component__message',
72 BUTTON: 'my-component__button',
73 TOGGLED: 'my-component--toggled'
74 };
75 }
76
77 static get defaultAdapter() {
78 return {
79 toggleClass: (/* className: string */) => {},
80 registerBtnClickHandler: (/* handler: Function */) => {},
81 deregisterBtnClickHandler: (/* handler: Function */) => {}
82 };
83 }
84
85 constructor(adapter) {
86 super({...MyFoundation.defaultAdapter, ...adapter});
87 const {TOGGLED} = MyFoundation.cssClasses;
88 this.clickHandler = () => this.adapter.toggleClass(TOGGLED);
89 }
90
91 init() {
92 this.adapter.registerBtnClickHandler(this.clickHandler);
93 }
94
95 destroy() {
96 this.adapter.deregisterBtnClickHandler(this.clickHandler);
97 }
98}
99```
100
101#### Static Getters
102
103The static getters specify constants that can be used within the foundation class, its component, and by 3rd-party code. _It's important to remember to always put constants into these getters_. This will ensure your component can interop in as many environments as possible, including those where CSS classes need to be overwritten by the host library (e.g., Closure Stylesheets), or strings need to be modified (for i18n, for example).
104
105Note that you do not have to explicitly provide getters for constants if your component has none.
106
107The getters which should be provided are specified below:
108
109| getter | description |
110| --- | --- |
111| cssClasses | returns an object where each key identifies a css class that some code will rely on. |
112| strings | returns an object where each key identifies a string constant, e.g. `ARIA_ROLE` |
113| numbers | returns an object where each key identifies a numeric constant, e.g. `TRANSITION_DELAY_MS` |
114| defaultAdapter | returns an object specifying the shape of the adapter. Can be used as sensible defaults for an adapter as well as a way to specify your adapter's "schema" |
115
116#### Lifecycle Methods
117
118Each foundation class has two lifecycle methods: `init()` and `destroy()`, which are described below:
119
120| method | time of invocation | use case |
121| --- | --- | --- |
122| init() | called by a host class when a component is ready to be initialized | add event listeners, query for info via adapters, etc. |
123| destroy() | called by a host class when a component is no longer in use | remove event listeners, reset any transient state, etc. |
124
125### MDCComponent
126
127MDCComponent provides the basic mechanisms for implementing component classes.
128
129```javascript
130import MyComponentFoundation from './foundation';
131
132export class MyComponent extends MDCComponent {
133 static attachTo(root) {
134 return new MyComponent(root);
135 }
136
137 getDefaultFoundation() {
138 const btn = this.root.querySelector(`.${MyComponentFoundation.cssClasses.BUTTON}`);
139 return new MyComponentFoundation({
140 toggleClass: className => {
141 if (this.root.classList.contains(className)) {
142 this.root.classList.remove(className);
143 return;
144 }
145 this.root.classList.add(className);
146 },
147 registerBtnClickHandler: handler => btn.addEventListener('click', handler),
148 deregisterBtnClickHandler: handler => btn.removeEventListener('click', handler)
149 });
150 }
151}
152```
153
154#### Properties
155
156`MDCComponent` provides the following "private" properties to subclasses:
157
158| property | description |
159| --- | --- |
160| `root` | The root element passed into the constructor as the first argument. |
161| `foundation` | The foundation class for this component. This is either passed in as an optional second argument to the constructor, or assigned the result of calling `getDefaultFoundation()` |
162
163#### Methods
164
165`MDCComponent` provides the following methods to subclasses:
166
167| method | description |
168| --- | --- |
169| `initialize(...args)` | Called after the root element is attached to the component, but _before_ the foundation is instantiated. Any positional arguments passed to the component constructor after the root element, along with the optional foundation 2nd argument, will be provided to this method. This is a good place to do any setup work normally done within a constructor function. |
170| `getDefaultFoundation()` | Returns an instance of a foundation class properly configured for the component. Called when no foundation instance is given within the constructor. Subclasses **must** implement this method. |
171| `initialSyncWithDOM()` | Called within the constructor. Subclasses may override this method if they wish to perform initial synchronization of state with the host DOM element. For example, a slider may want to check if its host element contains a pre-set value, and adjust its internal state accordingly. Note that the same caveats apply to this method as to foundation class lifecycle methods. Defaults to a no-op. |
172| `destroy()` | Subclasses may override this method if they wish to perform any additional cleanup work when a component is destroyed. For example, a component may want to deregister a window resize listener. |
173| `listen(type: string, handler: EventListener)` | Adds an event listener to the component's root node for the given `type`. Note that this is simply a proxy to `this.root.addEventListener`. |
174| `unlisten(type: string, handler: EventListener)` | Removes an event listener from the component's root node. Note that this is simply a proxy to `this.root.removeEventListener`. |
175| `emit(type: string, data: Object, shouldBubble: boolean = false)` | Dispatches a custom event of type `type` with detail `data` from the component's root node. It also takes an optional shouldBubble argument to specify if the event should bubble. This is the preferred way of dispatching events within our vanilla components. |
176
177#### Static Methods
178
179In addition to methods inherited, subclasses should implement the following two static methods within their code:
180
181| method | description |
182| --- | --- |
183| `attachTo(root) => <ComponentClass>` | Subclasses must implement this as a convenience method to instantiate and return an instance of the class using the root element provided. This will be used within `mdc-auto-init`, and in the future its presence may be enforced via a custom lint rule.|
184
185#### Foundation Lifecycle handling
186
187`MDCComponent` calls its foundation's `init()` function within its _constructor_, and its foundation's `destroy()` function within its own _destroy()_ function. Therefore it's important to remember to _always call super() when overriding destroy()_. Not doing so can lead to leaked resources.
188
189#### Initialization and constructor parameters
190
191If you need to pass in additional parameters into a component's constructor, you can make use of the
192`initialize` method, as shown above. An example of this is passing in a child component as a
193dependency.
194
195```js
196class MyComponent extends MDCComponent {
197 initialize(childComponent = null) {
198 this.child = childComponent ?
199 childComponent : new ChildComponent(this.root.querySelector('.child'));
200 }
201
202 getDefaultFoundation() {
203 return new MyComponentFoundation({
204 doSomethingWithChildComponent: () => this.child.doSomething(),
205 // ...
206 });
207 }
208}
209```
210
211You could call this code like so:
212
213```js
214const childComponent = new ChildComponent(document.querySelector('.some-child'));
215const myComponent = new MyComponent(
216 document.querySelector('.my-component'), /* foundation */ undefined, childComponent
217);
218// use myComponent
219```
220
221> NOTE: You could also pass in an initialized foundation if you wish. The example above simply
222> showcases how you could pass in initialization arguments without instantiating a foundation.
223
224#### Best Practice: Keep your adapters simple
225
226If you find your adapters getting too complex, you should consider refactoring the complex parts out into their own implementations.
227
228```javascript
229import MyComponentFoundation from './foundation';
230import {toggleClass} from './util';
231
232class MyComponent {
233 // ...
234 getDefaultFoundation() {
235 return new MyComponentFoundation({
236 toggleClass: className => util.toggleClass(this.root, className),
237 // ...
238 });
239 }
240}
241```
242
243Where `./util` could look like:
244
245```javascript
246export function toggleClass(element, className) {
247 if (root.classList.contains(className)) {
248 root.classList.remove(className);
249 return;
250 }
251 root.classList.add(className);
252}
253```
254
255This not only reduces the complexity of your component class, but allows for the functionality of complex adapters to be adequately tested:
256
257```javascript
258test('toggleClass() removes a class when present on an element', t => {
259 const root = document.createElement('div');
260 root.classList.add('foo');
261
262 util.toggleClass(root, 'foo');
263
264 t.false(root.classList.contains('foo'));
265 t.end();
266});
267```