Skip to content

Web

GraphQL Learn (1) - Queries and Mutations

在此页面上,你将详细了解如何查询GrahQL服务器。

字段(Fields)

最简单的,GraphQL是关于要求对象上的特定字段。我们先来看一个非常简单的查询,当我们运行它时得到结果:

{
  hero {
    name
  }
}
{
  "data": {
    "hero": {
      "name": "R2-D2"
    }
  }
}
可以看到,查询与结果的形状完全相同。这对于GraphQL是至关重要的,因为您总是收到您期望的内容,并且服务器确切知道客户端要求哪些字段。

GraphQL Learn (2) - Schemas and Types

在此页面上,您将了解有关GraphQL类型系统的所有知识,以及如何描述可查询哪些数据。 由于GraphQL可以与任何后端框架或编程语言一起使用,因此我们将远离实现特定的详细信息,仅讨论概念。

类型系统(Type System)

如果您以前看过GraphQL查询,那么你应该知道GraphQL查询语言基本上是在对象上选择字段。 所以在以下查询中:

{
  hero {
    name
    appearsIn
  }
}

GraphQL Learn (3) - Validation

通过使用类型系统,可以预先确定GraphQL查询是否有效。 这样可以让服务器和客户端有效地通知开发人员在创建无效查询时,无需在运行时检查。

对于我们的星球大战示例,文件starWarsValidation-test.js包含许多无效的查询,可以用来测试当前实现的验证器。

首先,我们来看一个复杂的有效查询。 这是一个嵌套查询,类似于上一节的一个示例,但将重复的字段分解成一个片段:

GraphQL Learn (4) - Execution

经过验证,GraphQL查询由GraphQL服务器执行,然后返回一个与查询形状相同的结果,通常为JSON。

GraphQL无法执行没有类型系统的查询,让我们使用类型系统例子来说明执行查询,这个例子是我们教程中使用的类型系统中的一部分:

type Query {
  human(id: ID!): Human
}
type Human {
  name: String
  appearsIn: [Episode]
  starships: [Starship]
}
enum Episode {
  NEWHOPE
  EMPIRE
  JEDI
}
type Starship {
  name: String
}

GraphQL Learn (5) - Introspection

查询GraphQL架构有关它支持的查询的信息通常很有用。 GraphQL允许我们使用内省系统来做到这一点!对于我们的星球大战例子来说,文件starWarsIntrospection-test.js包含许多查询内省系统的查询,并且是一个完整的按照规范实现的测试文件。

我们设计了类型系统,所以我们应该知道系统中可用的类型。但是如果不知道,我们还可以通过查询__schema字段来询问GraphQL。这个字段始终存在于根类型中。

Polymer 2.0 文档笔记(1) Custom Elements

https://www.polymer-project.org/2.0/docs/devguide/custom-elements

Custom element names. By specification, the custom element's name must start with a lower-case ASCII letter and must contain a dash (-). There's also a short list of prohibited element names that match existing names. For details, see the Custom elements core concepts section in the HTML specification.

自定义元素的命名规则: 按照规范,自定义元素的命名中必须以一个小写字母开始,必须包含一个连接符(-)

Custom properties can only be defined in rule-sets that match the html selector or a Polymer custom element. This is a limitation of the Polymer implementation of custom properties.

Polymer实现方式的局限: 只有html元素或者Polymer自定义元素才能使用自定义CSS属性。(个人感觉虽然shadowDOM原生支持CSS隔离,但是一部分元素能用cssnext一部分元素不能用,割裂感太严重了。)

Polymer does not currently support extending built-in elements. The custom elements spec provides a mechanism for extending built-in elements, such as <button> and <input>. The spec calls these elements customized built-in elements. Customized built-in elements provide many advantages (for example, being able to take advantage of built-in accessibility features of UI elements like <button> and<input>). However, not all browser makers have agreed to support customized built-in elements, so Polymer does not support them at this time.

因为浏览器厂商的争议,Polymer不支持扩展内建元素。

在生产环境下main document不要定义自定义元素。基于实验目的可以使用 HTMLImports.whenReady(callback)方法等待所有html import 加载完毕。

组件生命周期

标准的几个生命周期回调:

Reaction Description
constructor Called when the element is upgraded (that is, when an element is created, or when a previously-created element becomes defined).
connectedCallback Called when the element is added to a document.
disconnectedCallback Called when the element is removed from a document.
attributeChangedCallback Called when any of the element's attributes are changed, appended, removed, or replaced,

For each reaction, the first line of your implementation must be a call to the superclass constructor or reaction.

开始写自己的回调事件之前都必须先调用super上面的回调。Super.connectedCallback();  

The constructor can't examine the element's attributes or children, and the constructor can't add attributes or children.

Constructor函数中不能对DOM进行任何操作。

In general, work should be deferred to connectedCallback as much as possible—especially work involving fetching resources or rendering. However, note that connectedCallback can be called more than once, so any initialization work that is truly one-time will need a guard to prevent it from running twice. In general, the constructor should be used to set up initial state and default values, and to set up event listeners and possibly a shadow root.

https://html.spec.whatwg.org/multipage/custom-elements.html#custom-element-conformance

Constructor里面只应该做一些初始化状态、值、绑定事件,建立shadowroot之类的工作。其他工作应该推迟到connectedCallback里面做,但是需要注意的是__connectedCallback可能会调用多次__。

The custom elements specification doesn't provide a one-time initialization callback. Polymer provides a readycallback, invoked the first time the element is added to the DOM.

Polymer提供了一个规范里面没有的回调: Ready, 只在元素第一次加入DOM时候触发。

ready() { 
  super.ready(); 
  // When possible, use afterNextRender to defer non-critical 
  // work until after first paint. 
  Polymer.RenderStatus.afterNextRender(this, function() { 
    ... 
  }); 
} 

Elements have a custom element state that takes one of the following values:

  1. uncustomized: The element does not have a valid custom element name. It is either a built-in element (<p>, <input>) or an unknown element that cannot become a custom element (<nonsense>)
  2. undefined: The element has a valid custom element name (such as "my-element"), but has not been defined.
  3. custom: The element has a valid custom element name and has been defined and upgraded.
  4. failed: An attempt to upgrade the element failed (for example, because the class was invalid).

元素内部拥有四个状态,我们不能直接获得,但是可以使用:defined伪类来选择"uncustomized"和"custom"的元素。

自定义元素

Hybrid elements: should continue to use the Polymer DOM APIs, but may require some changes. Legacy elements: can use the Polymer DOM APIs or the native DOM APIs. Class-based elements: should use native DOM APIs.

总共有三种自定义元素:

  • Hybird element: 主要是为了兼容Polymer 1.x;但 Gesture Events只支持Hybird elements。详见下文。
  • Legacy element:处于两者之间(没有看出有什么用处)
  • Class-based element:Polymer 2.0 最常用的自定义元素方式。
Class-based elements
// define the element's class element
class MyElement extends Polymer.Element {
  // 'is' getter, return the tag name which is lowercased. required.
  static get is(){
    return 'my-element';
  }
  // Define the properties.
  static get properties() {}
  // Element class can define custom element reactions
  constructor() { super(); }
  connectedCallback() {
    super.connectedCallback();
    console.log('my-element created!');
  }
  ready() {
    super.ready();
    this.textContent = 'I\'m a custom element!';
  }
}

// Associate the new class with an element name
customElements.define(MyElement.is, MyElement);

// create an instance with createElement:
var el1 = document.createElement('my-element');

// ... or with the constructor:
var el2 = new MyElement();
Legacy elements
// register an element  
MyElement = Polymer({  
is: 'my-element',  
// See below for lifecycle callbacks  
created: function() { this.textContent = 'My element!'; }  
});  
// create an instance with createElement:  
var el1 = document.createElement('my-element'); 
// ... or with the constructor: 
var el2 = new MyElement();

Legacy元素的生命周期有着不同的名字:

Legacy lifecycle cb Class-based lifecycle cb
Created constructor
Ready ready
Attached connectedCallback
Detached disconnectedCallback
attributeChanged attributedChangedCallback
Hybird elements

这种方式主要是为了兼容Polymer 1.x。可以使用Class-based方式改写。详见Gesture Events。

自定义属性(properties)

自定义属性主要在properties getter中定义。可以直接传一个字符串。也可以传给他们一个Object。 Object需要包含下面几个属性:

type 数据类型

Type: constructor  Attribute type, used for deserializing from an attribute. Polymer supports deserializing the following types: Boolean, Date, Number, String, Array and Object. You can add support for other types by overriding the element's  _deserializeValue method. Unlike 0.5, the property's type is explicit, specified using the type's constructor. Seeattribute deserialization for more information.

When reflecting a property to an attribute or binding a property to an attribute, the property value is serialized to the attribute. By default, values are serialized according to value's current type, regardless of the property's type value:

String No serialization required.
Date or Number Serialized using toString. Boolean Results in a non-valued attribute to be either set (true) or removed (false). Array or Object Serialized using  JSON.stringify.

ArrayObject需要写成JSON的形式,Date需要写成任何符合Date解析形式的String。

可以重写属性序列化函数: _serializeValue method.

_serializeValue(value) { 
  if (value instanceof MyCustomType) { 
    return value.toString(); 
  }
  return super._serializeValue(value);
} 
value 默认值

Type: boolean, number, string or function. Default value for the property. If value is a function, the function is invoked and the return value is used as the default value of the property. If the default value should be an array or object unique to the instance, create the array or object inside a function. See Configuring default property values for more information

跟Vue一样,如果一个属性的默认值是以Array或者Object,那么所有该元素的实例的默认值都共享一个变量。如果要使每个元素拥有一份完全独立的拷贝的话,需要在这个值外面包一个函数。

class XCustom extends Polymer.Element { 
static get properties() { 
    return { 
      mode: { 
        type: String, 
        value: 'auto' 
      }, 
      data: { 
        type: Object, 
        notify: true, 
        value: function() { return {}; } 
      } 
    } 
}
reflectToAttribute

Type: boolean Set to true to cause the corresponding attribute to be set on the host node when the property value changes. If the property value is Boolean, the attribute is created as a standard HTML boolean attribute (set if true, not set if false). For other property types, the attribute value is a string representation of the property value. Equivalent to reflect in Polymer 0.5. See Reflecting properties to attributes for more information

值的变化是否同步更新DOM上面的attr

For a Boolean property to be configurable from markup, it must default to false. If it defaults to true, you cannot set it to false from markup, since the presence of the attribute, with or without a value, equates to true. This is the standard behavior for attributes in the web platform. If this behavior doesn't fit your use case, you can use a string-valued or number-valued attribute instead.

Boolean类型的属性只能把默认值设为false,因为标准的HTML属性行为(存在为true,不存在为false),如果必须把默认值设为true,可以用String或者Number类型属性代替。

readOnly

Type: boolean If true, the property can't be set directly by assignment or data binding.

notify

Type: boolean If true, the property is available for two-way data binding. In addition, an event, property-name-changed is fired whenever the property changes

DOM上面的属性变化是否调用回调(反向绑定)属性this.firstName的变化会触发first-name-changed事件。这些事件被用在了双向绑定系统,在外部代码里面我们可以直接使用addEventListener有点像Java里面的约定大于配置的思想,这样能使代码变得简单易懂

computed

Type: string The value is interpreted as a method name and argument list. The method is invoked to calculate the value whenever any of the argument values changes. Computed properties are always read-only. See Computed properties for more information

传入一个包含方法名和参数列表的字符串,参数必须readOnly,将该方法调用参数运行的结果作为该项的值

observer

Type: string The value is interpreted as a method name to be invoked when the property value changes. Note that unlike in 0.5, property change handlers must be registered explicitly. The propertyNameChanged method will not be invoked automatically. See Property change callbacks (observers) for more information

传入一个方法名称,当值发生变化时自动执行该方法

其他

  1. 隐式声明属性: if you add it to a data binding or add it as a dependency of an observer, computed property, or computed binding.
  2. Priavte和Protected属性:分别用__prop和_prop表示。

Polymer 2.0 文档笔记(2) ShadowDOM

ShadowDOM API

var div = document.createElement('div'); 
var shadowRoot = div.attachShadow({mode: 'open'}); 
shadowRoot.innerHTML = '<h1>Hello Shadow DOM</h1>';

ShadowDOM Composition

主要概念:

  • LightDOM: 元素的实际子孙节点,浏览器不会对LightDOM做任何的修改和移动。但是渲染的时候会渲染到相对于的slot节点之下,如果找不到对应的slot节点,则不会渲染。
  • ShadowDOM: 不解释。。。
  • Slot: slot标签是LightDOM插入到ShadowDOM中的标记。可以设置name属性来匹配对应的LightDOM。Slot标签不会渲染,但是还是会存在(即能够参与事件传递)。另外如果一个LightDOM找不到匹配的slot插入点,则改LightDOM也不会被渲染。
  • Flatterned Tree: LightDOM通过ShadowDOM里面的slot标记合并在一起(flattern)的抽象的DOM树(devTools中不可见),是最后浏览器实际用来渲染的DOM树。

需要注意的是:

Slots can only be selected explicitly, by slot name. It's impossible to select content implicitly, based on a tag name or an arbitrary selector like :not(.header).

slot只能用slot name来区分、选中。不能用其他任意的css选择器。

各个slot之间的区域是互斥的,而且 只能选择top-level元素 (包括后面的::slotted(selector)选择器)。

<!-- shadow DOM v1 template -->
<template>
  <!-- this slot gets any top-level nodes that don't have slot
       attributes. -->
  <slot></slot>
  <!-- the following insertion point gets any top-level nodes that have
       slot="special". -->
  <slot name="special"></slot>
  <!-- top-level nodes that have slot attributes with other values
       don't get distributed at all. -->
</template>

Fallback content

实际上就是可以在模版的slot里面写东西,作为一个默认值

Mulit-level distribution

多级slot嵌套。flatterning的时候规则是从外向里, 因此父节点的slot下面的lightDOM会渲染在子节点的LightDOM之下。

Slot APIs && Observe added and removed children

  • HTMLElement.assignedSlot
  • HTMLSlotElement.assignedNodes
  • HTMLSlotElement.slotchange(event)
  • Polymer.FlattenedNodesObserver
  • Polymer.FlattenedNodesObserver.getFlattenedNodes(node)
  • new Polymer.FlattenedNodesObserver(this.$.slot,(info)=>{})

详情参考https://www.polymer-project.org/2.0/docs/devguide/shadow-dom

Shadow DOM polyfills

因为shadowDOM不是每个浏览器都支持,所以使用了webcomponents.js中的shadyDOM和shadyCSS两个polyfills。

How the polyfills work

The polyfills use a combination of techniques to emulate shadow DOM: Shady DOM: Maintains the logical divisions of shadow tree and descendant tree internally, so children added to the light DOM or shadow DOM render correctly. Patches DOM APIs on affected elements in order to emulate the native shadow DOM APIs. Shady CSS: Provides style encapsulation by adding classes to shadow DOM children and rewriting style rules so that they apply to the correct scope.

ShadyDOM

ShadyDOM主要原理是patch原生DOM API来提供跟native shadowDOM一致的接口。在内存中维护了一个保存LightDOM的children树和一个保存ShadowDOM的ShadowRoot树。但是实际上元素下面就是一颗渲染好的flatterned树。因为这个是就是实际上的用来渲染的树,所以不存在slot,因此shadyDOM polyfills方案里面的slot元素不参与事件传递。

ShadyCSS

ShadyCSS主要提供了原生ShadowDOM规则的支持和两个cssnext特性:CSS custom propertiescustom property mixins. Scoped CSS特性的实现原理跟Vue相似:对元素的ShadowDOM添加class,并重写CSS的匹配规则。

ShadowDOM styling

ShadowDOM样式三大原则:

  1. 外层的样式不会对内层样式进行匹配。
  2. 内层样式不会影响外层样式。
  3. 可继承的css属性(例如:color)等,内层可以照样继承自外层。

两个特殊伪类: - 选择ShadowDOM的根节点节点:host/:host(selector)

#shadow-root
  <style>
    /* custom elements default to display: inline */
    :host {
      display: block;
    }
    /* set a special background when the host element
       has the .warning class */
    :host(.warning) {
      background-color: red;
    }
  </style>

  • 选择分布式节点——::slotted()
  • slotted()选择器右边不能写任何其他选择器。
  • slotted(selector) 里面的selector只能是简单选择器,并且只能选择顶级元素。
  • slotted(*) 选择默认的LightDOM(不包含有name值的slot)
  • slotted(slot=tkw) 选择name为tkw的LightDOM
<dom-module>
<template>
  <style>
    ::slotted(img){ 
      border-radius: 103%;
    }
  </style>
  <div><slot></slot></div>
</template>
<script>
  // define the element's class element
  class XFoo extends Polymer.Element {
    // 'is' getter, return the tag name which is lowercased. required.
    static get is(){
      return 'x-foo';
    }
    // Define the properties.
    static get properties() {}
    // Element class can define custom element reactions
    constructor() { super(); }
    connectedCallback() {
      super.connectedCallback();
      console.log('x-foo created!');
    }
  }

  window.customElements.define(XFoo.is, XFoo);
</script>
</dom-module>

<x-foo>
  <h1>A logo</h1>
  <img />
</x-foo>

Share styles between elements

<dom-module id="my-style-module"> 
  <template> 
    <style> 
      <!-- Styles to share go here! --> 
    </style> 
  </template> 
</dom-module>

<!-- another element -->
<dom-module id="new-element"> 
  <template> 
    <style include="my-style-module"> 
      <!-- Any additional styles go here --> 
    </style> 
    <!-- The rest of your element template goes here --> 
  </template> 
</dom-module>

custom-style

兼容性写法,使用<custom-style>标签包围main document里面的全局<style>能避免在不支持shadowDOM v1规范的浏览器中全局css规则继续在shadowDOM中生效。 注意是避免CSS规则生效,而不是避免CSS样式的继承 custom-style元素不包含在Polymer中,需要引入:

<link rel="import" href="components/polymer/lib/elements/custom-style.html">

CSS custom properties

主要是提供一个支持两个CSS Next语法的扩展版本:

custom properties & var()
  • 可以通过——customvar形式定义变量,然后通过var(--customvar,[defaultvar])取值。
  • 通过这个特性我们可以暴露出一些可配置的变量,然后供组件外部的父元素进行自定义。
  • 与cssnext规范不同的是,cssnext的变量定义必须写在:root{}中,而Polymer则是不能在LightDOM中使用。
p {
  color: var(--paper-red-500);
}
paper-checkbox {
  --paper-checkbox-checked-color: var(--paper-red-500);
}

custom properties set & @apply

Polymer默认不支持自定义属性集合,需要手动引入:

<!-- import CSS mixins polyfill -->
<link rel="import" href="/bower_components/shadycss/apply-shim.html">
相当于cssnext对应的语法,但是同样可以在任何css Rule下使用。
<dom-module id="my-element"> 
  <template> 
    <style> 
      /* Apply custom theme to toolbars */ 
      :host { 
        --my-toolbar-theme: { 
          background-color: green; 
          border-radius: 4px; 
          border: 1px solid gray; 
        }; 
        --my-toolbar-title-theme: { 
          color: green; 
        }; 
      } 
      /* Make only toolbars with the .warning class red and bold */ 
      .warning { 
        --my-toolbar-title-theme: { 
          color: red; 
          font-weight: bold; 
        }; 
      } 
    </style> 
    <my-toolbar title="This one is green."></my-toolbar> 
    <my-toolbar title="This one is green too."></my-toolbar> 
    <my-toolbar class="warning" title="This one is red."></my-toolbar> 
  </template> 
  <script> 
    class MyElement extends Polymer.Element { 
      static get is() { 
        return "my-element"; 
      } 
    } 
    customElements.define(MyElement.is, MyElement); 
  </script> 
</dom-module> 

<dom-module id="my-toolbar"> 
  <template> 
    <style> 
      :host { 
        padding: 4px; 
        background-color: gray; 
        /* apply a mixin */ 
        @apply --my-toolbar-theme; 
      } 
      .title { 
        @apply --my-toolbar-title-theme; 
      } 
    </style> 
    <span class="title">{{title}}</span> 
  </template> 
  ... 
</dom-module>

CSS Custom Property API

updateStyles

动态更改css自定义属性的值。

<dom-module id="x-custom">
  <template>
    <style>
      :host {
        --my-toolbar-color: red;
      }
    </style>
    <my-toolbar>My awesome app</my-toolbar>
    <button on-tap="changeTheme">Change theme</button>
  </template>
  <script>
    class XCustom extends Polymer.Element {
      static get is() {
        return "x-custom";
      }
      static get changeTheme() {
        return function() {
        this.updateStyles({
          '--my-toolbar-color': 'blue',
        });
      }
    }
    customElements.define(XCustom.is, XCustom);
  </script>
</dom-module>
注意当外层元素或者被继承元素里面已经定义了一个变量,那么当前再定义这个变量是无效的,需要手动调用该方法。这个行为类似于Less

getComputedStyle(ShadyCSS.getComputedStyle)

获得当前自定义属性的值(需要区分原生和Polyfill)

if (ShadyCSS) { 
  style = ShadyCSS.getComputedStyleValue('--something'); 
} else { 
  style = getComputedStyle(this, '--something'); 
} 

DOM Templating

By default, adding a DOM template to an element causes Polymer to create a shadow root for the element and clone the template into the shadow tree.

DOMTemplate是通过clone操作添加到shadow tree里面去的。 有三种方式定义一个DOM Template:

template标签

直接将模板写在<template>标签里面, 是最直接的方式、最常见的方式。

<dom-module id="x-foo"> 
<template>I am x-foo!</template> 
<script> 
    class XFoo extends Polymer.Element { 
      static get is() { return  'x-foo' } 
    } 
    customElements.define(XFoo.is, XFoo); 
</script> 
</dom-module> 

String template

字符串模板

class MyElement extends Polymer.Element { 

static get template() { 
    return `<style>:host { color: blue; }</style> 
       <h2>String template</h2> 
       <div>I've got a string template!</div>` 
  } 
} 
customElements.define('my-element', MyElement); 

Retrieve or generate your own template element

通过继承或手动实现template getter获得模板. 注意: 1. 不要对父类的template直接修改,应该先拷贝一份出来。 2. 如果需要做一些耗资源的操作,应该对你修改的template进行缓存,以免重复调用。

(function() { 
  let memoizedTemplate; 

class MyExtension extends MySuperClass { 
    static get template() { 
      if (!memoizedTemplate) { 
        // create a clone of superclass template (`true` = "deep" clone) 
        memoizedTemplate = MySuperClass.template.cloneNode(true); 
        // add a node to the template. 
        let div = document.createElement('div'); 
        div.textContent = 'Hello from an extended template.' 
        memoizedTemplate.content.appendChild(div); 
      } 
      return memoizedTemplate; 
    } 
  } 

})(); 

URLs in template

默认对于所有从其他文件里面引入的组件里面元素所包含的链接Polymer是不做处理的,所有的相对路径的资源最后都是相对于主文档(main document)的路径。 但是我们可以使用下面两个特殊的标记

importPath

A static getter on the element class that defaults to the element HTML import document URL and is overridable. It may be useful to override importPath when an element's template is not retrieved from a  or the element is not defined using an HTML import.

一个静态getter函数,默认指向该元素被HTML import时候的URL,也可以被重写。

rootPath

An instance property set to the value of Polymer.rootPath which is globally settable and defaults to the main document URL. It may be useful to set Polymer.rootPath to provide a stable application mount path when using client side routing.

一个实例化的属性,值被设置为Polymer.rootPath。代表着主文档(main document)的URL。

Relative URLs in styles are automatically re-written to be relative to the importPath property. Any URLs outside of a <style> element should be bound using importPath or rootPath where appropriate.

所有style标签里面的URL全部被重写为相对于importPath的路径。除此之外,都需要自己手动添加合适的前缀:

<img src$="[[importPath]]checked.jpg"> 

<a href$="[[rootPath]]users/profile">View profile</a>

Static node map

Polymer builds a static map of node IDs when the element initializes its DOM template, to provide convenient access to frequently used nodes without the need to query for them manually. Any node specified in the element's template with an id is stored on the this.$ hash by id. The this.$ hash is created when the shadow DOM is initialized. In the ready callback, you must call super.ready() before accessing this.$.

这个主要是Polymer提供的一个可以快速访问DOM节点的方式。可以通过this.$[id]来获取拥有对应id的元素/自定义元素。
相当于document.getElementById的升级版类似于react中的this.refs
this.$接口只能在ready回调函数的super.ready()之后被调用。
动态创建的节点(dom-repeat\ dom-if)并不包含在this.$集合里,但是还是可以用标准的querySelector方法获取。

<dom-module id="x-custom"> 

<template> 
    Hello World from <span id="name"></span>! 
  </template> 

<script> 
    class MyElement extends Polymer.Element { 
      static get is() { return  'x-custom' } 
      ready() { 
        super.ready(); 
        this.$.name.textContent = this.tagName; 
      } 
    } 
  </script> 

</dom-module>

Polymer 2.0 文档笔记(3) Events

Normal Events

Polymer elements can use the standard DOM APIs for creating, dispatching, and listening for events. Polymer also provides annotated event listeners, which allow you to specify event listeners declaratively as part of the element's DOM template.

Add annotated event listeners

这个其实就是在标签上使用on-event属性。

<dom-module id="x-custom"> 
  <template> 
    <button on-click="handleClick">Kick Me</button> 
  </template> 
  <script> 
    class XCustom extends Polymer.Element { 

static get is() {return 'x-custom'} 

handleClick() { 
console.log('Ow!'); 
} 
} 
customElements.define(XCustom.is, XCustom); 
     </script> 
</dom-module> 
需要注意的有:

  1. 如果添加了手势事件则应当使用on-tap事件代替on-click事件,提供更好的移动浏览器支持。
  2. 因为html属性的限制,所有的事件名称都将转化为小写字母。(To avoid confusion, always use lowercase event names. )

Add and remove listeners imperatively

直接使用原生的addEventListenerremoveEventListener

Fire custom events

直接使用原生的CustomEventdispatchEvent

Event Retargeting

  1. 为了保持ShadowDOM的隐蔽性。元素内部ShadowDOM触发的一些事件在网上传播时会将target重定向到当前元素。
  2. event.composedPath() : 包含事件传递过程中所经过的nodes路径列表。 

自定义的事件默认无法穿透ShadowDOM边界。可以如下设置:

var event = new CustomEvent('my-event', {bubbles: true, composed: true});

Gesture Events

手势事件的支持是基于Legacy Element的,如果使用Polymer2的class风格定义需要使用mixin:Polymer.GestureEventListeners

<link rel="import" href="polymer/lib/mixins/gesture-event-listeners.html"> 

<script> 
    class TestEvent extends Polymer.GestureEventListeners(Polymer.Element) { 
      ... 
</script> 
注册事件也有两种方式: 1. 支持on-event形式的声明方式 2. 显示定义的方式:Polymer.Gestures.addListener(this, 'tap', e => this.tapHandler(e));

包含以下几种手势事件,包含在e.detail中。

  • down—finger/button went down
  • x—clientX coordinate for event
  • y—clientY coordinate for event
  • sourceEvent—the original DOM event that caused the down action
  • up—finger/button went up
  • x—clientX coordinate for event
  • y—clientY coordinate for event
  • sourceEvent—the original DOM event that caused the up action
  • tap—down & up occurred
  • x—clientX coordinate for event
  • y—clientY coordinate for event
  • sourceEvent—the original DOM event that caused the tap action
  • track—moving while finger/button is down
  • state—a string indicating the tracking state:
    • start—fired when tracking is first detected (finger/button down and moved past a pre-set distance threshold)
    • track—fired while tracking
    • end—fired when tracking ends
  • x—clientX coordinate for event
  • y—clientY coordinate for event
  • dx—change in pixels horizontally since the first track event
  • dy—change in pixels vertically since the first track event
  • ddx—change in pixels horizontally since last track event
  • ddy—change in pixels vertically since last track event
  • hover()-a function that may be called to determine the element currently being hovered

Polymer 2.0 文档笔记(4) Data System

Polymer提供观察函数、计算属性、数据绑定三大模型功能:

  • Observers Callbacks invoked when data changes.
  • Computed properties Virtual properties computed based on other properties, and recomputed when the input data changes.
  • Data bindings Annotations that update the properties, attributes, or text content of a DOM node when data changes.

```html

```

Observable Change

The data system is based on paths, not objects, where a path represents a property or subproperty relative to the host element.

Polymer的数据系统是基于数据的 路径 之上的。并不是实际的对象。

可观察到的变化指的是Polymer里面存在一个相关的路径指向这个数据的变化。因此,直接改变一个object、array等引用对象不会被观察到。

this.address.street = 'Elm Street'
改变address.street并不能被address上的观察器捕捉到变化

Mutating objects and arrays observably

使用Polymer提供的方法:

// 改变object
this.set('address.street','Half Moon Street') 
// 改变array
this.set('users.3',{name:'Hawking'})
this.push('users',{name: 'Maturin'}) 
this.pop('users')
this.unshift('users',{name: 'Martin'})
this.shift('users')
this.splice('users',3,1,{name:'Hawking'})

// 批量更新
this.setProperties({item:'Orange', count:12},true) //setReadOnly:true 代表需要设置ready-only的属性
//延迟统一更新
this.notifyPath('address.street') 
this.notifySplices('users') //only array

// 获得路径代表的属性值
var value = this.get('address.street') 
 //获得users[2]的值
var item = this.get(['users',2])

Polymer performs dirty checking for objects and arrays using object equality. It doesn't produce any property effects if the value at the specified path hasn't changed.

notifyPath方式需要提供具体属性的路径,而不应该是Object或Array,因为Polymer直接使用数据引用地址进行比较。如果一定需要观察它们,Polymer提供了三种解决方案:

  1. 使用Immutable之类的库,或者每次改变Object和Array里面的值的时候都先克隆出一个副本,在克隆的副本上修改然后把路径指向克隆副本。
  2. 使用mixin Polymer.MutableData可以禁止Polymer的对object和array的脏检查(dirty check)
  3. 使用mixin Polymer.OptionalMutableData可以在标签上添加一个bool属性mutable,代表是否对object或array开启脏检测

Data Paths

A data path is a series of path segments. In most cases, each path segment is a property name. The data APIs accept two kinds of paths: - A string, with path segments separated by dots. - An array of strings, where each array element is either a path segment or a dotted path.

数据路径可以是一个路径字符串,也可以是一个包含路径字符串的数组,比如下面三行全部代表同一个路径:

"one.two.three"
["one", "two", "three"]
["one.two", "three"]

data-path

Polymer doesn't automatically know that these properties refer to the same object.<address-card> makes a change to the object, no property effects are invoked on <user-profile> For data changes to flow from one element to another, the elements must be connected with a data binding.

Polymer是以路径来监听数据变化的。所以,就算两个路径实际上都指向同一个对象,他俩也不会联动。需要对这两个路径进行链接操作。

Linking paths with data bindings

data binding is the only way to link paths on different elements

<dom-module id="user-profile">
  <template><address-card
        address="{{primaryAddress}}"></address-card>
  </template></dom-module>
链接两个路径之后的示意图: data-binding

The <user-profile> element has a property primaryAddress that refers to a JavaScript object. The <address-card> element has a property address that refers to the same object. The data binding connects the path "primaryAddress" on <user-profile> to the path "address" on <address-card>

Data binding scope

Paths are relative to the current data binding scope. The topmost scope for any element is the element's properties. Certain data binding helper elements (like template repeaters) introduce new, nested scopes. For observers and computed properties, the scope is always the element's properties.

  1. 上面所说的路径都是相对于当前数据绑定(data-binding scope)定的。
  2. 最外层的域就是当前元素的本身(this),但一些数据绑定辅助元素(比如: template repeaters)可以创建新的子域。
  3. 观察函数(observers)和计算属性computed properties的域永远都是当前元素本身(this)

Special paths

  1. 通配路径(Wildcard paths)-可以使用通配符*来表示当前路径下的所有子路径的任何变化。比如users指向一个数组,users.*代表该数组的所有变化
  2. splices- 可以用在数组路径后面,代表数组任何添加、删除的变化
  3. 数组路径后面接下标代表数组里面对应的项,比如users.12

注意:

  1. 通配符只能用在observers、computed properties里面的路径中,不能用在数据绑定里
  2. 观察splices路径时,事件参数中只提供当前数组发生变化的元素组成的子数组,所以在一般情况下通配符路径比splices路径要实用

Two paths referencing the same object

如果两条路径都指向同一个对象,如下图,需要使用linkPaths方法将它们关联起来。 two-paths 注意:

  1. 两条路径必须在同一个data scope下
  2. 如果需要接触两条路径的关联,使用unlinkPaths, 该函数只接受linkPaths调用时的第一条路径

Data flow

Polymer implements the mediator pattern, where a host element manages data flow between itself and its local DOM nodes. When two elements are connected with a data binding, data changes can flow downward, from host to target, upward, from target to host, or both ways. When two elements in the local DOM are bound to the same property data appears to flow from one element to the other, but this flow is mediated by the host. A change made by one element propagates up to the host, then the host propagates the change down to the second element.

Polymer的数据流是一个中间人模型。任何存在数据绑定的两个元素直接的数据流都不是表面上的直接传递的,而是先向上传递(upward)到host元素再向下传递(downward)到目标元素。

Data flow is synchronous. When your code makes an observable change, all of the data flow and property effects from that change occur before the next line of your JavaScript is executed, unless an element explicitly defers action (for example, by calling an asynchronous method).

Polymer的数据传递是同步的,除非调用一个外部异步函数

How data flow is controlled

数据流的方向主要由两个地方控制:数据绑定方式和属性配置项。

数据绑定主要有两种方式: - Automatic :双向绑定,包括向上(upward,target to host)和向下(downward.host to target),使用 \{\{ \}\}

<my-input value="{{name}}"></my-input>

  • One-way :单向绑定,仅向下(downward.host to target),使用[[ ]]
    <name-tag name="[[name]]"></name-tag>
    
    属性配置项具体有两项:
  • notify 允许数据向上(upward,target to host)传递,默认为false
  • readOnly 禁止数据向下(downward.host to target)传递到当前组件,默认false

属性配置实例:

properties: {
  // default prop, read/write, non-notifying.
  basicProp: {
  },
  // read/write, notifying
  notifyingProp: {
    notify: true
  },
  // read-only, notifying
  fancyProp: {
    readOnly: true,
    notify: true
  }
}
注意: 1. 当使用单向绑定的时候,notify配置项无效。 2. 当单向绑定且readOnly:true时,将没有任何数据流

Property configuration only affects the property itself, not subproperties. In particular, binding a property that's an object or array creates shared data between the host and target element. There's no way to prevent either element from mutating a shared object or array.

属性配置不能继承

Data flow examples

参考:Data flow examples

Upward and downward data flow

Since the host element manages data flow, it can directly interact with the target element. The host propagates data downward by setting the target element’s properties or invoking its methods.

host元素可以直接通过设置target元素属性或调用回调函数等方法将数据向下传递给target元素

An element, host-element connected to an element, target-element by an arrow labeled 1.

Polymer elements use events to propagate data upward. The target element fires a non-bubbling event when an observable change occurs.

当数据是双向绑定的时候,target元素通过触发一个non-bubbling的change事件来将数据传递给监听这个事件的host元素,host元素监听到事件后对数据变化做出响应改变数据绑定模型(可能影响相邻元素)并触发另外一个change事件向上传播。

当数据是单向绑定的时候,host元素不会监听target元素的change事件,因此数据无法向上传递。

An element, target-element connected to an element, host-element by an arrow labeled 1. An arrow labeled 2 connects from the host element back to itself.

Data flow for objects and arrays

For object and array properties, data flow is a little more complicated. An object or array can be referenced by multiple elements, and there's no way to prevent one element from mutating a shared array or changing a subproperty of an object. As a result, Polymer treats the contents of arrays and objects as always being available for two- way binding. That is:

  • Data updates always flow downwards, even if the target property is marked read-only.
  • Change events for upward data flow are always fired, even if the target property is not marked as notifying.

Since one-way binding annotations don't create an event listener, they prevent these change notifications from being propagated to the host element.

对于objects和arrays绑定的数据流非常复杂,不得已Polymer将忽略关于它们所有的readyOnlynotify配置项,并将它们hardcode为true,单项绑定和双向绑定不受影响。

Change notification events

当一个元素某路径比如property发生变化,则会响应的触发一个property-changed的通知事件。 事件内容根据路径类型相关:

  1. 属性变化: 新值将储存detail.value
  2. 子属性变化:子属性的路径将会储存在detail.path中,新值将会储存在detail.value中。
  3. 数组变化:变化路径将会储存在detail.path中(比如: myArray.splices),新值将会被储存在detail.value中。

Don't stop propagation on change notification events. To avoid creating and discarding event objects, Polymer uses cached event objects for change notifications. Calling stopPropagation on a change notification event prevents all future events for that property. Change notification events don't bubble, so there should be no reason to stop propagation.

注意: 不要在通知事件里面使用stopPropagation

Custom change notification events

一些Native元素比如<input>并不存在变化通知事件,因此也不能将数据向上传递。可以通过下面的方式手动自定义一个变化通知事件:

<input value="{{firstName::change}}">

In this example, the firstName property is bound to the input's value property. Whenever the input element fires its change event, Polymer updates the firstName property to match the input value, and invokes any associated property effects. The contents of the event aren't important.

This technique is especially useful for native input elements, but can be used to provide two-way binding for any non-Polymer component that exposes a property and fires an event when the property changes.

Property effects

当属性变化的时候,下面的任务会依次执行:

  1. 重写计算受到影响的属性的值 - 此步将会更新计算属性(computed properties)
  2. 更新数据绑定
  3. 更新host元素上面的html属性
  4. 执行观察事件observers
  5. 触发变化通知事件

Polymer 2.0 文档笔记(5) Observers && Computed Properites

有两种监听器:

  1. 简单监听器,只能监听单一的property
  2. 复杂监听器:可以监听一到多个property

每个监听器都有一个或多个 依赖 ,当依赖发生可监听的变化是,监听方法就会被调用。

Computed properties are virtual properties based on one or more pieces of the element's data. A computed property is generated by a computing function—essentially, a complex observer that returns a value.

计算属性顾名思义,是由一个返回某个值的计算函数算出来的属性。

Observers and element initialization

只有当元素初始值加载完毕并且至少有一个依赖的属性被定义(undefined => somevalue),监听器才会被调用。

Observers are synchronous

  1. 监听器的执行也是同步的
  2. 如果监听器调用比较频繁,影响效率,可以使用Polymer.Async库来将它放到异步任务队列里面去。
  3. Polymer不保证异步执行的监听器所传的参数是否是最新的

Simple observers

简单监听器需要在properties中需要监听的property里面注册。监听函数里面不能依赖其他任何property

Simple observers are fired the first time the property becomes defined (!= undefined), and on every change thereafter, even if the property becomes undefined.

简单监听器第一次触发在property被定义后(undefined => somevalue),在此之后,任何property变化(包括重新变回undefined)都会被调用。

Simple observers only fire when the property itself changes. They don't fire on subproperty changes, or array mutation.

简单监听器只会响应当前property的可观察变化(observable changes)

You specify an observer method by name. The host element must have a method with that name.

简单监听器的定义和实现(函数本身)必须一一对应,简单监听器的实现可以在当前class里面也可以继承自superclass也可以添加自mixin。

The observer method receives the new and old values of the property as arguments.

简单监听器接受两个参数: oldValue,newValue

Observe a property

class XCustom extends Polymer.Element {

  static get is() {return 'x-custom'; }

  static get properties() {
    return {
      active: {
        type: Boolean,
        // Observer method identified by name
        observer: '_activeChanged'
      }
    }
  }

  // Observer method defined as a class method
  _activeChanged(newValue, oldValue) {
    this.toggleClass('highlight', newValue);
  }
}

Complex observers

复杂监听器需要在this.observers数组里面注册,复杂监听器可以监听多条路径(依赖)

static get observers() {
  return [
    // Observer method name, followed by a list of dependencies, in parenthesis
    'userListChanged(users.*, filter)'
  ]
}
注册函数里面的参数有如下几种:

  • 一个简单的属性路径 (firstName).

  • 一个简单的子属性路径 (address.street).

  • 一个数组的变化结果路径 (users.splices).

  • 一个包含通配符的路径 (users.*).

函数被调用时所得到的参数依据监听的路径种类不同而不同:

  • 简单的属性或子属性,传参为新值
  • 数组变化路径,传参为描述变化详情的 变化记录 对象
  • 通配符路径,传参为 变化记录 对象以及变化的详细路径

Note that any of the arguments can be undefined when the observer is called.

监听函数中每次调用,任何一个参数都有可能是undefined

复杂监听器的参数只有新值没有旧值

Observe changes to multiple properties

class XCustom extends Polymer.Element {

  static get is() {return 'x-custom'; }

  static get properties() {
    return {
        preload: Boolean,
        src: String,
        size: String
    }
  }

  // Each item of observers array is a method name followed by
  // a comma-separated list of one or more dependencies.
  static get observers() {
    return [
        'updateImage(preload, src, size)'
    ]
  }

  // Each method referenced in observers must be defined in
  // element prototype. The arguments to the method are new value
  // of each dependency, and may be undefined.
  updateImage(preload, src, size) {
    // ... do work using dependent values
  }
}

Observe array mutations

数组变化路径的参数change record是一个对象,包含一个indexSplices数组,数组中的每一项表示一处变更记录,包含下面信息:

  • index. 变更其实的地方
  • removed. 被删除的数据
  • addedCount. 插入的数据长度
  • object: 一个指向新数据的数组(不是拷贝) //A reference to the array in question. 这句话不懂什么意思
  • type: 一个值为'splice'的字符串

注意: change record也许会为undefined

class XCustom extends Polymer.Element {

  static get is() {return 'x-custom'; }

  static get properties() {
    return {
      users: {
        type: Array,
        value: function() {
          return [];
        }
      }
    }
  }

  // Observe changes to the users array
  static get observers() {
    return [
      'usersAddedOrRemoved(users.splices)'
    ]
  }


  // For an array mutation dependency, the corresponding argument is a change record
  usersAddedOrRemoved(changeRecord) {
    if (changeRecord) {
      changeRecord.indexSplices.forEach(function(s) {
        s.removed.forEach(function(user) {
          console.log(user.name + ' was removed');
        });
        for (var i=0; i<s.addedCount; i++) {
          var index = s.index + i;
          var newUser = s.object[index];
          console.log('User ' + newUser.name + ' added at index ' + index);
        }
      }, this);
    }
  }

  ready() {
    super.ready();
    this.push('users', {name: "Jack Aubrey"});
  }
}

customElements.define(XCustom.is, XCustom);

通配符路径的参数是一个change record对象,包含:

  • path 监听器监听的路径
  • value 新值
  • base 通配符之前的路径所代表的对象

如果通配符路径监听到的是数组的变化,那么

  1. path则是该数组的路径(不包括.splices
  2. value里面则会包含一个indexSplices
<dom-module id="x-custom">
  <template>
    <input value="{{user.name.first::input}}"
           placeholder="First Name">
    <input value="{{user.name.last::input}}"
           placeholder="Last Name">
  </template>
  <script>
    class XCustom extends Polymer.Element {

      static get is() { return 'x-custom'; }

      static get properties() {
        return {
          user: {
            type: Object,
            value: function() {
              return {'name':{}};
            }
          }
        }
      }

      static get observers() {
        return [
            'userNameChanged(user.name.*)'
        ]
      }

      userNameChanged(changeRecord) {
        console.log('path: ' + changeRecord.path);
        console.log('value: ' + changeRecord.value);
      }
    }

    customElements.define(XCustom.is, XCustom);
  </script>
</dom-module>

Identify all dependencies

Observers shouldn't rely on any properties, sub-properties, or paths other than those listed as dependencies of the observer. This creates "hidden" dependencies

监听器不能依赖任何其他未被注册过的路径,否则: 1. 不能保证该路径是否已经初始化完成 2. 当该路径的属性发生变化时,无法触发当前监听器

For example:

static get properties() {
  return {
    firstName: {
      type: String,
      observer: 'nameChanged'
    },
    lastName: {
      type: String
    }
  }
}

// WARNING: ANTI-PATTERN! DO NOT USE
nameChanged(newFirstName, oldFirstName) {
  // Not invoked when this.lastName changes
  var fullName = newFirstName + ' ' + this.lastName;
  // ...
}

Note that Polymer doesn't guarantee that properties are initialized in any particular order.

Polymer不能保证属性之间的初始化顺序。

Computed properties

Computed properties are virtual properties computed on the basis of one or more paths. The computing function for a computed property follows the same rules as a complex observer, except that it returns a value, which is used as the value of the computed property.

计算属性定义跟复杂监听器类似,但是计算属性的计算函数需要返回一个值。

Define a computed property

计算属性需要在properties里面的property配置对象中使用computed键注册,注册语法跟复杂监听器一致。

<dom-module id="x-custom">

  <template>
    My name is <span>{{fullName}}</span>
  </template>

  <script>
    class XCustom extends Polymer.Element {

      static get is() { return 'x-custom'; }

      static get properties() {
        return {
          first: String,

          last: String,

          fullName: {
            type: String,
            // when `first` or `last` changes `computeFullName` is called once
            // and the value it returns is stored as `fullName`
            computed: 'computeFullName(first, last)'
          }
        }
      }

      computeFullName(first, last) {
        return first + ' ' + last;
      }
    }

    customElements.define(XCustom.is, XCustom);
  </script>

</dom-module>

另外见 : Computed Bindings

Dynamic observer methods

If the observer method is declared in the properties object, the method is considered dynamic: the method itself may change during runtime. A dynamic method is considered an extra dependency of the observer, so the observer re-runs if the method itself changes. For example:

如果一个监听器或者计算属性的方法被定义在了properties里面,那么我们可以动态的对这个方法进行覆盖、重写。 当方法发生变化的时候,新的简单监听器或计算属性会被立即触发或者被更新。

class NameCard extends Polymer.Element {

  static get is() {
    return 'name-card'
  }

  static get properties() {
    return {
      // Override default format by assigning a new formatter
      // function
      formatter: {
        type: Function
      },
      formattedName: {
        computed: 'formatter(name.title, name.first, name.last)'
      },
      name: {
        type: Object,
        value() {
         return { title: "", first: "", last: "" };
        }
      }
    }
  }

  constructor() {
    super();
    this.formatter = this.defaultFormatter;
  }

  defaultFormatter(title, first, last) {
    return `${title} ${first} ${last}`
  }
}
customElements.define('name-card', NameCard);

nameCard.name = { title: 'Admiral', first: 'Grace', last: 'Hopper'}
console.log(nameCard.formattedName); // Admiral Grace Hopper
nameCard.formatter = function(title, first, last) {
  return `${last}, ${first}`
}
console.log(nameCard.formattedName); // Hopper, Grace
计算属性formattedName的方法formatter发生变化的时候,尽管依赖name没有变化,但是该属性还是触发更新了。

因为动态监听器方法出于properties里面,因此会被看作一个依赖,一旦这个方法被定义,监听器就在初始化的时候触发,尽管其他依赖都没有被定义。

Add observers and computed properties dynamically

In some cases, you may want to add an observer or computed property dynamically. A set of instance methods allow you to add a simple observer, complex observer, or computed property to the current element instance.

使用js API来动态添加计算属性或监听器 - _createPropertyObserver - _createMethodObserver - _createComputedProperty

Add a simple observer dynamically

this._observedPropertyChanged = (newVal) => { console.log('observedProperty changed to ' + newVal); };
this._createPropertyObserver('observedProperty', '_observedPropertyChanged', true);
第三个参数代表这个方法(_observedPropertyChanged)是否应该被看作一个依赖

Add a complex observer dynamically

this._createMethodObserver('_observeSeveralProperties(prop1,prop2,prop3)', true);

第三个参数代表这个方法(_observeSeveralProperties)是否应该被看作一个依赖

Add a computed property dynamically

this._createComputedProperty('newProperty', '_computeNewProperty(prop1,prop2)', true);
第三个参数代表这个方法(_computeNewProperty)是否应该被看作一个依赖