Imported from tekartik/mdl_js.dart (
skills/tekartik-mdl-js-setup/SKILL.md). Install upstream withnpx skills add tekartik/mdl_js.dart --skill tekartik-mdl-js-setup. Copyright stays with the author.
Loading MDL and upgrading elements (tekartik_mdl_js)
tekartik_mdl_js packages the Material Design Lite 1.3.0 material.min.js /
material.min.css bundle plus the material icons font, and wraps MDL's
componentHandler so a Dart web app can upgrade elements it creates at
runtime. Material Design Lite is retired upstream and every library here is
annotated @Deprecated('Do no use'): keep it for existing dart:html apps, do
not start a new project on it. The DOM builders (buttons, layout, list, grid,
textfield) are covered by the sibling skill
tekartik-mdl-js-widgets.
Guidelines
- Dependency (git, not published on pub.dev):
It is adependencies: tekartik_mdl_js: git: url: https://github.com/tekartik/mdl_js.dartdart:html+dart:jspackage: JavaScript compilation only (dart2js/ddc), never dart2wasm, and nothing in it runs on the Dart VM. - Expect
deprecated_member_usewarnings on every import; the package's own code silences them with// ignore_for_file: deprecated_member_use, deprecated_member_use_from_same_package. - The assets live in
lib/and are served bybuild_web_compilers/webdevatpackages/tekartik_mdl_js/js/material.min.js,packages/tekartik_mdl_js/css/material.min.cssandpackages/tekartik_mdl_js/font/material-design-icons/material-icons.css. The loaders inject exactly those urls, so no<link>/<script>tag is needed inindex.html. package:tekartik_mdl_js/mdl_js_loader.dart:loadMdl()- css + material icons css + js, in parallel. The normal startup call.loadMdlJs()- js only; returns immediately whencomponentHandleris already onwindow, so it is safe to call repeatedly.loadMdlCss(),loadMaterialIconsCss()- the two stylesheets.loadCdnMdlJs({version}),loadCdnMdlCss({version, theme})andloadCdnMaterialIconsCss()are deprecated shims that now load the packaged files; the code.getmdl.io CDN no longer serves them. Do not use them, and do not passversion/themeexpecting an effect.
- Always
awaita loader before touchingcomponentHandler,jsComponentHandleror any widget helper:ComponentHandlercaptures the jscomponentHandlerobject when first built and the getter caches that instance, so an early call leaves a broken handler behind. package:tekartik_mdl_js/mdl_js.dart(import it for the handler):componentHandler- a nullableComponentHandler?(usecomponentHandler!as the package's own code does).ComponentHandler.upgradeElement(element, {String? jsClass})- upgrades one element; withoutjsClassMDL picks the components from the element'smdl-js-*classes, with it only that one. Upgrading twice is a no-op.ComponentHandler.upgrade(HtmlElement element)- upgrades and throwsStateErrorif the element did not get upgraded; returns a already completedFuture, it does not wait for the event.jsComponentHandler- the raw js object;jsComponentHandler.upgradeDom()upgrades everymdl-js-*element currently in the document (use it after injecting a block of static markup).- js class names to pass as
jsClass:materialButtonType,materialTextfieldType,materialRippleType,materialProgressType,materialSpinnerType,materialSliderType,materialLayoutType.
- An element is only upgraded if it carries the
mdl-js-*class of the component (mdl-js-button,mdl-js-textfield, ...):mdl-buttonalone upgrades to nothing unless you passjsClassexplicitly. Ripples needmdl-js-ripple-effect, which is a second upgrade (MaterialRipple). package:tekartik_mdl_js/mdl_component.darttracks upgrades:isComponentUpgraded(element)(readsdata-upgraded),onComponentUpgraded(element)(themdl-componentupgradedevent stream, one event per js class) andwhenComponentUpgraded(element)(aFuture, already completed when the element is upgraded). Subscribe before callingupgradeElement:data-upgradedis set synchronously but the event fires after.package:tekartik_mdl_js/mdl_version.dartexposesversionandmdlVersionDefault(both1.3.0), the version of the bundledmaterial.min.js; the package test asserts the bundle matches.package:tekartik_mdl_js/mdl_import.dartre-exports every library at once (loader, handler, classes and widget builders) for app code that wants a single import.- Tests are
@TestOn('browser')and need the assets served, so run them through build_runner:dart run build_runner test -- -p chrome(dart test -p chromeworks for tests that do not fetchpackages/tekartik_mdl_js/...).dart_test.yamldeclaresvmandchrome.
Examples
App startup
// ignore_for_file: deprecated_member_use
import 'dart:html';
import 'package:tekartik_mdl_js/mdl_js_loader.dart';
Future<void> main() async {
// css + material icons css + js, in parallel.
await loadMdl();
document.body!.classes.add('app-ready');
}
Upgrade an element created at runtime
// ignore_for_file: deprecated_member_use
import 'dart:html';
import 'package:tekartik_mdl_js/mdl_component.dart';
import 'package:tekartik_mdl_js/mdl_js.dart';
import 'package:tekartik_mdl_js/mdl_js_loader.dart';
Future<void> main() async {
await loadMdl();
var button = document.createElement('button')
..className = 'mdl-button mdl-js-button mdl-js-ripple-effect'
..text = 'Click me';
document.body!.append(button);
// Listen first: the event fires after data-upgraded is set.
var upgraded = whenComponentUpgraded(button);
componentHandler!.upgradeElement(button);
await upgraded;
print(isComponentUpgraded(button)); // true
print(button.attributes['data-upgraded']); // ,MaterialButton,MaterialRipple
}
Upgrade static markup, or one precise component
// ignore_for_file: deprecated_member_use
import 'dart:html';
import 'package:tekartik_mdl_js/mdl_js.dart';
import 'package:tekartik_mdl_js/mdl_js_loader.dart';
Future<void> main() async {
await loadMdlJs();
await loadMdlCss();
// Whole document at once, after injecting a block of mdl markup.
jsComponentHandler.upgradeDom();
// Or one element, one js class.
var slider = document.createElement('input') as InputElement
..className = 'mdl-slider mdl-js-slider'
..type = 'range';
document.body!.append(slider);
componentHandler!.upgradeElement(slider, jsClass: materialSliderType);
}
Count upgrades of one element
// ignore_for_file: deprecated_member_use
import 'dart:html';
import 'package:tekartik_mdl_js/mdl_component.dart';
import 'package:tekartik_mdl_js/mdl_js.dart';
import 'package:tekartik_mdl_js/mdl_js_loader.dart';
Future<void> main() async {
await loadMdlJs();
var button = document.createElement('button')
..className = 'mdl-button mdl-js-button mdl-js-ripple-effect';
onComponentUpgraded(button).listen((_) => print('upgraded'));
// One event per js class, nothing on the second identical call.
componentHandler!.upgradeElement(button, jsClass: materialButtonType);
componentHandler!.upgradeElement(button, jsClass: materialButtonType);
componentHandler!.upgradeElement(button, jsClass: materialRippleType);
}
Browser test
// ignore_for_file: deprecated_member_use
@TestOn('browser')
library;
import 'dart:html';
import 'package:tekartik_mdl_js/mdl_component.dart';
import 'package:tekartik_mdl_js/mdl_js.dart';
import 'package:tekartik_mdl_js/mdl_js_loader.dart';
import 'package:test/test.dart';
void main() {
setUp(() async {
await loadMdlJs();
});
test('upgrade a button', () async {
var button = document.createElement('button')
..className = 'mdl-button mdl-js-button';
var upgraded = whenComponentUpgraded(button);
componentHandler!.upgradeElement(button);
expect(isComponentUpgraded(button), isTrue);
expect(button.attributes['data-upgraded'], contains('MaterialButton'));
await upgraded;
});
}