b904bf81ba
Original CL is the first patch, changes follow. Includes fixes for Chrome test failures and Firefox type check issues. BUG= R=jmesserly@google.com Review URL: https://codereview.chromium.org//23442011 git-svn-id: https://dart.googlecode.com/svn/branches/bleeding_edge/dart@27313 260f80e4-7a28-3924-810f-c04153c831b5
1365 lines
43 KiB
Plaintext
1365 lines
43 KiB
Plaintext
// Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file
|
|
// for details. All rights reserved. Use of this source code is governed by a
|
|
// BSD-style license that can be found in the LICENSE file.
|
|
|
|
part of $LIBRARYNAME;
|
|
|
|
class _ChildrenElementList extends ListBase<Element> {
|
|
// Raw Element.
|
|
final Element _element;
|
|
final HtmlCollection _childElements;
|
|
|
|
_ChildrenElementList._wrap(Element element)
|
|
: _childElements = element._children,
|
|
_element = element;
|
|
|
|
bool contains(Object element) => _childElements.contains(element);
|
|
|
|
|
|
bool get isEmpty {
|
|
return _element._firstElementChild == null;
|
|
}
|
|
|
|
int get length {
|
|
return _childElements.length;
|
|
}
|
|
|
|
Element operator [](int index) {
|
|
return _childElements[index];
|
|
}
|
|
|
|
void operator []=(int index, Element value) {
|
|
_element._replaceChild(value, _childElements[index]);
|
|
}
|
|
|
|
void set length(int newLength) {
|
|
// TODO(jacobr): remove children when length is reduced.
|
|
throw new UnsupportedError('Cannot resize element lists');
|
|
}
|
|
|
|
Element add(Element value) {
|
|
_element.append(value);
|
|
return value;
|
|
}
|
|
|
|
Iterator<Element> get iterator => toList().iterator;
|
|
|
|
void addAll(Iterable<Element> iterable) {
|
|
if (iterable is _ChildNodeListLazy) {
|
|
iterable = new List.from(iterable);
|
|
}
|
|
|
|
for (Element element in iterable) {
|
|
_element.append(element);
|
|
}
|
|
}
|
|
|
|
void sort([int compare(Element a, Element b)]) {
|
|
throw new UnsupportedError('Cannot sort element lists');
|
|
}
|
|
|
|
void removeWhere(bool test(Element element)) {
|
|
_filter(test, false);
|
|
}
|
|
|
|
void retainWhere(bool test(Element element)) {
|
|
_filter(test, true);
|
|
}
|
|
|
|
void _filter(bool test(var element), bool retainMatching) {
|
|
var removed;
|
|
if (retainMatching) {
|
|
removed = _element.children.where((e) => !test(e));
|
|
} else {
|
|
removed = _element.children.where(test);
|
|
}
|
|
for (var e in removed) e.remove();
|
|
}
|
|
|
|
void setRange(int start, int end, Iterable<Element> iterable,
|
|
[int skipCount = 0]) {
|
|
throw new UnimplementedError();
|
|
}
|
|
|
|
void replaceRange(int start, int end, Iterable<Element> iterable) {
|
|
throw new UnimplementedError();
|
|
}
|
|
|
|
void fillRange(int start, int end, [Element fillValue]) {
|
|
throw new UnimplementedError();
|
|
}
|
|
|
|
bool remove(Object object) {
|
|
if (object is Element) {
|
|
Element element = object;
|
|
if (identical(element.parentNode, _element)) {
|
|
_element._removeChild(element);
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void insert(int index, Element element) {
|
|
if (index < 0 || index > length) {
|
|
throw new RangeError.range(index, 0, length);
|
|
}
|
|
if (index == length) {
|
|
_element.append(element);
|
|
} else {
|
|
_element.insertBefore(element, this[index]);
|
|
}
|
|
}
|
|
|
|
void setAll(int index, Iterable<Element> iterable) {
|
|
throw new UnimplementedError();
|
|
}
|
|
|
|
void clear() {
|
|
// It is unclear if we want to keep non element nodes?
|
|
_element.text = '';
|
|
}
|
|
|
|
Element removeAt(int index) {
|
|
final result = this[index];
|
|
if (result != null) {
|
|
_element._removeChild(result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Element removeLast() {
|
|
final result = this.last;
|
|
if (result != null) {
|
|
_element._removeChild(result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Element get first {
|
|
Element result = _element._firstElementChild;
|
|
if (result == null) throw new StateError("No elements");
|
|
return result;
|
|
}
|
|
|
|
|
|
Element get last {
|
|
Element result = _element._lastElementChild;
|
|
if (result == null) throw new StateError("No elements");
|
|
return result;
|
|
}
|
|
|
|
Element get single {
|
|
if (length > 1) throw new StateError("More than one element");
|
|
return first;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* An immutable list containing HTML elements. This list contains some
|
|
* additional methods when compared to regular lists for ease of CSS
|
|
* manipulation on a group of elements.
|
|
*/
|
|
abstract class ElementList<T extends Element> extends ListBase<T> {
|
|
/**
|
|
* The union of all CSS classes applied to the elements in this list.
|
|
*
|
|
* This set makes it easy to add, remove or toggle (add if not present, remove
|
|
* if present) the classes applied to a collection of elements.
|
|
*
|
|
* htmlList.classes.add('selected');
|
|
* htmlList.classes.toggle('isOnline');
|
|
* htmlList.classes.remove('selected');
|
|
*/
|
|
CssClassSet get classes;
|
|
|
|
/** Replace the classes with `value` for every element in this list. */
|
|
set classes(Iterable<String> value);
|
|
|
|
/**
|
|
* Access the union of all [CssStyleDeclaration]s that are associated with an
|
|
* [ElementList].
|
|
*
|
|
* Grouping the style objects all together provides easy editing of specific
|
|
* properties of a collection of elements. Setting a specific property value
|
|
* will set that property in all [Element]s in the [ElementList]. Getting a
|
|
* specific property value will return the value of the property of the first
|
|
* element in the [ElementList].
|
|
*/
|
|
CssStyleDeclarationBase get style;
|
|
|
|
/**
|
|
* Access dimensions and position of the Elements in this list.
|
|
*
|
|
* Setting the height or width properties will set the height or width
|
|
* property for all elements in the list. This returns a rectangle with the
|
|
* dimenions actually available for content
|
|
* in this element, in pixels, regardless of this element's box-sizing
|
|
* property. Getting the height or width returns the height or width of the
|
|
* first Element in this list.
|
|
*
|
|
* Unlike [getBoundingClientRect], the dimensions of this rectangle
|
|
* will return the same numerical height if the element is hidden or not.
|
|
*/
|
|
@Experimental()
|
|
CssRect get contentEdge;
|
|
|
|
/**
|
|
* Access dimensions and position of the first Element's content + padding box
|
|
* in this list.
|
|
*
|
|
* This returns a rectangle with the dimenions actually available for content
|
|
* in this element, in pixels, regardless of this element's box-sizing
|
|
* property. Unlike [getBoundingClientRect], the dimensions of this rectangle
|
|
* will return the same numerical height if the element is hidden or not. This
|
|
* can be used to retrieve jQuery's `innerHeight` value for an element. This
|
|
* is also a rectangle equalling the dimensions of clientHeight and
|
|
* clientWidth.
|
|
*/
|
|
@Experimental()
|
|
CssRect get paddingEdge;
|
|
|
|
/**
|
|
* Access dimensions and position of the first Element's content + padding +
|
|
* border box in this list.
|
|
*
|
|
* This returns a rectangle with the dimenions actually available for content
|
|
* in this element, in pixels, regardless of this element's box-sizing
|
|
* property. Unlike [getBoundingClientRect], the dimensions of this rectangle
|
|
* will return the same numerical height if the element is hidden or not. This
|
|
* can be used to retrieve jQuery's `outerHeight` value for an element.
|
|
*/
|
|
@Experimental()
|
|
CssRect get borderEdge;
|
|
|
|
/**
|
|
* Access dimensions and position of the first Element's content + padding +
|
|
* border + margin box in this list.
|
|
*
|
|
* This returns a rectangle with the dimenions actually available for content
|
|
* in this element, in pixels, regardless of this element's box-sizing
|
|
* property. Unlike [getBoundingClientRect], the dimensions of this rectangle
|
|
* will return the same numerical height if the element is hidden or not. This
|
|
* can be used to retrieve jQuery's `outerHeight` value for an element.
|
|
*/
|
|
@Experimental()
|
|
CssRect get marginEdge;
|
|
$!STREAM_GETTER_SIGNATURES
|
|
}
|
|
|
|
// TODO(jacobr): this is an inefficient implementation but it is hard to see
|
|
// a better option given that we cannot quite force NodeList to be an
|
|
// ElementList as there are valid cases where a NodeList JavaScript object
|
|
// contains Node objects that are not Elements.
|
|
class _FrozenElementList<T extends Element> extends ListBase<T> implements ElementList {
|
|
final List<Node> _nodeList;
|
|
// The subset of _nodeList that are Elements.
|
|
List<Element> _elementList;
|
|
|
|
_FrozenElementList._wrap(this._nodeList) {
|
|
_elementList = _nodeList.where((e) => e is Element).toList();
|
|
}
|
|
|
|
int get length => _nodeList.length;
|
|
|
|
Element operator [](int index) => _nodeList[index];
|
|
|
|
void operator []=(int index, Element value) {
|
|
throw new UnsupportedError('Cannot modify list');
|
|
}
|
|
|
|
void set length(int newLength) {
|
|
throw new UnsupportedError('Cannot modify list');
|
|
}
|
|
|
|
void sort([Comparator<Element> compare]) {
|
|
throw new UnsupportedError('Cannot sort list');
|
|
}
|
|
|
|
Element get first => _nodeList.first;
|
|
|
|
Element get last => _nodeList.last;
|
|
|
|
Element get single => _nodeList.single;
|
|
|
|
CssClassSet get classes => new _MultiElementCssClassSet(_elementList);
|
|
|
|
CssStyleDeclarationBase get style =>
|
|
new _CssStyleDeclarationSet(_elementList);
|
|
|
|
void set classes(Iterable<String> value) {
|
|
_elementList.forEach((e) => e.classes = value);
|
|
}
|
|
|
|
CssRect get contentEdge => new _ContentCssListRect(_elementList);
|
|
|
|
CssRect get paddingEdge => _elementList.first.paddingEdge;
|
|
|
|
CssRect get borderEdge => _elementList.first.borderEdge;
|
|
|
|
CssRect get marginEdge => _elementList.first.marginEdge;
|
|
$!ELEMENT_STREAM_GETTERS
|
|
}
|
|
|
|
/**
|
|
* An abstract class, which all HTML elements extend.
|
|
*/
|
|
$(ANNOTATIONS)abstract class $CLASSNAME$EXTENDS$IMPLEMENTS$NATIVESPEC {
|
|
|
|
/**
|
|
* Creates an HTML element from a valid fragment of HTML.
|
|
*
|
|
* var element = new Element.html('<div class="foo">content</div>');
|
|
*
|
|
* The HTML fragment should contain only one single root element, any
|
|
* leading or trailing text nodes will be removed.
|
|
*
|
|
* The HTML fragment is parsed as if it occurred within the context of a
|
|
* `<body>` tag, this means that special elements such as `<caption>` which
|
|
* must be parsed within the scope of a `<table>` element will be dropped. Use
|
|
* [createFragment] to parse contextual HTML fragments.
|
|
*
|
|
* Unless a validator is provided this will perform the default validation
|
|
* and remove all scriptable elements and attributes.
|
|
*
|
|
* See also:
|
|
*
|
|
* * [NodeValidator]
|
|
*
|
|
*/
|
|
factory Element.html(String html,
|
|
{NodeValidator validator, NodeTreeSanitizer treeSanitizer}) {
|
|
var fragment = document.body.createFragment(html, validator: validator,
|
|
treeSanitizer: treeSanitizer);
|
|
|
|
return fragment.nodes.where((e) => e is Element).single;
|
|
}
|
|
|
|
/**
|
|
* Creates the HTML element specified by the tag name.
|
|
*
|
|
* This is similar to [Document.createElement].
|
|
* [tag] should be a valid HTML tag name. If [tag] is an unknown tag then
|
|
* this will create an [UnknownElement].
|
|
*
|
|
* var divElement = new Element.tag('div');
|
|
* print(divElement is DivElement); // 'true'
|
|
* var myElement = new Element.tag('unknownTag');
|
|
* print(myElement is UnknownElement); // 'true'
|
|
*
|
|
* For standard elements it is more preferable to use the type constructors:
|
|
* var element = new DivElement();
|
|
*
|
|
* See also:
|
|
*
|
|
* * [isTagSupported]
|
|
*/
|
|
factory $CLASSNAME.tag(String tag, [String typeExtention]) =>
|
|
_$(CLASSNAME)FactoryProvider.createElement_tag(tag, typeExtention);
|
|
|
|
/// Creates a new `<a>` element.
|
|
///
|
|
/// This is identical to calling `new Element.tag('a')`.
|
|
factory Element.a() => new Element.tag('a');
|
|
|
|
/// Creates a new `<article>` element.
|
|
///
|
|
/// This is identical to calling `new Element.tag('article')`.
|
|
factory Element.article() => new Element.tag('article');
|
|
|
|
/// Creates a new `<aside>` element.
|
|
///
|
|
/// This is identical to calling `new Element.tag('aside')`.
|
|
factory Element.aside() => new Element.tag('aside');
|
|
|
|
/// Creates a new `<audio>` element.
|
|
///
|
|
/// This is identical to calling `new Element.tag('audio')`.
|
|
factory Element.audio() => new Element.tag('audio');
|
|
|
|
/// Creates a new `<br>` element.
|
|
///
|
|
/// This is identical to calling `new Element.tag('br')`.
|
|
factory Element.br() => new Element.tag('br');
|
|
|
|
/// Creates a new `<canvas>` element.
|
|
///
|
|
/// This is identical to calling `new Element.tag('canvas')`.
|
|
factory Element.canvas() => new Element.tag('canvas');
|
|
|
|
/// Creates a new `<div>` element.
|
|
///
|
|
/// This is identical to calling `new Element.tag('div')`.
|
|
factory Element.div() => new Element.tag('div');
|
|
|
|
/// Creates a new `<footer>` element.
|
|
///
|
|
/// This is identical to calling `new Element.tag('footer')`.
|
|
factory Element.footer() => new Element.tag('footer');
|
|
|
|
/// Creates a new `<header>` element.
|
|
///
|
|
/// This is identical to calling `new Element.tag('header')`.
|
|
factory Element.header() => new Element.tag('header');
|
|
|
|
/// Creates a new `<hr>` element.
|
|
///
|
|
/// This is identical to calling `new Element.tag('hr')`.
|
|
factory Element.hr() => new Element.tag('hr');
|
|
|
|
/// Creates a new `<iframe>` element.
|
|
///
|
|
/// This is identical to calling `new Element.tag('iframe')`.
|
|
factory Element.iframe() => new Element.tag('iframe');
|
|
|
|
/// Creates a new `<img>` element.
|
|
///
|
|
/// This is identical to calling `new Element.tag('img')`.
|
|
factory Element.img() => new Element.tag('img');
|
|
|
|
/// Creates a new `<li>` element.
|
|
///
|
|
/// This is identical to calling `new Element.tag('li')`.
|
|
factory Element.li() => new Element.tag('li');
|
|
|
|
/// Creates a new `<nav>` element.
|
|
///
|
|
/// This is identical to calling `new Element.tag('nav')`.
|
|
factory Element.nav() => new Element.tag('nav');
|
|
|
|
/// Creates a new `<ol>` element.
|
|
///
|
|
/// This is identical to calling `new Element.tag('ol')`.
|
|
factory Element.ol() => new Element.tag('ol');
|
|
|
|
/// Creates a new `<option>` element.
|
|
///
|
|
/// This is identical to calling `new Element.tag('option')`.
|
|
factory Element.option() => new Element.tag('option');
|
|
|
|
/// Creates a new `<p>` element.
|
|
///
|
|
/// This is identical to calling `new Element.tag('p')`.
|
|
factory Element.p() => new Element.tag('p');
|
|
|
|
/// Creates a new `<pre>` element.
|
|
///
|
|
/// This is identical to calling `new Element.tag('pre')`.
|
|
factory Element.pre() => new Element.tag('pre');
|
|
|
|
/// Creates a new `<section>` element.
|
|
///
|
|
/// This is identical to calling `new Element.tag('section')`.
|
|
factory Element.section() => new Element.tag('section');
|
|
|
|
/// Creates a new `<select>` element.
|
|
///
|
|
/// This is identical to calling `new Element.tag('select')`.
|
|
factory Element.select() => new Element.tag('select');
|
|
|
|
/// Creates a new `<span>` element.
|
|
///
|
|
/// This is identical to calling `new Element.tag('span')`.
|
|
factory Element.span() => new Element.tag('span');
|
|
|
|
/// Creates a new `<svg>` element.
|
|
///
|
|
/// This is identical to calling `new Element.tag('svg')`.
|
|
factory Element.svg() => new Element.tag('svg');
|
|
|
|
/// Creates a new `<table>` element.
|
|
///
|
|
/// This is identical to calling `new Element.tag('table')`.
|
|
factory Element.table() => new Element.tag('table');
|
|
|
|
/// Creates a new `<td>` element.
|
|
///
|
|
/// This is identical to calling `new Element.tag('td')`.
|
|
factory Element.td() => new Element.tag('td');
|
|
|
|
/// Creates a new `<textarea>` element.
|
|
///
|
|
/// This is identical to calling `new Element.tag('textarea')`.
|
|
factory Element.textarea() => new Element.tag('textarea');
|
|
|
|
/// Creates a new `<th>` element.
|
|
///
|
|
/// This is identical to calling `new Element.tag('th')`.
|
|
factory Element.th() => new Element.tag('th');
|
|
|
|
/// Creates a new `<tr>` element.
|
|
///
|
|
/// This is identical to calling `new Element.tag('tr')`.
|
|
factory Element.tr() => new Element.tag('tr');
|
|
|
|
/// Creates a new `<ul>` element.
|
|
///
|
|
/// This is identical to calling `new Element.tag('ul')`.
|
|
factory Element.ul() => new Element.tag('ul');
|
|
|
|
/// Creates a new `<video>` element.
|
|
///
|
|
/// This is identical to calling `new Element.tag('video')`.
|
|
factory Element.video() => new Element.tag('video');
|
|
|
|
/**
|
|
* All attributes on this element.
|
|
*
|
|
* Any modifications to the attribute map will automatically be applied to
|
|
* this element.
|
|
*
|
|
* This only includes attributes which are not in a namespace
|
|
* (such as 'xlink:href'), additional attributes can be accessed via
|
|
* [getNamespacedAttributes].
|
|
*/
|
|
Map<String, String> get attributes => new _ElementAttributeMap(this);
|
|
|
|
void set attributes(Map<String, String> value) {
|
|
Map<String, String> attributes = this.attributes;
|
|
attributes.clear();
|
|
for (String key in value.keys) {
|
|
attributes[key] = value[key];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* List of the direct children of this element.
|
|
*
|
|
* This collection can be used to add and remove elements from the document.
|
|
*
|
|
* var item = new DivElement();
|
|
* item.text = 'Something';
|
|
* document.body.children.add(item) // Item is now displayed on the page.
|
|
* for (var element in document.body.children) {
|
|
* element.style.background = 'red'; // Turns every child of body red.
|
|
* }
|
|
*/
|
|
List<Element> get children => new _ChildrenElementList._wrap(this);
|
|
|
|
void set children(List<Element> value) {
|
|
// Copy list first since we don't want liveness during iteration.
|
|
List copy = new List.from(value);
|
|
var children = this.children;
|
|
children.clear();
|
|
children.addAll(copy);
|
|
}
|
|
|
|
/**
|
|
* Finds all descendent elements of this element that match the specified
|
|
* group of selectors.
|
|
*
|
|
* [selectors] should be a string using CSS selector syntax.
|
|
*
|
|
* var items = element.query('.itemClassName');
|
|
*/
|
|
ElementList queryAll(String selectors) =>
|
|
new _FrozenElementList._wrap(_querySelectorAll(selectors));
|
|
|
|
/**
|
|
* The set of CSS classes applied to this element.
|
|
*
|
|
* This set makes it easy to add, remove or toggle the classes applied to
|
|
* this element.
|
|
*
|
|
* element.classes.add('selected');
|
|
* element.classes.toggle('isOnline');
|
|
* element.classes.remove('selected');
|
|
*/
|
|
CssClassSet get classes => new _ElementCssClassSet(this);
|
|
|
|
void set classes(Iterable<String> value) {
|
|
CssClassSet classSet = classes;
|
|
classSet.clear();
|
|
classSet.addAll(value);
|
|
}
|
|
|
|
/**
|
|
* Allows access to all custom data attributes (data-*) set on this element.
|
|
*
|
|
* The keys for the map must follow these rules:
|
|
*
|
|
* * The name must not begin with 'xml'.
|
|
* * The name cannot contain a semi-colon (';').
|
|
* * The name cannot contain any capital letters.
|
|
*
|
|
* Any keys from markup will be converted to camel-cased keys in the map.
|
|
*
|
|
* For example, HTML specified as:
|
|
*
|
|
* <div data-my-random-value='value'></div>
|
|
*
|
|
* Would be accessed in Dart as:
|
|
*
|
|
* var value = element.dataset['myRandomValue'];
|
|
*
|
|
* See also:
|
|
*
|
|
* * [Custom data attributes](http://www.w3.org/TR/html5/global-attributes.html#custom-data-attribute)
|
|
*/
|
|
Map<String, String> get dataset =>
|
|
new _DataAttributeMap(attributes);
|
|
|
|
void set dataset(Map<String, String> value) {
|
|
final data = this.dataset;
|
|
data.clear();
|
|
for (String key in value.keys) {
|
|
data[key] = value[key];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Gets a map for manipulating the attributes of a particular namespace.
|
|
*
|
|
* This is primarily useful for SVG attributes such as xref:link.
|
|
*/
|
|
Map<String, String> getNamespacedAttributes(String namespace) {
|
|
return new _NamespacedAttributeMap(this, namespace);
|
|
}
|
|
|
|
/**
|
|
* The set of all CSS values applied to this element, including inherited
|
|
* and default values.
|
|
*
|
|
* The computedStyle contains values that are inherited from other
|
|
* sources, such as parent elements or stylesheets. This differs from the
|
|
* [style] property, which contains only the values specified directly on this
|
|
* element.
|
|
*
|
|
* PseudoElement can be values such as `::after`, `::before`, `::marker`,
|
|
* `::line-marker`.
|
|
*
|
|
* See also:
|
|
*
|
|
* * [CSS Inheritance and Cascade](http://docs.webplatform.org/wiki/tutorials/inheritance_and_cascade)
|
|
* * [Pseudo-elements](http://docs.webplatform.org/wiki/css/selectors/pseudo-elements)
|
|
*/
|
|
CssStyleDeclaration getComputedStyle([String pseudoElement]) {
|
|
if (pseudoElement == null) {
|
|
pseudoElement = '';
|
|
}
|
|
// TODO(jacobr): last param should be null, see b/5045788
|
|
return window._getComputedStyle(this, pseudoElement);
|
|
}
|
|
|
|
/**
|
|
* Gets the position of this element relative to the client area of the page.
|
|
*/
|
|
Rect get client => new Rect(clientLeft, clientTop, clientWidth, clientHeight);
|
|
|
|
/**
|
|
* Gets the offset of this element relative to its offsetParent.
|
|
*/
|
|
Rect get offset => new Rect(offsetLeft, offsetTop, offsetWidth, offsetHeight);
|
|
|
|
/**
|
|
* Adds the specified text after the last child of this element.
|
|
*/
|
|
void appendText(String text) {
|
|
this.insertAdjacentText('beforeend', text);
|
|
}
|
|
|
|
/**
|
|
* Parses the specified text as HTML and adds the resulting node after the
|
|
* last child of this element.
|
|
*/
|
|
void appendHtml(String text) {
|
|
this.insertAdjacentHtml('beforeend', text);
|
|
}
|
|
|
|
/**
|
|
* Checks to see if the tag name is supported by the current platform.
|
|
*
|
|
* The tag should be a valid HTML tag name.
|
|
*/
|
|
static bool isTagSupported(String tag) {
|
|
var e = _ElementFactoryProvider.createElement_tag(tag, null);
|
|
return e is Element && !(e is UnknownElement);
|
|
}
|
|
|
|
/**
|
|
* Called by the DOM when this element has been instantiated.
|
|
*/
|
|
@Experimental()
|
|
void created() {}
|
|
|
|
// Hooks to support custom WebComponents.
|
|
|
|
$if DART2JS
|
|
@Creates('Null') // Set from Dart code; does not instantiate a native type.
|
|
$endif
|
|
Element _xtag;
|
|
|
|
/**
|
|
* Experimental support for [web components][wc]. This field stores a
|
|
* reference to the component implementation. It was inspired by Mozilla's
|
|
* [x-tags][] project. Please note: in the future it may be possible to
|
|
* `extend Element` from your class, in which case this field will be
|
|
* deprecated.
|
|
*
|
|
* If xtag has not been set, it will simply return `this` [Element].
|
|
*
|
|
* [wc]: http://dvcs.w3.org/hg/webcomponents/raw-file/tip/explainer/index.html
|
|
* [x-tags]: http://x-tags.org/
|
|
*/
|
|
// Note: return type is `dynamic` for convenience to suppress warnings when
|
|
// members of the component are used. The actual type is a subtype of Element.
|
|
get xtag => _xtag != null ? _xtag : this;
|
|
|
|
void set xtag(Element value) {
|
|
_xtag = value;
|
|
}
|
|
|
|
@DomName('Element.localName')
|
|
@DocsEditable()
|
|
String get localName => _localName;
|
|
|
|
@DomName('Element.namespaceUri')
|
|
@DocsEditable()
|
|
String get namespaceUri => _namespaceUri;
|
|
|
|
String toString() => localName;
|
|
|
|
/**
|
|
* Scrolls this element into view.
|
|
*
|
|
* Only one of of the alignment options may be specified at a time.
|
|
*
|
|
* If no options are specified then this will attempt to scroll the minimum
|
|
* amount needed to bring the element into view.
|
|
*
|
|
* Note that alignCenter is currently only supported on WebKit platforms. If
|
|
* alignCenter is specified but not supported then this will fall back to
|
|
* alignTop.
|
|
*
|
|
* See also:
|
|
*
|
|
* * [scrollIntoView](http://docs.webplatform.org/wiki/dom/methods/scrollIntoView)
|
|
* * [scrollIntoViewIfNeeded](http://docs.webplatform.org/wiki/dom/methods/scrollIntoViewIfNeeded)
|
|
*/
|
|
void scrollIntoView([ScrollAlignment alignment]) {
|
|
var hasScrollIntoViewIfNeeded = false;
|
|
$if DART2JS
|
|
hasScrollIntoViewIfNeeded =
|
|
JS('bool', '!!(#.scrollIntoViewIfNeeded)', this);
|
|
$endif
|
|
if (alignment == ScrollAlignment.TOP) {
|
|
this._scrollIntoView(true);
|
|
} else if (alignment == ScrollAlignment.BOTTOM) {
|
|
this._scrollIntoView(false);
|
|
} else if (hasScrollIntoViewIfNeeded) {
|
|
if (alignment == ScrollAlignment.CENTER) {
|
|
this._scrollIntoViewIfNeeded(true);
|
|
} else {
|
|
this._scrollIntoViewIfNeeded();
|
|
}
|
|
} else {
|
|
this._scrollIntoView();
|
|
}
|
|
}
|
|
|
|
$if DART2JS
|
|
@DomName('Element.mouseWheelEvent')
|
|
static const EventStreamProvider<WheelEvent> mouseWheelEvent =
|
|
const _CustomEventStreamProvider<WheelEvent>(
|
|
Element._determineMouseWheelEventType);
|
|
|
|
static String _determineMouseWheelEventType(EventTarget e) {
|
|
if (JS('bool', '#.onwheel !== undefined', e)) {
|
|
// W3C spec, and should be IE9+, but IE has a bug exposing onwheel.
|
|
return 'wheel';
|
|
} else if (JS('bool', '#.onmousewheel !== undefined', e)) {
|
|
// Chrome & IE
|
|
return 'mousewheel';
|
|
} else {
|
|
// Firefox
|
|
return 'DOMMouseScroll';
|
|
}
|
|
}
|
|
|
|
@DomName('Element.transitionEndEvent')
|
|
static const EventStreamProvider<TransitionEvent> transitionEndEvent =
|
|
const _CustomEventStreamProvider<TransitionEvent>(
|
|
Element._determineTransitionEventType);
|
|
|
|
static String _determineTransitionEventType(EventTarget e) {
|
|
// Unfortunately the normal 'ontransitionend' style checks don't work here.
|
|
if (Device.isWebKit) {
|
|
return 'webkitTransitionEnd';
|
|
} else if (Device.isOpera) {
|
|
return 'oTransitionEnd';
|
|
}
|
|
return 'transitionend';
|
|
}
|
|
/**
|
|
* Inserts text into the DOM at the specified location.
|
|
*
|
|
* To see the possible values for [where], read the doc for
|
|
* [insertAdjacentHtml].
|
|
*
|
|
* See also:
|
|
*
|
|
* * [insertAdjacentHtml]
|
|
*/
|
|
void insertAdjacentText(String where, String text) {
|
|
if (JS('bool', '!!#.insertAdjacentText', this)) {
|
|
_insertAdjacentText(where, text);
|
|
} else {
|
|
_insertAdjacentNode(where, new Text(text));
|
|
}
|
|
}
|
|
|
|
@JSName('insertAdjacentText')
|
|
void _insertAdjacentText(String where, String text) native;
|
|
|
|
/**
|
|
* Parses text as an HTML fragment and inserts it into the DOM at the
|
|
* specified location.
|
|
*
|
|
* The [where] parameter indicates where to insert the HTML fragment:
|
|
*
|
|
* * 'beforeBegin': Immediately before this element.
|
|
* * 'afterBegin': As the first child of this element.
|
|
* * 'beforeEnd': As the last child of this element.
|
|
* * 'afterEnd': Immediately after this element.
|
|
*
|
|
* var html = '<div class="something">content</div>';
|
|
* // Inserts as the first child
|
|
* document.body.insertAdjacentHtml('afterBegin', html);
|
|
* var createdElement = document.body.children[0];
|
|
* print(createdElement.classes[0]); // Prints 'something'
|
|
*
|
|
* See also:
|
|
*
|
|
* * [insertAdjacentText]
|
|
* * [insertAdjacentElement]
|
|
*/
|
|
void insertAdjacentHtml(String where, String html) {
|
|
if (JS('bool', '!!#.insertAdjacentHTML', this)) {
|
|
_insertAdjacentHtml(where, html);
|
|
} else {
|
|
_insertAdjacentNode(where, new DocumentFragment.html(html));
|
|
}
|
|
}
|
|
|
|
@JSName('insertAdjacentHTML')
|
|
void _insertAdjacentHtml(String where, String text) native;
|
|
|
|
/**
|
|
* Inserts [element] into the DOM at the specified location.
|
|
*
|
|
* To see the possible values for [where], read the doc for
|
|
* [insertAdjacentHtml].
|
|
*
|
|
* See also:
|
|
*
|
|
* * [insertAdjacentHtml]
|
|
*/
|
|
Element insertAdjacentElement(String where, Element element) {
|
|
if (JS('bool', '!!#.insertAdjacentElement', this)) {
|
|
_insertAdjacentElement(where, element);
|
|
} else {
|
|
_insertAdjacentNode(where, element);
|
|
}
|
|
return element;
|
|
}
|
|
|
|
@JSName('insertAdjacentElement')
|
|
void _insertAdjacentElement(String where, Element element) native;
|
|
|
|
void _insertAdjacentNode(String where, Node node) {
|
|
switch (where.toLowerCase()) {
|
|
case 'beforebegin':
|
|
this.parentNode.insertBefore(node, this);
|
|
break;
|
|
case 'afterbegin':
|
|
var first = this.nodes.length > 0 ? this.nodes[0] : null;
|
|
this.insertBefore(node, first);
|
|
break;
|
|
case 'beforeend':
|
|
this.append(node);
|
|
break;
|
|
case 'afterend':
|
|
this.parentNode.insertBefore(node, this.nextNode);
|
|
break;
|
|
default:
|
|
throw new ArgumentError("Invalid position ${where}");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Checks if this element matches the CSS selectors.
|
|
*/
|
|
@Experimental()
|
|
bool matches(String selectors) {
|
|
if (JS('bool', '!!#.matches', this)) {
|
|
return JS('bool', '#.matches(#)', this, selectors);
|
|
} else if (JS('bool', '!!#.webkitMatchesSelector', this)) {
|
|
return JS('bool', '#.webkitMatchesSelector(#)', this, selectors);
|
|
} else if (JS('bool', '!!#.mozMatchesSelector', this)) {
|
|
return JS('bool', '#.mozMatchesSelector(#)', this, selectors);
|
|
} else if (JS('bool', '!!#.msMatchesSelector', this)) {
|
|
return JS('bool', '#.msMatchesSelector(#)', this, selectors);
|
|
} else {
|
|
throw new UnsupportedError("Not supported on this platform");
|
|
}
|
|
}
|
|
$else
|
|
$endif
|
|
|
|
/** Checks if this element or any of its parents match the CSS selectors. */
|
|
@Experimental()
|
|
bool matchesWithAncestors(String selectors) {
|
|
var elem = this;
|
|
do {
|
|
if (elem.matches(selectors)) return true;
|
|
elem = elem.parent;
|
|
} while(elem != null);
|
|
return false;
|
|
}
|
|
|
|
$if DART2JS
|
|
@Creates('Null') // Set from Dart code; does not instantiate a native type.
|
|
$endif
|
|
Element _templateInstanceRef;
|
|
|
|
// Note: only used if `this is! TemplateElement`
|
|
$if DART2JS
|
|
@Creates('Null') // Set from Dart code; does not instantiate a native type.
|
|
$endif
|
|
DocumentFragment _templateContent;
|
|
|
|
bool _templateIsDecorated;
|
|
|
|
$if DART2JS
|
|
@DomName('Element.createShadowRoot')
|
|
@SupportedBrowser(SupportedBrowser.CHROME, '25')
|
|
@Experimental()
|
|
ShadowRoot createShadowRoot() {
|
|
return JS('ShadowRoot',
|
|
'(#.createShadowRoot || #.webkitCreateShadowRoot).call(#)',
|
|
this, this, this);
|
|
}
|
|
|
|
@DomName('Element.shadowRoot')
|
|
@SupportedBrowser(SupportedBrowser.CHROME, '25')
|
|
@Experimental()
|
|
ShadowRoot get shadowRoot =>
|
|
JS('ShadowRoot', '#.shadowRoot || #.webkitShadowRoot', this, this);
|
|
$endif
|
|
|
|
|
|
/**
|
|
* Gets the template this node refers to.
|
|
* This is only supported if [isTemplate] is true.
|
|
*/
|
|
@Experimental()
|
|
Element get ref {
|
|
_ensureTemplate();
|
|
|
|
Element result = null;
|
|
var refId = attributes['ref'];
|
|
if (refId != null) {
|
|
var treeScope = this;
|
|
while (treeScope.parentNode != null) {
|
|
treeScope = treeScope.parentNode;
|
|
}
|
|
|
|
// Note: JS code tests that getElementById is present. We can't do that
|
|
// easily, so instead check for the types known to implement it.
|
|
if (treeScope is Document ||
|
|
treeScope is ShadowRoot ||
|
|
treeScope is svg.SvgSvgElement) {
|
|
|
|
result = treeScope.getElementById(refId);
|
|
}
|
|
}
|
|
|
|
if (result == null) {
|
|
result = _templateInstanceRef;
|
|
if (result == null) return this;
|
|
}
|
|
|
|
var nextRef = result.ref;
|
|
return nextRef != null ? nextRef : result;
|
|
}
|
|
|
|
/**
|
|
* Gets the content of this template.
|
|
* This is only supported if [isTemplate] is true.
|
|
*/
|
|
@Experimental()
|
|
DocumentFragment get content {
|
|
_ensureTemplate();
|
|
return _templateContent;
|
|
}
|
|
|
|
/**
|
|
* Creates an instance of the template, using the provided model and optional
|
|
* binding delegate.
|
|
*
|
|
* This is only supported if [isTemplate] is true.
|
|
*/
|
|
@Experimental()
|
|
DocumentFragment createInstance(model, [BindingDelegate delegate]) {
|
|
_ensureTemplate();
|
|
return TemplateElement.mdvPackage(this).createInstance(model, delegate);
|
|
}
|
|
|
|
/**
|
|
* The data model which is inherited through the tree.
|
|
* This is only supported if [isTemplate] is true.
|
|
*/
|
|
@Experimental()
|
|
get model => TemplateElement.mdvPackage(this).model;
|
|
|
|
@Experimental()
|
|
void set model(value) {
|
|
_ensureTemplate();
|
|
TemplateElement.mdvPackage(this).model = value;
|
|
}
|
|
|
|
/**
|
|
* The binding delegate which is inherited through the tree. It can be used
|
|
* to configure custom syntax for `{{bindings}}` inside this template.
|
|
*
|
|
* This is only supported if [isTemplate] is true.
|
|
*/
|
|
@Experimental()
|
|
BindingDelegate get bindingDelegate =>
|
|
TemplateElement.mdvPackage(this).bindingDelegate;
|
|
|
|
@Experimental()
|
|
void set bindingDelegate(BindingDelegate value) {
|
|
_ensureTemplate();
|
|
TemplateElement.mdvPackage(this).bindingDelegate = value;
|
|
}
|
|
|
|
// TODO(jmesserly): const set would be better
|
|
static const _TABLE_TAGS = const {
|
|
'caption': null,
|
|
'col': null,
|
|
'colgroup': null,
|
|
'tbody': null,
|
|
'td': null,
|
|
'tfoot': null,
|
|
'th': null,
|
|
'thead': null,
|
|
'tr': null,
|
|
};
|
|
|
|
bool get _isAttributeTemplate => attributes.containsKey('template') &&
|
|
(localName == 'option' || localName == 'optgroup' ||
|
|
_TABLE_TAGS.containsKey(localName));
|
|
|
|
/**
|
|
* Returns true if this node is a template.
|
|
*
|
|
* A node is a template if [tagName] is TEMPLATE, or the node has the
|
|
* 'template' attribute and this tag supports attribute form for backwards
|
|
* compatibility with existing HTML parsers. The nodes that can use attribute
|
|
* form are table elments (THEAD, TBODY, TFOOT, TH, TR, TD, CAPTION, COLGROUP
|
|
* and COL), OPTION, and OPTGROUP.
|
|
*/
|
|
// TODO(jmesserly): this is not a public MDV API, but it seems like a useful
|
|
// place to document which tags our polyfill considers to be templates.
|
|
// Otherwise I'd be repeating it in several other places.
|
|
// See if we can replace this with a TemplateMixin.
|
|
@Experimental()
|
|
bool get isTemplate => tagName == 'TEMPLATE' || _isAttributeTemplate;
|
|
|
|
void _ensureTemplate() {
|
|
if (!isTemplate) {
|
|
throw new UnsupportedError('$this is not a template.');
|
|
}
|
|
TemplateElement.decorate(this);
|
|
}
|
|
|
|
/**
|
|
* Access this element's content position.
|
|
*
|
|
* This returns a rectangle with the dimenions actually available for content
|
|
* in this element, in pixels, regardless of this element's box-sizing
|
|
* property. Unlike [getBoundingClientRect], the dimensions of this rectangle
|
|
* will return the same numerical height if the element is hidden or not.
|
|
*
|
|
* _Important_ _note_: use of this method _will_ perform CSS calculations that
|
|
* can trigger a browser reflow. Therefore, use of this property _during_ an
|
|
* animation frame is discouraged. See also:
|
|
* [Browser Reflow](https://developers.google.com/speed/articles/reflow)
|
|
*/
|
|
@Experimental()
|
|
CssRect get contentEdge => new _ContentCssRect(this);
|
|
|
|
/**
|
|
* Access the dimensions and position of this element's content + padding box.
|
|
*
|
|
* This returns a rectangle with the dimenions actually available for content
|
|
* in this element, in pixels, regardless of this element's box-sizing
|
|
* property. Unlike [getBoundingClientRect], the dimensions of this rectangle
|
|
* will return the same numerical height if the element is hidden or not. This
|
|
* can be used to retrieve jQuery's
|
|
* [innerHeight](http://api.jquery.com/innerHeight/) value for an element.
|
|
* This is also a rectangle equalling the dimensions of clientHeight and
|
|
* clientWidth.
|
|
*
|
|
* _Important_ _note_: use of this method _will_ perform CSS calculations that
|
|
* can trigger a browser reflow. Therefore, use of this property _during_ an
|
|
* animation frame is discouraged. See also:
|
|
* [Browser Reflow](https://developers.google.com/speed/articles/reflow)
|
|
*/
|
|
@Experimental()
|
|
CssRect get paddingEdge => new _PaddingCssRect(this);
|
|
|
|
/**
|
|
* Access the dimensions and position of this element's content + padding +
|
|
* border box.
|
|
*
|
|
* This returns a rectangle with the dimenions actually available for content
|
|
* in this element, in pixels, regardless of this element's box-sizing
|
|
* property. Unlike [getBoundingClientRect], the dimensions of this rectangle
|
|
* will return the same numerical height if the element is hidden or not. This
|
|
* can be used to retrieve jQuery's
|
|
* [outerHeight](http://api.jquery.com/outerHeight/) value for an element.
|
|
*
|
|
* _Important_ _note_: use of this method _will_ perform CSS calculations that
|
|
* can trigger a browser reflow. Therefore, use of this property _during_ an
|
|
* animation frame is discouraged. See also:
|
|
* [Browser Reflow](https://developers.google.com/speed/articles/reflow)
|
|
*/
|
|
@Experimental()
|
|
CssRect get borderEdge => new _BorderCssRect(this);
|
|
|
|
/**
|
|
* Access the dimensions and position of this element's content + padding +
|
|
* border + margin box.
|
|
*
|
|
* This returns a rectangle with the dimenions actually available for content
|
|
* in this element, in pixels, regardless of this element's box-sizing
|
|
* property. Unlike [getBoundingClientRect], the dimensions of this rectangle
|
|
* will return the same numerical height if the element is hidden or not. This
|
|
* can be used to retrieve jQuery's
|
|
* [outerHeight](http://api.jquery.com/outerHeight/) value for an element.
|
|
*
|
|
* _Important_ _note_: use of this method will perform CSS calculations that
|
|
* can trigger a browser reflow. Therefore, use of this property _during_ an
|
|
* animation frame is discouraged. See also:
|
|
* [Browser Reflow](https://developers.google.com/speed/articles/reflow)
|
|
*/
|
|
@Experimental()
|
|
CssRect get marginEdge => new _MarginCssRect(this);
|
|
|
|
/**
|
|
* Provides the coordinates of the element relative to the top of the
|
|
* document.
|
|
*
|
|
* This method is the Dart equivalent to jQuery's
|
|
* [offset](http://api.jquery.com/offset/) method.
|
|
*/
|
|
Point get documentOffset => offsetTo(document.documentElement);
|
|
|
|
/**
|
|
* Provides the offset of this element's [borderEdge] relative to the
|
|
* specified [parent].
|
|
*
|
|
* This is the Dart equivalent of jQuery's
|
|
* [position](http://api.jquery.com/position/) method. Unlike jQuery's
|
|
* position, however, [parent] can be any parent element of `this`,
|
|
* rather than only `this`'s immediate [offsetParent]. If the specified
|
|
* element is _not_ an offset parent or transitive offset parent to this
|
|
* element, an [ArgumentError] is thrown.
|
|
*/
|
|
Point offsetTo(Element parent) {
|
|
return Element._offsetToHelper(this, parent);
|
|
}
|
|
|
|
static Point _offsetToHelper(Element current, Element parent) {
|
|
// We're hopping from _offsetParent_ to offsetParent (not just parent), so
|
|
// offsetParent, "tops out" at BODY. But people could conceivably pass in
|
|
// the document.documentElement and I want it to return an absolute offset,
|
|
// so we have the special case checking for HTML.
|
|
bool foundAsParent = identical(current, parent) || parent.tagName == 'HTML';
|
|
if (current == null || identical(current, parent)) {
|
|
if (foundAsParent) return new Point(0, 0);
|
|
throw new ArgumentError("Specified element is not a transitive offset "
|
|
"parent of this element.");
|
|
}
|
|
Element parentOffset = current.offsetParent;
|
|
Point p = Element._offsetToHelper(parentOffset, parent);
|
|
return new Point(p.x + current.offsetLeft, p.y + current.offsetTop);
|
|
}
|
|
|
|
static HtmlDocument _parseDocument;
|
|
static Range _parseRange;
|
|
static NodeValidatorBuilder _defaultValidator;
|
|
static _ValidatingTreeSanitizer _defaultSanitizer;
|
|
|
|
/**
|
|
* Create a DocumentFragment from the HTML fragment and ensure that it follows
|
|
* the sanitization rules specified by the validator or treeSanitizer.
|
|
*
|
|
* If the default validation behavior is too restrictive then a new
|
|
* NodeValidator should be created, either extending or wrapping a default
|
|
* validator and overriding the validation APIs.
|
|
*
|
|
* The treeSanitizer is used to walk the generated node tree and sanitize it.
|
|
* A custom treeSanitizer can also be provided to perform special validation
|
|
* rules but since the API is more complex to implement this is discouraged.
|
|
*
|
|
* The returned tree is guaranteed to only contain nodes and attributes which
|
|
* are allowed by the provided validator.
|
|
*
|
|
* See also:
|
|
*
|
|
* * [NodeValidator]
|
|
* * [NodeTreeSanitizer]
|
|
*/
|
|
DocumentFragment createFragment(String html,
|
|
{NodeValidator validator, NodeTreeSanitizer treeSanitizer}) {
|
|
if (treeSanitizer == null) {
|
|
if (validator == null) {
|
|
if (_defaultValidator == null) {
|
|
_defaultValidator = new NodeValidatorBuilder.common();
|
|
}
|
|
validator = _defaultValidator;
|
|
}
|
|
if (_defaultSanitizer == null) {
|
|
_defaultSanitizer = new _ValidatingTreeSanitizer(validator);
|
|
} else {
|
|
_defaultSanitizer.validator = validator;
|
|
}
|
|
treeSanitizer = _defaultSanitizer;
|
|
} else if (validator != null) {
|
|
throw new ArgumentError(
|
|
'validator can only be passed if treeSanitizer is null');
|
|
}
|
|
|
|
if (_parseDocument == null) {
|
|
_parseDocument = document.implementation.createHtmlDocument('');
|
|
_parseRange = _parseDocument.createRange();
|
|
|
|
// Workaround for Chrome bug 229142- URIs are not resolved in new doc.
|
|
var base = _parseDocument.$dom_createElement('base');
|
|
base.href = document._baseUri;
|
|
_parseDocument.head.append(base);
|
|
}
|
|
var contextElement;
|
|
if (this is BodyElement) {
|
|
contextElement = _parseDocument.body;
|
|
} else {
|
|
contextElement = _parseDocument.$dom_createElement(tagName);
|
|
_parseDocument.body.append(contextElement);
|
|
}
|
|
var fragment;
|
|
if (Range.supportsCreateContextualFragment) {
|
|
_parseRange.selectNodeContents(contextElement);
|
|
fragment = _parseRange.createContextualFragment(html);
|
|
} else {
|
|
contextElement._innerHtml = html;
|
|
|
|
fragment = _parseDocument.createDocumentFragment();
|
|
while (contextElement.firstChild != null) {
|
|
fragment.append(contextElement.firstChild);
|
|
}
|
|
}
|
|
if (contextElement != _parseDocument.body) {
|
|
contextElement.remove();
|
|
}
|
|
|
|
treeSanitizer.sanitizeTree(fragment);
|
|
return fragment;
|
|
}
|
|
|
|
/**
|
|
* Parses the HTML fragment and sets it as the contents of this element.
|
|
*
|
|
* This uses the default sanitization behavior to sanitize the HTML fragment,
|
|
* use [setInnerHtml] to override the default behavior.
|
|
*/
|
|
void set innerHtml(String html) {
|
|
this.setInnerHtml(html);
|
|
}
|
|
|
|
/**
|
|
* Parses the HTML fragment and sets it as the contents of this element.
|
|
* This ensures that the generated content follows the sanitization rules
|
|
* specified by the validator or treeSanitizer.
|
|
*
|
|
* If the default validation behavior is too restrictive then a new
|
|
* NodeValidator should be created, either extending or wrapping a default
|
|
* validator and overriding the validation APIs.
|
|
*
|
|
* The treeSanitizer is used to walk the generated node tree and sanitize it.
|
|
* A custom treeSanitizer can also be provided to perform special validation
|
|
* rules but since the API is more complex to implement this is discouraged.
|
|
*
|
|
* The resulting tree is guaranteed to only contain nodes and attributes which
|
|
* are allowed by the provided validator.
|
|
*
|
|
* See also:
|
|
*
|
|
* * [NodeValidator]
|
|
* * [NodeTreeSanitizer]
|
|
*/
|
|
void setInnerHtml(String html,
|
|
{NodeValidator validator, NodeTreeSanitizer treeSanitizer}) {
|
|
text = null;
|
|
append(createFragment(
|
|
html, validator: validator, treeSanitizer: treeSanitizer));
|
|
}
|
|
String get innerHtml => _innerHtml;
|
|
|
|
/**
|
|
* For use while transitioning to the safe [innerHtml] or [setInnerHtml].
|
|
* Unsafe because it opens the app to cross-site scripting vulnerabilities.
|
|
*/
|
|
@deprecated
|
|
void set unsafeInnerHtml(String html) {
|
|
_innerHtml = html;
|
|
}
|
|
|
|
$!MEMBERS
|
|
}
|
|
|
|
|
|
class _ElementFactoryProvider {
|
|
|
|
@DomName('Document.createElement')
|
|
$if DART2JS
|
|
// Optimization to improve performance until the dart2js compiler inlines this
|
|
// method.
|
|
static dynamic createElement_tag(String tag, String typeExtension) {
|
|
// Firefox may return a JS function for some types (Embed, Object).
|
|
if (typeExtension != null) {
|
|
return JS('Element|=Object', 'document.createElement(#, #)',
|
|
tag, typeExtension);
|
|
}
|
|
// Should be able to eliminate this and just call the two-arg version above
|
|
// with null typeExtension, but Chrome treats the tag as case-sensitive if
|
|
// typeExtension is null.
|
|
// https://code.google.com/p/chromium/issues/detail?id=282467
|
|
return JS('Element|=Object', 'document.createElement(#)', tag);
|
|
}
|
|
|
|
$else
|
|
static Element createElement_tag(String tag, String typeExtension) =>
|
|
document.$dom_createElement(tag, typeExtension);
|
|
$endif
|
|
}
|
|
|
|
|
|
/**
|
|
* Options for Element.scrollIntoView.
|
|
*/
|
|
class ScrollAlignment {
|
|
final _value;
|
|
const ScrollAlignment._internal(this._value);
|
|
toString() => 'ScrollAlignment.$_value';
|
|
|
|
/// Attempt to align the element to the top of the scrollable area.
|
|
static const TOP = const ScrollAlignment._internal('TOP');
|
|
/// Attempt to center the element in the scrollable area.
|
|
static const CENTER = const ScrollAlignment._internal('CENTER');
|
|
/// Attempt to align the element to the bottom of the scrollable area.
|
|
static const BOTTOM = const ScrollAlignment._internal('BOTTOM');
|
|
}
|