▩Dart-初识‘dart:html’库-实例解析

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 = "&nbsp;";
  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');
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

itzyjr

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值