dart:html
: DOM manipulation for web apps (available only to web apps).
HTML elements and other resources for web-based applications that need to interact with the browser and the DOM (Document Object Model)
即:dart:html库是操纵DOM的,仅用于Web。
Document 对象:
当浏览器载入 HTML 文档, 它就会成为 Document 对象。
Document 对象是 HTML 文档的根节点。
Document 对象使我们可以从脚本中对 HTML 页面中的所有元素进行访问。
提示:Document 对象是 Window 对象的一部分,可通过 window.document 属性对其进行访问。
上完整示例:
main.html
<!DOCTYPE html>
<html>
<head>
<style type="text/css">
.item { cursor:pointer; }<!--所有class="item"的元素,鼠标指向元素时的样式为“一只伸出食指的手”-->
.packed { text-decoration:line-through; }<!--所有class="packed"的元素有删除线效果-->
</style>
<script type="application/dart" src="main.dart"></script>
<script defer src="main.dart.js"></script>
</head>
<body>
</body>
</html>
main.dart
import 'dart:html';
main() {
var title = Element.html("<h2>PackList</h2>");
document.body!.children.add(title);
var itemInput = Element.tag("input") as InputElement;
itemInput.id = "txt-item";
itemInput.placeholder = "Enter an item";
itemInput.onKeyPress.listen((KeyboardEvent event) {
if (event.keyCode == 13) {// Enter key
addItem();
}
});
document.body!.children.add(itemInput);
ButtonElement addButton = Element.tag("button") as ButtonElement;
addButton.id = "btn-add";
addButton.text = "Add";
addButton.onClick.listen((event) => addItem());
document.body!.children.add(addButton);
DivElement itemContainer = Element.tag("div") as DivElement;
itemContainer.id = "items";
itemContainer.style.width = "300px";
itemContainer.style.border = "1px solid black";
itemContainer.innerHtml = " ";
document.body!.children.add(itemContainer);
}
addItem() {
var itemInputList = querySelectorAll("input");
InputElement itemInput = itemInputList[0] as InputElement;
DivElement itemContainer = querySelector("#items") as DivElement;
var packItem = PackItem(itemInput.value);
itemContainer.children.add(packItem.uiElement);
itemInput.value = "";
}
class PackItem {
var itemText;
var _uiElement;
var _isPacked = false;
PackItem(this.itemText) {}
bool get isPacked => _isPacked;
set isPacked(value) {
_isPacked = value;
if (_isPacked == true) {
uiElement.classes.add("packed");
} else {
uiElement.classes.remove("packed");
}
}
DivElement get uiElement {
if (_uiElement == null) {
_uiElement = Element.tag("div");
_uiElement.classes.add("item");
_uiElement.text = itemText;
_uiElement.onClick.listen((event) => isPacked = !isPacked);
}
return _uiElement;
}
}
效果
对于main.html:
有<script type="application/dart" src="main.dart" />
,即引入了main.dart脚本。
<script defer src="main.dart.js"/>
即引入Dart SDK中自带的js文件,毕竟dart旨在转换为js,这个js文件是必须引入的,它与dart脚本有调用关系,这样才能完成dart脚本的功能。其中有defer
关键字:defer[dɪˈfɜːrz]从字面意思上来看就是延迟,推迟的意思。它的目的在于:让浏览器在下载脚本的时候就不必立即对其进行处理,而是继续对页面进行下载和解析,等到全部页面下载完成后执行JS脚本。这样做有时候能够提高下载的性能。
对于main.dart:
Element类
它是一个抽象类,所有HTML元素都实现了这个类。
实现层次:Object > EventTarget > Node > Element
它实现了:ParentNode,ChildNode,NonDocumentTypeChildNode,GlobalEventHandlers
它的子类:HtmlElement,InputElementBase,SvgElement
构造函数:
Element类有很多构造函数,全部列出如下:
Element.a():创建一个<a>元素。
Element.br():创建一个<br>元素。
Element.div():创建一个<div>元素。
Element.html(String? html, {NodeValidator? validator, NodeTreeSanitizer? treeSanitizer}):从一个有效的HTML片段创建HTML元素。
Element.tag(String tag, [String? typeExtension]):从一个HTML tag创建HTML元素。
Element.article()-<article>
Element.aside()-<aside>
Element.audio()-<audio>
Element.canvas()-<canvas>
Element.created()-<created>
Element.footer()-<footer>
Element.header()-<header>
Element.hr()-<hr>
Element.iframe()-<iframe>
Element.img()-<img>
Element.li()-<li>
Element.nav()-<nav>
Element.ol()-<ol>
Element.option()-<option>
Element.p()-<p>
Element.pre()-<pre>
Element.section()-<section>
Element.select()-<select>
Element.span()-<span>
Element.svg()-<svg>
Element.table()-<table>
Element.td()-<td>
Element.textarea()-<textarea>
Element.th()-<th>
Element.tr()-<tr>
Element.ul()-<ul>
Element.video()-<video>
部分属性:
children ↔ List<Element>
List of the direct children of this element.(read / write)
attributes ↔ Map<String, String>
All attributes on this element.(read / write)
classes ↔ CssClassSet
The set of CSS classes applied to this element.(read / write)
className ↔ String(read / write)
clientHeight → int(read-only)
clientLeft → int?(read-only)
firstChild → Node?
The first child of this node.(read-only, inherited)
hidden ↔ bool
Indicates whether the element is not relevant to the page’s current state.(read / write)
onClick → ElementStream<MouseEvent>
Stream of click events handled by this Element.(read-only, override)
onDrag → ElementStream<MouseEvent>
A stream of drag events fired when this element currently being dragged.(read-only, override)
onKeyUp → ElementStream<KeyboardEvent>
Stream of keyup events handled by this Element.(read-only, override)
onMouseMove → ElementStream<MouseEvent>
Stream of mousemove events handled by this Element.(read-only, override)
onTouchLeave → ElementStream<TouchEvent>
Stream of touchleave events handled by this Element.(read-only)
scrollTop ↔ int(read / write)
。。。。。。
部分方法:
addEventListener(String type, EventListener? listener, [bool? useCapture]) → void (inherited)
animate(Iterable<Map<String, dynamic>> frames, [dynamic timing]) → Animation
append(Node node) → Node
Adds a node to the end of the child nodes list of this node.
appendHtml(String text, {NodeValidator? validator, NodeTreeSanitizer? treeSanitizer}) → void
Parses the specified text as HTML and adds the resulting node after the last child of this element.
appendText(String text) → void
Adds the specified text after the last child of this element.
offsetTo(Element parent) → Point
Provides the offset of this element’s borderEdge relative to the specified parent.
scrollBy([dynamic options_OR_x, num? y]) → void
toString() → String
The string representation of this element.(override)
。。。。。。
Element类多个构造函数原理:
Element.section()
:创建一个新的< section>元素。
它等价于调用:new Element.tag('section')
它的实现是:factory Element.section() => new Element.tag('section');
(通过工厂构造函数实现)
工厂构造函数典型例子:
abstract class IGreetable {// 定义接口
String sayHello(String name);// 提供必须实现的方法
factory IGreetable() => Greeter();// 返回实现了接口的实例的工厂构造函数
}
class Greeter implements IGreetable {// 接口实现者
@override
sayHello(name) =>"Hello $name";// 实现接口方法
}
注:工厂构造函数不是说一定要在抽象类中,一般类都可以。
factory 构造函数() => 接口实现者的构造函数()
而上面是factory Element.section() => xxx
这就涉及“命名构造函数”:
class EnterpriseAuthService {
EnterpriseAuthService() {
// default constructor
}
EnterpriseAuthService.withConn(String connection) {
// user supplies a connection string
}
EnterpriseAuthService.usingServer(String server, int port) {
// user supplies a server and port
}
}
main() {
var authSvc1 = new EnterpriseAuthService();// 默认构造函数
var authSvc2 = new EnterpriseAuthService.withConn("connection string");// 命名的withConn构造函数
var authSvc3 = new EnterpriseAuthService.usingServer("localhost", 8080);// 命名的usingServer构造函数
}
所以,Element类有N个命名构造函数,Element.section()就是其中一个。不得不说,源代码类的设计,着实是优!
对Element类的描述有:
它的子类:HtmlElement,InputElementBase,SvgElement
但InputElement、ButtonElement、DivElement呢?
HtmlElement
的所有子类如下:
AnchorElement,AreaElement,BaseElement,BodyElement,BRElement,ButtonElement
,CanvasElement,ContentElement,DataElement,DataListElement,DetailsElement,DialogElement,DivElement
,DListElement,EmbedElement,FieldSetElement,FormElement,HeadElement,HeadingElement,HRElement,HtmlHtmlElement,IFrameElement,ImageElement,InputElement
,LabelElement,LegendElement,LIElement,LinkElement,MapElement,MediaElement,MenuElement,MetaElement,MeterElement,ModElement,ObjectElement,OListElement,OptGroupElement,OptionElement,OutputElement,ParagraphElement,ParamElement,PictureElement,PreElement,ProgressElement,QuoteElement,ScriptElement,SelectElement,ShadowElement,SlotElement,SourceElement,SpanElement,StyleElement,TableCaptionElement,TableCellElement,TableColElement,TableElement,TableRowElement,TableSectionElement,TemplateElement,TextAreaElement,TimeElement,TitleElement,TrackElement,UListElement,UnknownElement
InputElementBase
部分子类:ButtonInputElement、CheckboxInputElement、FileUploadInputElement、HiddenInputElement。。。。。。
SvgElement
部分子类:AnimationElement,DescElement,DiscardElement,FEBlendElement。。。。。。
注:子类还有子类呢。
var itemInput = Element.tag("input") as InputElement;
itemInput.id = "txt-item";
itemInput.placeholder = "Enter an item";
如果去掉第1行代码最后面的“as InputElement”,则:
鼠标放在itemInput上:
加上“as InputElement”后,鼠标放在itemInput上:
var element = new Element.html('<div class="foo">content</div>');
这样就创建了一个HTML元素。
InputElement itemInput = new Element.tag("input") as InputElement;
document.body.children.add(itemInput);
ButtonElement addButton = new Element.tag("button") as ButtonElement;
document.body.children.add(addButton);
DivElement itemContainer = new Element.tag("div") as DivElement;
document.body.children.add(itemContainer);
document.body.children
:文档 → <body> → 子元素列表。不仅body,其他tag也都有children属性。
DOM官方文档:
document.body:返回文档的body元素
document.documentElement:返回文档的根节点
即:document.body属性返回<body>元素,document.documentElement属性返回<html>元素。
itemInput.onKeyPress.listen((KeyboardEvent event) {
if (event.keyCode == 13)// Enter key
addItem();
});
onKeyPress,即键盘按下事件,listen()的参数是一个匿名函数,即没有函数名。
可以用一个变量接收这个匿名函数:var keyListener = (event) => {...};
则itemInput.onKeyPress.listen(keyListener);
对于一个tag,可以有多个class(用空格分隔),只能有一个id,如<div id="abc" class="dv1 dv2 dv3"></div>
对于dart代码中的isPacked有getter和setter方法:
_uiElement.onClick.listen((event) => isPacked = !isPacked);
isPacked = xxx,这个赋值操作就调用了setter方法。而setter方法里面有相应的控制UI的逻辑。
xxx=!isPacked,等式右边访问了isPacked,调用了getter方法。
HTML DOM Document 对象
document.querySelector():返回文档中匹配指定的CSS选择器的第一元素。
document.querySelectorAll():是 HTML5中引入的新方法,返回文档中匹配的CSS选择器的所有元素节点列表。
在导入dart:html库后,对于以上两函数,可以省略掉document前缀。
例子中,用到的是:
querySelectorAll("input");
,其中"input"是一个tag="<input>"、querySelector("#items");
,其中"#items"是一个元素id=“item”。
当然,querySelectorAll与querySelector,参数都可以是这三种:“tagName”、"#id"、".className"。
document.body!.children.add(addButton);
我们看到,body后面有个感叹号!,作用是什么?
如果去掉这个"!",会报错如下图:
报错信息已经很明确了:
属性’children’不能无条件地访问,因为它的接收着(body)可能为null。
‘body’引用了一个属性,因此它不能被提升。
尝试让访问成为有条件的(使用’?’)或对目标添加一个null检测(使用’!’)。
关于?后缀与!后缀:
String? name;
加一个问号可以让静态检查通过,表示可空类型,即String?
是可空类型String。
document.body!.children
确信字段body100%不为null,那么就可以添加!,强调这个字段一定不为空。
class B {
name?: string
}
class A implements B {
name!: string
}
因为隐式接口B里面属性name被定义为可空的值,但是实际情况是不为空的,那么我们就可以通过在属性后加“!”,重新强调了name属性不为空值null。
new Error().stack.split('\n');
// 这里 Error对象定义的stack是可选参数,如果这样写的话编译器会提示出错:
// TS2532: Object is possibly 'undefined'.
// 如果确信这个字段stack 100%不是'undefined',那么就可以添加“!”,强调这个字段一定存在。这样编译器就不会报错。
new Error().stack!.split('\n');