<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>绿色记忆 &#187; HTML</title>
	<atom:link href="https://blog.gmem.cc/category/work/web/html/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.gmem.cc</link>
	<description></description>
	<lastBuildDate>Fri, 17 Apr 2026 09:20:32 +0000</lastBuildDate>
	<language>en-US</language>
		<sy:updatePeriod>hourly</sy:updatePeriod>
		<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.9.14</generator>
	<item>
		<title>HTML5视频监控技术预研</title>
		<link>https://blog.gmem.cc/research-on-html5-video-surveillance</link>
		<comments>https://blog.gmem.cc/research-on-html5-video-surveillance#comments</comments>
		<pubDate>Mon, 28 Aug 2017 05:49:57 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[C++]]></category>
		<category><![CDATA[Graphic]]></category>
		<category><![CDATA[HTML]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[JSMpeg]]></category>
		<category><![CDATA[MSE]]></category>
		<category><![CDATA[Multimedia]]></category>
		<category><![CDATA[WebRTC]]></category>
		<category><![CDATA[视频监控]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=15526</guid>
		<description><![CDATA[<p>引言 安防类项目中通常都有视频监控方面的需求。视频监控客户端主要是Native应用的形式，在Web端需要利用NPAPI、ActiveX之类的插件技术实现。 但是，IE式微，Chrome也放弃了NPAPI，另一方面，监控设备硬件厂商的视频输出格式则逐渐标准化。这让基于开放、标准化接口的Web视频监控成为可能。 本文讨论以HTML5及其衍生技术为基础的B/S架构实时视频监控解决方案。主要包括两方面的内容： 视频编码、流媒体基础知识，以及相关的库、框架的介绍 介绍可以用于视频监控的HTML5特性，例如媒体标签、MSE、WebRTC，以及相关的库、框架 本文仅仅简介若干种备选的解决方案，本站其它文章进行了更加深入的探讨： H.264学习笔记 实时通信协议族 基于Kurento搭建WebRTC服务器 基于Broadway的HTML5视频监控 音视频编码 音频、视频的编码（Codec，压缩）算法有很多，不同浏览器对音视频的编码算法的支持有差异。H264这样的监控设备常用的视频编码格式，主流浏览器都有某种程度的支持。 常见的音频编码算法包括： MP3, Vorbis, AAC；常见的视频编码算法包括： H.264, HEVC, VP8, VP9。 编码后的音频、视频通常被封装在一个比特流容器格式（container）中，这些格式中常见的有： <a class="read-more" href="https://blog.gmem.cc/research-on-html5-video-surveillance">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/research-on-html5-video-surveillance">HTML5视频监控技术预研</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">引言</span></div>
<p>安防类项目中通常都有视频监控方面的需求。视频监控客户端主要是Native应用的形式，在Web端需要利用NPAPI、ActiveX之类的插件技术实现。</p>
<p>但是，IE式微，Chrome也放弃了NPAPI，另一方面，监控设备硬件厂商的视频输出格式则逐渐标准化。这让基于开放、标准化接口的Web视频监控成为可能。</p>
<p>本文讨论以HTML5及其衍生技术为基础的B/S架构实时视频监控解决方案。主要包括两方面的内容：</p>
<ol>
<li>视频编码、流媒体基础知识，以及相关的库、框架的介绍</li>
<li>介绍可以用于视频监控的HTML5特性，例如媒体标签、MSE、WebRTC，以及相关的库、框架</li>
</ol>
<p>本文仅仅简介若干种备选的解决方案，本站其它文章进行了更加深入的探讨：</p>
<ol>
<li><a href="/h264-study-note">H.264学习笔记</a></li>
<li><a href="/realtime-communication-protocols">实时通信协议族</a></li>
<li><a href="/webrtc-server-basedon-kurento">基于Kurento搭建WebRTC服务器</a></li>
<li><a href="/html5-vs-with-broadway">基于Broadway的HTML5视频监控</a></li>
</ol>
<div class="blog_h1"><span class="graybg">音视频编码</span></div>
<p>音频、视频的编码（Codec，压缩）算法有很多，不同浏览器对音视频的编码算法的支持有<a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Supported_media_formats#Browser_compatibility">差异</a>。H264这样的监控设备常用的视频编码格式，主流浏览器都有某种程度的支持。</p>
<p>常见的音频编码算法包括： MP3, Vorbis, AAC；常见的视频编码算法包括： H.264, HEVC, VP8, VP9。</p>
<p>编码后的音频、视频通常被封装在一个比特流容器格式（container）中，这些格式中常见的有： MP4, FLV, WebM,  ASF, ISMA等。</p>
<div class="blog_h2"><span class="graybg">JSMpeg</span></div>
<p>视频解码工作通常由浏览器本身负责，配合video实现视频播放。</p>
<p>现代浏览器的JS引擎性能较好，因此出现了纯粹由JS实现的解码器<a href="https://github.com/phoboslab/jsmpeg">JSMpeg</a>，它能够解码视频格式MPEG1、音频格式MP2。支持通过Ajax加载静态视频文件，支持低延迟（小于50ms）的流式播放（通过WebSocket）。JSMpeg包括以下组件：</p>
<ol>
<li>MPEG-TS分流器（demuxer）。muxer负责把视频、音频、字幕打包成一种容器格式，demuxer则作相反的工作</li>
<li>MPEG1视频解码器</li>
<li>MP2音频解码器</li>
<li>WebGL渲染器、Canvas2D渲染器</li>
<li>WebAudio音频输出组件</li>
</ol>
<p>JSMpeg的优势在于兼容性好，几乎所有现代浏览器都能运行JSMpeg。</p>
<div class="blog_h3"><span class="graybg">性能</span></div>
<p>JSMpeg不能使用硬件加速。在iPhone 5S这样的设备上，JSMpeg能够处理720p@30fps视频。</p>
<p>比起现代解码器，MPEG1压缩率较低，因而需要更大的带宽。720p的视频大概占用250KB/s的带宽。</p>
<div class="blog_h3"><span class="graybg">示例</span></div>
<p>下面我们尝试利用ffmpeg编码本地摄像头视频，并通过JSMpeg播放。</p>
<p>创建一个NPM项目，安装依赖：</p>
<pre class="crayon-plain-tag">npm install jsmpeg --save
npm install ws --save</pre>
<p>JSMpeg提供了一个中继器，能够把基于HTTP的MPEG-TS流转换后通过WebSocket发送给客户端。此脚本需要<a href="https://github.com/phoboslab/jsmpeg/blob/master/websocket-relay.js">到Github下载</a>。 下面的命令启动一个中继器：</p>
<pre class="crayon-plain-tag">node ./app/websocket-relay.js 12345 8800 8801
# Listening for incomming MPEG-TS Stream on http://127.0.0.1:8800/&lt;secret&gt;
# Awaiting WebSocket connections on ws://127.0.0.1:8801/
# 实际上在所有网络接口上监听，并非仅仅loopback</pre>
<p>下面的命令捕获本地摄像头（Linux），并编码为MPEG1格式，然后发送到中继器：</p>
<pre class="crayon-plain-tag"># 从摄像头/dev/video0以480的分辨率捕获原始视频流
ffmpeg -s 640x480 -f video4linux2 -i /dev/video0 \
       # 输出为原始MPEG-1视频（JSMpeg可用），帧率30fps，比特率800kbps
       -f mpegts -codec:v mpeg1video -b 800k -r 30 http://127.0.0.1:8800/12345
# 在我的机器上，上述ffmpeg私有内存占用18MB</pre>
<p>上述命令执行后，中继器控制台上打印：</p>
<pre class="crayon-plain-tag">Stream Connected: ::ffff:127.0.0.1:42399</pre>
<p>客户端代码：</p>
<pre class="crayon-plain-tag">var player = new JSMpeg.Player( 'ws://127.0.0.1:8801/', {
    canvas: document.getElementById( 'canvas' ),
    autoplay: true
} ); </pre>
<div class="blog_h2"><span class="graybg">Broadway</span></div>
<p><a href="https://github.com/mbebenita/Broadway">Broadway</a>是一个基于JavaScript的H.264解码器，其源码来自于Android的H.264解码器，利用Emscripten转译成了JavaScript，之后利用Google的Closure编译器优化，并针对WebGL进一步优化。</p>
<p>注意：Broadway仅仅支持Baseline这个H.264 Profile。</p>
<p><a href="https://github.com/131/h264-live-player">h264-live-player</a>是基于Broadway实现的播放器，允许通过WebSocket来传输NAL单元（原始H.264帧），并在画布上渲染。我们运行一下它的示例应用：</p>
<pre class="crayon-plain-tag">git clone https://github.com/131/h264-live-player.git
cd h264-live-player
npm install</pre>
<p>因为我的机器是Linux，所以修改h264-live-player/lib/ffmpeg.js， 把ffpmeg的参数改为：</p>
<pre class="crayon-plain-tag">var args = [
    "-f", "video4linux2",
    "-i",  "/dev/video0" ,
    "-framerate", this.options.fps,
    "-video_size", this.options.width + 'x' + this.options.height,
    '-pix_fmt',  'yuv420p',
    '-c:v',  'libx264',
    '-b:v', '600k',
    '-bufsize', '600k',
    '-vprofile', 'baseline',
    '-tune', 'zerolatency',
    '-f' ,'rawvideo',
    '-'
];</pre>
<p>然后运行<pre class="crayon-plain-tag">node server-ffmpeg</pre>，打开http://127.0.0.1:8080/，可以看到自己摄像头传来的H.264码流，效果还不错。</p>
<div class="blog_h2"><span class="graybg">服务器端技术</span></div>
<div class="blog_h3"><span class="graybg">ffpmeg</span></div>
<p>老牌的编解码库，支持很多的音频、视频格式的编解码，支持多种容器格式，支持多种流协议。关于ffpmeg的详细介绍参见<a href="/linux-command-faq#ffmpeg">Linux命令知识集锦</a>。</p>
<p>ffpmeg除了提供开发套件之外，还有一个同名的命令行工具，直接使用它就可以完成很多编解码、流转换的工作。</p>
<p>类似的库是libav，ffpmeg和它的功能非常相似，特性更多一些。</p>
<div class="blog_h3"><span class="graybg">x264</span></div>
<p>官网自称是最好的H.264编码器。特性包括：</p>
<ol>
<li>提供一流的性能、压缩比。特别是性能方面，可以在普通PC上并行编码4路或者更多的1080P流</li>
<li>提供最好的视频质量，具有最高级的心理视觉优化</li>
<li>支持多种不同应用程序所需要的特性，例如电视广播、蓝光低延迟视频应用、Web视频</li>
</ol>
<div class="blog_h1"><span class="graybg">流媒体技术</span></div>
<p>有了上面介绍的HTML5标签、合理编码的视频格式，就可以实现简单的监控录像回放了。但是，要进行实时监控画面预览则没有这么简单，必须依赖流媒体技术实现。</p>
<div class="blog_h2"><span class="graybg">流媒体</span></div>
<p>所谓多媒体（Multimedia）是指多种内容形式 —— 文本、音频、视频、图片、动画等的组合。</p>
<p>所谓流媒体，就是指源源不断的由提供者产生，并持续的被终端用户接收、展示的多媒体，就像水流一样。现实世界中的媒体，有些天生就是流式的，例如电视、广播，另外一些则不是，例如书籍、CD。</p>
<p>流媒体技术（从传递媒体角度来看）可以作为文件下载的替代品。</p>
<p><span style="background-color: #c0c0c0;">流媒体技术关注的是如何传递媒体，而不是如何编码媒体</span>，具体的实现就是各种流媒体协议。封装后的媒体比特流（容器格式）由流媒体服务器递送到流媒体客户端。流媒体协议可能对底层容器格式、编码格式有要求，也可能没有任何要求。</p>
<div class="blog_h2"><span class="graybg">直播</span></div>
<p>直播流（Live streaming）和静态文件播放的关键差异：</p>
<ol>
<li>点播的目标文件通常位于服务器上，具有一定的播放时长、文件大小。浏览器可以使用渐进式下载，一边下载一边播放</li>
<li>直播不存在播放起点、终点。它表现为一种流的形式，源源不断的从视频采集源通过服务器，传递到客户端</li>
<li>直播流通常是自适应的（adaptive），其码率随着客户端可用带宽的变化，可能变大、变小，以尽可能消除延迟</li>
</ol>
<p>流媒体技术不但可以用于监控画面预览，也可以改善录像播放的用户体验，比起简单的静态文件回放，流式回放具有以下优势：</p>
<ol>
<li>延迟相对较低，播放能够尽快开始</li>
<li>自适应流可以避免卡顿</li>
</ol>
<div class="blog_h2"><span class="graybg">流协议</span></div>
<p>主流的用于承载视频流的流媒体协议包括：</p>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 18%; text-align: center;">协议</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>HLS</td>
<td>
<p>HTTP实时流（HTTP Live Streaming），由苹果开发，基于HTTP协议</p>
<p>HLS的工作原理是，把整个流划分成一个个较小的文件，客户端在建立流媒体会话后，基于HTTP协议下载流片段并播放。客户端可以从多个服务器（源）下载流。</p>
<p>在建立会话时，客户端需要下载extended M3U (m3u8) 播放列表文件，其中包含了MPEG-2 TS（Transport Stream）容器格式的视频的列表。在播放完列表中的文件后，需要再次下载<span style="color: #444444;">m3u8，如此循环</span></p>
<p>此协议在移动平台上支持较好，目前的Android、iOS版本都支持</p>
<p>此协议的重要缺点是高延迟（5s以上通常），要做到低延迟会导致频繁的缓冲（下载新片段）并对服务器造成压力，不适合视频监控</p>
<p>播放HLS流的HTML代码片段：</p>
<pre class="crayon-plain-tag">&lt;video src="http://movie.m3u8" height="329" width="480"&gt;&lt;/video&gt;</pre>
</td>
</tr>
<tr>
<td>RTMP</td>
<td>
<p>实时消息协议（Real Time Messaging Protocol），由Macromedia（Adobe）开发。此协议实时性很好，需要Flash插件才能在客户端使用，但是Adobe已经打算在不久的将来放弃对Flash的支持了
<p>有一个开源项目<a href="https://github.com/Bilibili/flv.js">HTML5 FLV Player</a>，它支持在没有Flash插件的情况下，播放Flash的视频格式FLV。此项目依赖于<a href="https://w3c.github.io/media-source/">MSE</a>，支持以下特性：</p>
<ol>
<li>支持H.264 + AAC/MP3编码的FLV容器格式的播放</li>
<li>分段（segmented）视频播放</li>
<li>基于HTTP的FLV低延迟实时流播放</li>
<li>兼容主流浏览器</li>
<li>资源占用低，可以使用客户端的硬件加速</li>
</ol>
</td>
</tr>
<tr>
<td>RTSP</td>
<td>
<p>实时流协议（Real Time Streaming Protocol），由RealNetworks等公司开发。此协议负责控制通信端点（Endpoint）之间的媒体会话（media sessions） —— 例如播放、暂停、录制。通常需要结合：实时传输协议（Real-time Transport Protocol）、实时控制协议（Real-time Control Protocol）来实现视频流本身的传递</p>
<p>大部分浏览器没有对RTSP提供原生的支持</p>
<p>RTSP 2.0版本目前正在开发中，和旧版本不兼容</p>
</td>
</tr>
<tr>
<td>MPEG-DASH</td>
<td>
<p>基于HTTP的动态自适应流（Dynamic Adaptive Streaming over HTTP），它类似于HLS，也是把流切分为很小的片段。DASH为支持为每个片段提供多种码率的版本，以满足不同客户带宽</p>
<p>协议的客户端根据自己的可用带宽，选择尽可能高（避免卡顿、重新缓冲）的码率进行播放，并根据网络状况实时调整码率</p>
<p>DASH不限制编码方式，你可以使用H.265, H.264, VP9等视频编码算法</p>
<p>Chrome 24+、Firefox 32+、Chrome for Android、IE 10+支持此格式</p>
<p>类似于HLS的高延迟问题也存在</p>
</td>
</tr>
<tr>
<td>WebRTC</td>
<td>
<p>WebRTC是一整套API，为浏览器、移动应用提供实时通信（RealTime Communications）能力。它包含了流媒体协议的功能，但是不是以协议的方式暴露给开发者的</p>
<p>WebRTC支持Chrome 23+、Firefox 22+、Chrome for Android，提供Java / Objective-C绑定</p>
<p>WebRTC主要有三个职责：</p>
<ol>
<li>捕获客户端音视频，对应接口MediaStream（也就是getUserMedia）</li>
<li>音视频传输，对应接口RTCPeerConnection</li>
<li>任意数据传输，对应接口RTCDataChannel</li>
</ol>
<p>WebRTC内置了点对点的支持，也就是说流不一定需要经过服务器中转</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">服务器端技术</span></div>
<p>视频监控通常都是CS模式（而非P2P），在服务器端，你需要部署流媒体服务。</p>
<div class="blog_h3"><span class="graybg">GStreamer</span></div>
<p><a href="https://gstreamer.freedesktop.org/">这是</a>一个开源的跨平台多媒体框架。通过它你可以构建各种各样的媒体处理组件，包括流媒体组件。通过插件机制，GStreamer支持上百种编码格式，包括MPEG-1, MPEG-2, MPEG-4, H.261, H.263, H.264, RealVideo, MP3, WMV, FLV</p>
<p><a href="https://www.kurento.org/">Kurento</a>、<a href="http://www.flumotion.net/features/">Flumotion</a>是基于GStreamer构建的流媒体服务器软件。</p>
<div class="blog_h3"><span class="graybg">Live555</span></div>
<p><a href="http://www.live555.com/">Live555</a>是流媒体服务开发的基础库，支持 RTP/RTCP/RTSP/SIP等协议，适合在硬件资源受限的情况下使用（例如嵌入式设备）。</p>
<p>基于Live555的软件包括：</p>
<ol>
<li>Live555媒体服务器，完整的RTSP服务器</li>
<li>openRTSP，一个命令行程序，支持提供RTSP流、接收RTSP流、把RTSP流中的媒体录像到磁盘</li>
<li>playSIP，可以进行VoIP通话</li>
<li>liveCaster，支持组播的MP3流媒体服务</li>
</ol>
<div class="blog_h3"><span class="graybg">其它</span></div>
<p>流媒体服务实现有很多，它们中的一些在最初针对特定的流协议，大部分都走向多元化。例如，Red5是一个RTMP流媒体服务器，Wowza是一个综合的流媒体服务器，支持WebRTC的流媒体服务在后面的章节介绍。</p>
<div class="blog_h1"><span class="graybg">HTML5媒体标签</span></div>
<p>HTML5支持<pre class="crayon-plain-tag">&lt;audio&gt;</pre>和<pre class="crayon-plain-tag">&lt;video&gt;</pre>标签（两者都对应了HTMLMediaElement的子类型）以实现视频、音频的播放。</p>
<div class="blog_h2"><span class="graybg">&lt;audio&gt;</span></div>
<p>此标签用于在浏览器中创建一个纯音频播放器。播放静态文件的示例：</p>
<pre class="crayon-plain-tag">&lt;audio controls preload="auto"&gt;
    &lt;source src="song.mp3" type="audio/mpeg"&gt;
    &lt;!-- 备选格式，如果浏览器不支持mp3 --&gt;
    &lt;source src="song.ogg" type="audio/ogg"&gt;
    &lt;!-- 如果浏览器不支持audio标签，显示下面的连接 --&gt;
    &lt;a href="audiofile.mp3"&gt;download audio&lt;/a&gt;
&lt;/audio&gt;</pre>
<div class="blog_h2"><span class="graybg">&lt;video&gt;</span></div>
<p>此标签用于在浏览器中创建一个视频播放器。播放静态文件的示例：</p>
<pre class="crayon-plain-tag">&lt;!-- poster指定预览图，autoplay自动播放，muted静音 --&gt;
&lt;video controls width="640" height="480" poster="movie.png" autoplay muted&gt;
  &lt;source src="movie.mp4" type="video/mp4"&gt;
  &lt;!-- 备选格式，如果浏览器不支持mp4 --&gt;
  &lt;source src="movie.webm" type="video/webm"&gt;
  &lt;!-- 可以附带字幕 --&gt;
  &lt;track src="subtitles_en.vtt" kind="subtitles" srclang="en" label="English"&gt;
  &lt;!-- 如果浏览器不支持video标签，显示下面的连接 --&gt;
  &lt;a href="videofile.mp4"&gt;download video&lt;/a&gt;
&lt;/video&gt;</pre>
<div class="blog_h2"><span class="graybg">&lt;canvas&gt;</span></div>
<p>在画布中，你可以进行任意的图形绘制，当然可以去逐帧渲染视频内容。</p>
<div class="blog_h2"><span class="graybg">编程方式创建</span></div>
<p>音频、视频播放器标签也可以利用JavaScript编程式的创建，示例代码：</p>
<pre class="crayon-plain-tag">var video = document.createElement( 'video' );
if ( video.canPlayType( 'video/mp4' ) ) {
    video.setAttribute( 'src', 'movie.mp4' );
}
else if ( video.canPlayType( 'video/webm' ) ) {
    video.setAttribute( 'src', 'movie.webm' );
}
video.width = 640;
video.height = 480; </pre>
<div class="blog_h1"><span class="graybg">MSE</span></div>
<p>媒体源扩展（Media Source Extensions，MSE）是一个W3C草案，<a href="http://caniuse.com/#feat=mediasource">桌面浏览器对MSE的支持较好</a>。MSE扩展流video/audio元素的能力，允许你<span style="background-color: #c0c0c0;">通过JavaScript来生成（例如从服务器抓取）媒体流供video/audio元素播放</span>。使用MSE你可以：</p>
<ol>
<li>通过JavaScript来构建媒体流，不管媒体是如何捕获的</li>
<li>处理自适应码流、广告插入、时间平移（time-shifting，回看）、视频编辑等应用场景</li>
<li>最小化JavaScript中处理媒体解析的代码</li>
</ol>
<p>MSE定义支持的（你生成的）<a href="https://www.w3.org/TR/media-source/">媒体格式</a>，只有符合要求的容器格式、编码格式才能被MSE处理。通常容器格式是<span style="color: #24292e;">ISO BMFF（MP4），也就是说你需要生成MP4的片断，然后Feed给MSE进行播放。</span></p>
<p>MediaSource对象作为video/audio元素的媒体来源，它可以具有多个SourceBuffer对象。应用程序把数据片段（segment）附加到SourceBuffer中，并可以根据系统性能对数据片段的质量进行适配。SourceBuffer中包含多个track buffer —— 分别对应音频、视频、文本等可播放数据。这些数据被音频、视频解码器解码，然后在屏幕上显示、在扬声器中播放：</p>
<p> <img class="aligncenter size-large wp-image-15564" src="https://blog.gmem.cc/wp-content/uploads/2017/08/pipeline_model.png" alt="pipeline_model" width="710" height="516" /></p>
<p>要把MediaSource提供给video/audio播放，调用：</p>
<pre class="crayon-plain-tag">video.src = URL.createObjectURL(mediaSource);</pre>
<div class="blog_h2"><span class="graybg">基于MSE的框架</span></div>
<div class="blog_h3"><span class="graybg">wfs</span></div>
<p><a href="https://github.com/ChihChengYang/wfs.js">wfs</a>是一个播放原始H.264帧的HTML5播放器，它的工作方式是把H.264 NAL单元封装为 ISO BMFF（MP4）片，然后Feed给MSE处理。</p>
<div class="blog_h3"><span class="graybg">flv.js</span></div>
<p><a href="https://github.com/Bilibili/flv.js">flv.js</a>是一个HTML5 Flash视频播放器，基于纯JS，不需要Flash插件的支持。此播放器将FLV流转换为ISO BMFF（MP4）片断，然后把MP4片断提供给video元素使用。</p>
<p>flv.js支持Chrome 43+, FireFox 42+, Edge 15.15048+以上版本的直播流 。</p>
<div class="blog_h3"><span class="graybg">Streamedian</span></div>
<p><a href="https://github.com/Streamedian/html5_rtsp_player/wiki/HTML5-RTSP-Player">Streamedian</a>是一个HTML5的RTSP播放器。实现了RTSP客户端功能，你可以利用此框架直接播放RTSP直播流。此播放器把RTP协议下的H264/AAC在转换为ISO BMFF供video元素使用。Streamedian支持Chrome 23+, FireFox 42+, Edge 13+，以及Android 5.0+。不支持iOS和IE。</p>
<p>在服务器端，你需要安装Streamedian提供的代理（此代理收费），此代理将RTSP转换为WebSocket。Streamedian处理视频流的流程如下：<img class="aligncenter size-large wp-image-15609" src="https://blog.gmem.cc/wp-content/uploads/2017/08/streamedian-1024x391.png" alt="streamedian" width="710" height="271" /></p>
<div class="blog_h1"><span class="graybg">WebRTC</span></div>
<p>WebRTC是一整套API，其中一部分供Web开发者使用，另外一部分属于要求浏览器厂商实现的接口规范。WebRTC解决诸如客户端流媒体发送、点对点通信、视频编码等问题。<a href="http://iswebrtcreadyyet.com/legacy.html">桌面浏览器对WebRTC的支持较好</a>，WebRTC也很容易和Native应用集成。</p>
<p>使用MSE时，你需要自己构建视频流。使用WebRTC时则可以直接捕获客户端视频流。</p>
<p>使用WebRTC时，大部分情况下流量不需要依赖于服务器中转，服务器的作用主要是：</p>
<ol>
<li>在信号处理时，转发客户端的数据</li>
<li>配合实现NAT/防火墙穿透</li>
<li>在点对点通信失败时，作为中继器使用</li>
</ol>
<div class="blog_h2"><span class="graybg">架构</span></div>
<p><img class="aligncenter size-full wp-image-15576" src="https://blog.gmem.cc/wp-content/uploads/2017/08/webrtcArchitecture.png" alt="webrtcarchitecture" width="100%" /></p>
<div class="blog_h2"><span class="graybg">流捕获</span></div>
<div class="blog_h3"><span class="graybg">捕获视频</span></div>
<p>主要是捕获客户端摄像头、麦克风。在视频监控领域用处不大，这里大概了解一下。流捕获通过navigator.getUserMedia调用实现： </p>
<pre class="crayon-plain-tag">&lt;script type="text/javascript"&gt;
    navigator.getUserMedia = navigator.webkitGetUserMedia || navigator.getUserMedia;
    var success = function ( stream ) {
        var video = document.getElementById( 'camrea' );
        // 把MediaStream对象转换为Blob URL，提供给video播放
        video.src = URL.createObjectURL( stream );
        video.play();
    }
    var error = function ( err ) {
        console.log( err )
    }
    // 调用成功后，得到MediaStream对象
    navigator.getUserMedia( { video: true, audio: true }, success, error );
&lt;/script&gt;
&lt;video id="camrea" width="640" height="480"/&gt;</pre>
<p>三个调用参数分别是：</p>
<ol>
<li><a href="http://io13webrtc.appspot.com/#22">约束条件</a>，你可以指定媒体类型、分辨率、帧率 </li>
<li>成功后的回调，你可以在回调中解析出URL提供给video元素播放</li>
<li>失败后的回调</li>
</ol>
<div class="blog_h3"><span class="graybg">捕获音频</span></div>
<p>捕获音频类似：</p>
<pre class="crayon-plain-tag">navigator.getUserMedia( { audio: true }, function ( stream ) {
    var audioContext = new AudioContext();

    // 从捕获的音频流创建一个媒体源管理
    var streamSource = audioContext.createMediaStreamSource( stream );

    // 把媒体源连接到目标（默认是扬声器）
    streamSource.connect( audioContext.destination );
}, error );</pre>
<div class="blog_h3"><span class="graybg">MediaStream</span></div>
<p>MediaStream对象提供以下方法：</p>
<ol>
<li>getAudioTracks()，音轨列表</li>
<li>getVideoTracks()，视轨列表</li>
</ol>
<p>每个音轨、视轨都有个label属性，对应其设备名称。</p>
<div class="blog_h3"><span class="graybg">Camera.js</span></div>
<p><a href="https://github.com/idevelop/camera.js">Camera.js</a>是对getUserMedia的简单封装，简化了API并提供了跨浏览器支持：</p>
<pre class="crayon-plain-tag">camera.init( {
    width: 640,
    height: 480,
    fps: 30, // 帧率
    mirror: false,  // 是否显示为镜像
    targetCanvas: document.getElementById( 'webcam' ), // 默认null，如果设置了则在画布中渲染

    onFrame: function ( canvas ) {
        // 每当新的帧被捕获，调用此回调
    },

    onSuccess: function () {
        // 流成功获取后
    },

    onError: function ( error ) {
        // 如果初始化失败
    },

    onNotSupported: function () {
        // 当浏览器不支持camera.js时
    }
} );
// 暂停
camera.pause();
// 恢复
camera.start();</pre>
<p><a href="https://idevelop.ro/predator-vision/">掠食者视觉</a>是基于Camera实现的一个好玩的例子（移动侦测）。</p>
<div class="blog_h2"><span class="graybg">信号处理</span></div>
<p>在端点之间（Peer）发送流之前，需要进行通信协调、发送控制消息，即所谓信号处理（Signaling），信号处理牵涉到三类信息：</p>
<ol>
<li>会话控制信息：初始化、关闭通信，报告错误</li>
<li>网络配置：对于其它端点来说，本机的IP和端口是什么</li>
<li>媒体特性：本机能够处理什么音视频编码、多高的分辨率。本机发送什么样的音视频编码</li>
</ol>
<p>WebRTC没有对信号处理规定太多，我们可以通过Ajax/WebSocket通信，以SIP、Jingle、ISUP等协议完成信号处理。点对点连接设立后，流的传输并不需要服务器介入。信号处理的示意图如下：</p>
<p><img class="aligncenter wp-image-15587 size-full" src="https://blog.gmem.cc/wp-content/uploads/2017/08/jsep.png" alt="jsep" width="100%" /></p>
<div class="blog_h3"><span class="graybg">示例代码</span></div>
<p>下面的代表片段包含了一个视频电话的信号处理过程：</p>
<pre class="crayon-plain-tag">// 信号处理通道，底层传输方式和协议自定义
var signalingChannel = createSignalingChannel();
var conn;

// 信号通过此回调送达本地，可能分多次送达
signalingChannel.onmessage = function ( evt ) {
    if ( !conn ) start( false );

    var signal = JSON.parse( evt.data );
    // 会话描述协议（Session Description Protocol），用于交换媒体配置信息（分辨率、编解码能力）
    if ( signal.sdp )
    // 设置Peer的RTCSessionDescription
        conn.setRemoteDescription( new RTCSessionDescription( signal.sdp ) );
    else
    // 添加Peer的Candidate信息
        conn.addIceCandidate( new RTCIceCandidate( signal.candidate ) );
};

// 调用此方法启动WebRTC，获取本地流并显示，侦听连接上的事件并处理
function start( isCaller ) {
    conn = new RTCPeerConnection( { /**/ } );

    // 把地址/端口信息发送给其它Peer。所谓Candidate就是基于ICE框架获得的本机可用地址/端口
    conn.onicecandidate = function ( evt ) {
        signalingChannel.send( JSON.stringify( { "candidate": evt.candidate } ) );
    };

    // 当远程流到达后，在remoteView元素中显示
    conn.onaddstream = function ( evt ) {
        remoteView.src = URL.createObjectURL( evt.stream );
    };

    // 获得本地流
    navigator.getUserMedia( { "audio": true, "video": true }, function ( stream ) {
        // 在remoteView元素中显示
        localView.src = URL.createObjectURL( stream );
        // 添加本地流，Peer将接收到onaddstream事件
        conn.addStream( stream );


        if ( isCaller )
        // 获得本地的RTCSessionDescription
            conn.createOffer( gotDescription );
        else
        // 针对Peer的RTCSessionDescription生成兼容的本地SDP
            conn.createAnswer( conn.remoteDescription, gotDescription );

        function gotDescription( desc ) {
            // 设置自己的RTCSessionDescription
            conn.setLocalDescription( desc );
            // 把自己的RTCSessionDescription发送给Peer
            signalingChannel.send( JSON.stringify( { "sdp": desc } ) );
        }
    } );
}

// 通信发起方调用：
start( true );</pre>
<div class="blog_h2"><span class="graybg">流转发</span></div>
<p>主要牵涉到的接口是RTCPeerConnection，上面的例子中已经包含了此接口的用法。WebRTC在底层做很多复杂的工作，这些工作对于JavaScript来说是透明的： </p>
<ol>
<li>执行解码</li>
<li>屏蔽丢包的影响</li>
<li>点对点通信：WebRTC引入流交互式连接建立（Interactive Connectivity Establishment，ICE）框架。ICE负责建立点对点链路的建立：
<ol>
<li>首先尝试直接</li>
<li>不行的话尝试STUN（Session Traversal Utilities for NAT）协议。此协议通过一个简单的保活机制确保NAT端口映射在会话期间有效</li>
<li>仍然不行尝试TURN（Traversal Using Relays around NAT）协议。此协议依赖于部署在公网上的中继服务器。只要端点可以访问TURN服务器就可以建立连接</li>
</ol>
</li>
<li>通信安全</li>
<li>带宽适配</li>
<li>噪声抑制</li>
<li>动态抖动缓冲（dynamic jitter buffering），抖动是由于网络状况的变化，缓冲用于收集、存储数据，定期发送</li>
</ol>
<div class="blog_h2"><span class="graybg">任意数据交换</span></div>
<p>通过RTCDataChannel完成，允许点对点之间任意的数据交换。RTCPeerConnection连接创建后，不但可以传输音视频流，还可以打开多个信道（RTCDataChannel）进行任意数据的交换。RTCDataChanel的特点是：</p>
<ol>
<li>类似于WebSocket的API</li>
<li>支持带优先级的多通道</li>
<li>超低延迟，因为不需要通过服务器中转</li>
<li>支持可靠/不可靠传输语义。支持SCTP、DTLS、UDP几种传输协议</li>
<li>内置安全传输（DTLS）</li>
<li>内置拥塞控制</li>
</ol>
<p>使用RTCDataChannel可以很好的支持游戏、远程桌面、实时文本聊天、文件传输、去中心化网络等业务场景。</p>
<div class="blog_h2"><span class="graybg">adapter.js</span></div>
<p><a href="https://github.com/webrtc/adapter">WebRTC adapter</a>是一个垫片库，使用它开发WebRTC应用时，不需要考虑不同浏览器厂商的<a href="https://webrtc.org/web-apis/interop/">API前缀差异</a>。</p>
<div class="blog_h2"><span class="graybg">WebRTC示例</span></div>
<p>本节列出一些WebRTC的代码示例，这些例子都使用adapter.js。</p>
<div class="blog_h3"><span class="graybg">限定分辨率</span></div>
<pre class="crayon-plain-tag">// 指定分辨率
// adapter.js 支持Promise
navigator.mediaDevices.getUserMedia( { video: { width: { exact: 640 }, height: { exact: 480 } } } ).then( stream =&gt; {
    let video = document.createElement( 'video' );
    document.body.appendChild( video );
    video.srcObject = stream;
    video.play();
} ).catch( err =&gt; console.log( err ) );</pre>
<div class="blog_h3"><span class="graybg">在画布中截图</span></div>
<pre class="crayon-plain-tag">// video为video元素
canvas.getContext('2d').drawImage(video, 0, 0, canvas.width, canvas.height);</pre>
<div class="blog_h2"><span class="graybg">WebRTC框架</span></div>
<table class="full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">框架</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td><a href="http://peerjs.com/">PeerJS </a></td>
<td>
<p>简化WebRTC的点对点通信、视频、音频调用</p>
<p>提供云端的PeerServer，你也可以自己搭建服务器</p>
</td>
</tr>
<tr>
<td><a href="https://github.com/peer5/sharefest">Sharefest</a></td>
<td>基于Web的P2P文件共享</td>
</tr>
<tr>
<td><a href="https://github.com/webRTC-io/webRTC.io">webRTC.io</a></td>
<td>
<p>WebRTC的一个抽象层，同时提供了客户端、服务器端Node.js组件。服务器端组件抽象了STUN</p>
<p>类似的框架还有<a href="https://github.com/andyet/SimpleWebRTC">SimpleWebRTC</a>、<a href="https://github.com/priologic/easyrtc">easyrtc</a></p>
</td>
</tr>
<tr>
<td><a href="https://www.openwebrtc.org/">OpenWebRTC</a></td>
<td>
<p>允许你构建能够和遵循WebRTC标准的浏览器进行通信的Native应用程序，支持Java绑定</p>
</td>
</tr>
<tr>
<td><a href="https://nextrtc.org/">NextRTC</a></td>
<td>
<p>基于Java实现的WebRTC信号处理服务器</p>
</td>
</tr>
<tr>
<td><a href="https://github.com/meetecho/janus-gateway">Janus</a></td>
<td>
<p>这是一个WebRTC网关，纯服务器端组件，目前仅仅支持Linux环境下安装</p>
<p>Janus本身实现了到浏览器的WebRTC连接机制，支持以JSON格式交换数据，支持在服务器端应用逻辑 - 浏览器之间中继RTP/RTCP和消息。特殊化的功能有服务器端插件完成</p>
<p>官网地址：<a href="https://janus.conf.meetecho.com/index.html">https://janus.conf.meetecho.com</a></p>
</td>
</tr>
<tr>
<td><a href="https://www.kurento.org">Kurento</a></td>
<td>
<p>这是一个开源的WebRTC媒体服务器</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">备选方案一：从RTSP开始</span></div>
<p>我们首先尝试的方案是直接使用RTSP源，原因是海康、大华主流厂商的较新的IP摄像头均支持暴露标准化的RTSP流。</p>
<div class="blog_h2"><span class="graybg">尝试播放</span></div>
<p>使用VLC播放器，打开网络串流：rtsp://admin:12345@192.168.0.196:554/ch1/main/av_stream，视频源为公司门口的海康摄像头的主码流（main，子码流为sub）。</p>
<p>发现可以正常播放，说明视频格式应该是标准的。VLC菜单 Tool ⇨ Codec Info查看，编码格式为H264。</p>
<p>浏览器无法直接使用RTSP协议，因此，需要有服务器端来处理视频源的RTSP，将其转换为：</p>
<ol>
<li>通过WebSocket发送的视频片断，由客户端的：
<ol>
<li>JSMpeg/Broadway直接解码，渲染到画布</li>
<li>或者，构造MP4片断Feed给MSE播放</li>
</ol>
</li>
<li>或者，通过WebRTC网关，转换后提供给客户端的WebRTC代码处理</li>
<li>或者，使用浏览器插件机制，例如Chrome的NaCl</li>
</ol>
<div class="blog_h2"><span class="graybg">实现方式一：MSE</span></div>
<p>Streamedian的服务器端需要授权，我们选用了另外一个实现。</p>
<p><span style="color: #24292e;"><a href="https://github.com/veyesys/h5stream">H5S</a>是一个基于live555实现的开源的HTML5 RTSP网关，支持将RTSP/H264流输入转换为HTML5 MSE支持的H264，客户端基于MSE。</span></p>
<div class="blog_h3"><span class="graybg">服务器</span></div>
<p>尝试在容器中运行H5S：</p>
<pre class="crayon-plain-tag">docker create --name ubuntu-16.04 -h ubuntu-16 --network local --dns 172.21.0.1 --ip 172.21.0.6 -it docker.gmem.cc/ubuntu:16.04 bash
docker start ubuntu-16.04
docker exec -it ubuntu-16.04 bash

apt update &amp;&amp; apt install wget
wget https://raw.githubusercontent.com/veyesys/release/master/h5stream/H5S-r1.0.1128.16-Ubuntu-16.04-64bit.tar.gz
tar xzf H5S-r1.0.1128.16-Ubuntu-16.04-64bit.tar.gz &amp;&amp; mv H5S-r1.0.1128.16-Ubuntu-16.04-64bit h5s-1.0

cd h5s-1.0
export LD_LIBRARY_PATH=`pwd`/lib/:$LD_LIBRARY_PATH
# 指定两次密码，可能H5S存在bug，不这样报身份验证失败
./h5ss rtsp://admin:12345@192.168.0.196:554/ch1/sub/av_stream admin 12345</pre>
<div class="blog_h3"><span class="graybg">客户端</span></div>
<p>使用H5S自带的基于MSE的客户端代码 + Chrome 49，播放后发现画面静止。控制它查看发现解码错误。打开chrome://media-internals/，发现错误Media segment did not begin with key frame. Support for such segments will be available in a future version。看样子是提供给SourceBuffer的数据不是以关键帧开始导致，未来版本的Chrome可能取消此限制。</p>
<p>换成Chrome 50，可以正常播放，但是流畅度较差，播放一段时间后出现卡死的情况。</p>
<div class="blog_h3"><span class="graybg">小结</span></div>
<p>H5S实现不完善，在不修改源码的情况下，服务器端只能接入一路视频输入。客户端也存在不流畅、卡死的问题，不适合生产环境。</p>
<div class="blog_h2"><span class="graybg">实现方式二：JSMpeg</span></div>
<div class="blog_h3"><span class="graybg">转码进程</span></div>
<p>在上文中我们已经成功尝试了利用JSMpege + WebSocket的方式，在网页中显示摄像头捕获的视频。ffmpeg转换RTSP也是非常简单的：</p>
<pre class="crayon-plain-tag">ffmpeg -i rtsp://admin:12345@192.168.0.196:554/ch1/main/av_stream -s 427x240 -f mpegts -vcodec mpeg1video -b 800k -r 30 http://127.0.0.1:8800/12345</pre>
<div class="blog_h3"><span class="graybg">服务器</span></div>
<p>可以使用JSMpeg自带的简单Node.js服务器测试：</p>
<pre class="crayon-plain-tag">node ./app/websocket-relay.js 12345 8800 8801 </pre>
<div class="blog_h3"><span class="graybg">客户端</span></div>
<p>下面是客户端代码，默认JSMpeg会基于WebGL渲染，但是我的机器最多开到8画面，开9画面时出现警告：</p>
<p>Too many active WebGL contexts. Oldest context will be lost，且第一画面丢失，简单的通融方法是，第9画面使用Canvas2D渲染：</p>
<pre class="crayon-plain-tag">new JSMpeg.Player( 'ws://127.0.0.1:8801/', {
    canvas: document.getElementById( 'canvas9' ),
    autoplay: true,
    // 浏览器对WebGL context的数量有限制
    disableGl: true
} ); </pre>
<p>渲染截图：</p>
<p><img class="aligncenter size-large wp-image-15669" src="https://blog.gmem.cc/wp-content/uploads/2017/08/jsmpeg-s9-1024x621.png" alt="jsmpeg-s9" width="100%" /></p>
<div class="blog_h3"><span class="graybg">小结</span></div>
<p>这种方式客户端解码压力较大，同时开9画面的352x288视频，我的机器上CPU占用率大概到40%左右，画面变化较为剧烈的时候会出现卡顿现象。</p>
<div class="blog_h2"><span class="graybg">实现方式三：Broadway</span></div>
<p>与JSMpeg类似，Broadway也是JavaScript解码工具。关键之处是，Broadway支持的视频编码是H.264，意味着可能免去消耗服务器资源的视频重编码。</p>
<p><a id="hk-av-config"></a>最初的尝试并不顺利，根据IP摄像头的RTSP Describe应答（SDP），我们推断其H.264 Profile为Baseline，但是不转码的情况下Broadway根本无法播放。后来查看ffmpeg的日志输出，发现其实际上使用的Profile是Main。进一步尝试，发现摄像头是可以配置为Baseline的：</p>
<p><img class="aligncenter size-full wp-image-16196" src="https://blog.gmem.cc/wp-content/uploads/2017/08/hk-config.png" alt="hk-config" width="707" height="465" /></p>
<p>只需要把编码复杂度设置为低，H.264的Profile就从Main变为Baseline。</p>
<p>设置完毕后，仍然基于h264-live-player的Demo进行测试，使用如下命令行抽取原始H.264帧：</p>
<pre class="crayon-plain-tag">ffmpeg -i rtsp://admin:12345@192.168.0.196:554/ch1/main/av_stream -c:v copy -f rawvideo  -</pre>
<p>即可免转码的进行实时视频预览了。 </p>
<p>此实现方式更多细节信息请参考<a href="/html5-vs-with-broadway">基于Broadway的HTML5视频监控</a>。</p>
<div class="blog_h2"><span class="graybg">实现方式四：NaCl</span></div>
<p>Chrome放弃NPAPI之后，插件开发需要使用PPAPI /NaCl。目前能找到的实现有<a href="https://www.videoexpertsgroup.com/vxg-chrome-plugin/">VXG Chrome Plugin</a>，这是一个商业产品，需要授权。除了RTSP之外，还支持RTMP、HLS等协议。</p>
<p>插件方案的缺点是，需要安装，而且仅仅针对单种浏览器。优势则是灵活性高，理论上性能可以做的很好。</p>
<div class="blog_h2"><span class="graybg">实现方式五：WebRTC</span></div>
<p>WebRTC相关的框架非常多，经过简单的比较，我们决定从Kurento入手。主要原因是：</p>
<ol>
<li>容易扩展的模块化设计</li>
<li>提供Java客户端、JS客户端</li>
<li>可以在服务器端合成多画面，这样可以减轻客户端解码压力，特别是那些低配置的客户端</li>
<li>内置对RTSP协议的支持</li>
</ol>
<p><a href="/webrtc-server-basedon-kurento#vs">基于Kurento搭建WebRTC服务器</a>一文详细讨论了这种实现方式。</p>
<div class="blog_h1"><span class="graybg">备选方案二：从设备SDK开始</span></div>
<p>这里的设备，主要包括：网络硬盘录像机（NVR）、视频服务器、IP摄像头。为了便于二次开发，硬件厂商都为这些设备配置的相应的SDK套件。这些SDK通常都提供了：实时码流预览、录像文件回放、播放控制（如：暂停、单帧前进、单帧后退）、获取码流基本信息、播放截图等功能。</p>
<p>我们的基本目标是，通过SDK得到标准化的码流，例如H264格式。具体如何操作，得看厂商的SDK，但是思路基本是：</p>
<ol>
<li>如果SDK直接支持获取标准格式的流，例如RTSP，那么备选方案一就可以直接用上</li>
<li>如果SDK支持获取标准编码的视频帧，例如H264，那我们只需要将其包装为合适的容器格式，再通过RTSP/HTTP的方式发送出去</li>
<li>如果SDK支持获取解码后的原始图像数据，例如RGB、YV12，我们可以基于H264再次编码，然后按第2步方式处理。这种方式对服务器性能要求比较高，CPU压力较大，PC机处理不了多少个通道</li>
<li>如果都不支持，只提供了封装好的播放控件 —— 这个就比较悲催了，不过通过OS底层API，例如Windows的GDI应该也是可以实现，否则那些屏幕录像软件怎么做的呢？</li>
</ol>
<div class="blog_h2"><span class="graybg">海康SDK</span></div>
<p>根据Linux版本的海康设备网络编程指南的描述，我们应该可以：</p>
<ol>
<li>调用NET_DVR_Init进行SDK初始化</li>
<li>调用NET_DVR_Login登陆到目标设备</li>
<li>调用NET_DVR_RealPlay进行播放，此时返回一个实时播放句柄
<ol>
<li>如果设备支持RTSP协议取流：针对上述句柄调用NET_DVR_SetStandardDataCallBack，可以设置一个标准的数据回调函数，此回调会接受到标准码流，这对应上面的第1种思路</li>
<li>如果设备不支持RTSP协议取流：针对上述句柄调用NET_DVR_SetRealDataCallBack，然后通过PlayM4播放库中的PlayM4_SetDecCallBack回调得到<a href="/image-processing-faq#yv12">yv12</a>格式的原始图像。这对应上面的第3种思路</li>
</ol>
</li>
</ol>
<div class="blog_h3"><span class="graybg">示例代码</span></div>
<p>cmake构建配置：</p>
<pre class="crayon-plain-tag">cmake_minimum_required(VERSION 3.6)
project(hikvision)

include_directories(/home/alex/CPP/lib/hcnedsdk/include)

set(SOURCE_FILES getstream.cpp)
add_executable(getstream ${SOURCE_FILES})
target_link_libraries(getstream /home/alex/CPP/lib/hcnedsdk/lib/libhcnetsdk.so)</pre>
<p> C++代码：</p>
<pre class="crayon-plain-tag">#include &lt;HCNetSDK.h&gt;
#include &lt;stdio.h&gt;
#include &lt;cstring&gt;
#include &lt;unistd.h&gt;

// RTSP协议取流
void CALLBACK cbStdData( LONG lRealHandle, DWORD dwDataType, BYTE *pBuffer, DWORD dwBufSize, DWORD dwUser ) {
    switch ( dwDataType ) {
        case NET_DVR_SYSHEAD:        // 系统头数据，回调的第一个包是40字节的文件头
            break;
        case NET_DVR_STREAMDATA:     // 基于私有协议时：视频流数据（包括复合流和音视频分开的视频流数据）
            break;
        case NET_DVR_STD_VIDEODATA:  // 基于标准协议时：标准视频流数据（RTP包）
            break;
        case NET_DVR_STD_AUDIODATA:  // 基于标准协议时：标准音频流数据
            break;
        case NET_DVR_SDP:            // SDP信息(RTSP传输时有效)
            break;
        case NET_DVR_PRIVATE_DATA:   // 私有数据,包括智能信息叠加等
            break;
    }
}

int main() {
    // SDK初始化
    BOOL result = NET_DVR_Init();
    if ( !result ) return 1;

    // 同步登陆
    NET_DVR_USER_LOGIN_INFO struLoginInfo = { 0 };
    struLoginInfo.bUseAsynLogin = 0;
    strcpy( struLoginInfo.sDeviceAddress, "192.168.0.196" );
    struLoginInfo.wPort = 8000;
    strcpy( struLoginInfo.sUserName, "admin" );
    strcpy( struLoginInfo.sPassword, "12345" );
    NET_DVR_DEVICEINFO_V40 struDevInfo = { 0 };
    LPNET_DVR_DEVICEINFO_V30 lpDevInfo30;
    long lUserID = NET_DVR_Login_V40( &amp;struLoginInfo, &amp;struDevInfo );
    if ( lUserID &lt; 0 ) {
        printf( "登陆失败，错误码 %d\n", NET_DVR_GetLastError());
        NET_DVR_Cleanup();
        return 1;
    } else {
        lpDevInfo30 = &amp;struDevInfo.struDeviceV30;
        printf( "成功登陆到设备：%s\n", lpDevInfo30-&gt;sSerialNumber );
        printf( "SDK字符串编码方式（1 GB2312，2 GBK，3 BIG5，6 UTF-8）：%d\n", struDevInfo.byCharEncodeType );
        printf( "设备类型（31 高清网络摄像机）：%d\n", lpDevInfo30-&gt;wDevType );
        printf( "模拟通道起始号：%d，模拟通道个数%d，数字通道起始号：%d，数字通道个数%d\n", lpDevInfo30-&gt;byStartChan, lpDevInfo30-&gt;byChanNum,
                lpDevInfo30-&gt;byStartDChan, lpDevInfo30-&gt;byIPChanNum + lpDevInfo30-&gt;byHighDChanNum &lt;&lt; 8 );
        printf( "主码流是否支持RTSP方式：%s，子码流是否支持RTSP方式：%s\n", lpDevInfo30-&gt;byMainProto &gt; 0 ? "是" : "否",
                lpDevInfo30-&gt;bySubProto &gt; 0 ? "是" : "否" );
    }

    // 启动预览
    NET_DVR_PREVIEWINFO struPrevInfo = { 0 };
    struPrevInfo.hPlayWnd = NULL;    // Linux 64 位系统不支持软解码功能
    struPrevInfo.lChannel = 1;       // 预览通道号
    struPrevInfo.dwStreamType = 0;   // 0-主码流， 1-子码流， 2-码流 3， 3-码流 4，以此类推
    struPrevInfo.dwLinkMode = 0;     // 0- TCP 方式， 1- UDP 方式， 2- 组播方式， 3- RTP 方式， 4-RTP/RTSP， 5-RSTP/HTTP
    struPrevInfo.bBlocked = 1;       // 0- 非阻塞取流， 1- 阻塞取流
    struPrevInfo.byProtoType = 1;    // 应用层取流协议使用RTSP
    LONG lRealHandle = NET_DVR_RealPlay_V40( lUserID, &amp;struPrevInfo, NULL, NULL );
    if ( lRealHandle == -1 ) {
        printf( "启动预览失败，错误码 %d\n", NET_DVR_GetLastError());
        NET_DVR_Logout( lUserID );
        NET_DVR_Cleanup();
        return 1;
    }

    if ( lpDevInfo30-&gt;byMainProto ) {
        printf( "设置获取标准码流的回调\n" );
        // 仅支持对 支持RTSP协议取流的设备的 标准码流回调
        NET_DVR_SetStandardDataCallBack( lRealHandle, cbStdData, NULL );
    }

    sleep( 120 );
    // 停止预览
    NET_DVR_StopRealPlay( lRealHandle );
    // 登出
    NET_DVR_Logout( lUserID );
    // SDK清理
    NET_DVR_Cleanup();
    return 0;
}</pre>
<p>运行脚本：</p>
<pre class="crayon-plain-tag">export HKLIB_HOME=/home/alex/CPP/lib/hcnedsdk/lib
export LD_LIBRARY_PATH=$HKLIB_HOME:$HKLIB_HOME/HCNetSDKCom
./getstream </pre>
<p>此程序运行后，会自动获取到基于RTSP协议的媒体流，回调函数会反复被调用：</p>
<ol>
<li>第一次调用为40字节的头，不太清楚有什么用</li>
<li>第二次调用传递了SDP</li>
<li>后续调用传递标准音视频数据，其内容是RTP封包</li>
</ol>
<div class="blog_h1"><span class="graybg">总结</span></div>
<p>基于HTM5的视频监控，媒体流从采集设备到浏览器，主要路径如下图所示：</p>
<p><img class="aligncenter size-full wp-image-15824" src="https://blog.gmem.cc/wp-content/uploads/2017/08/h5vs-dataflow.png" alt="h5vs-dataflow" width="100%" /></p>
<p>对上图的说明如下：</p>
<ol>
<li>在设备层，需要以某种方式获得码流，以流协议的方式发送出去。最常用的方式是RTSP/RTP。流的可能获取路径为：
<ol>
<li>设备直接暴露RTSP协议端点，并且发送标准码流</li>
<li>设备SDK允许获取标准码流，需要自己以RTSP协议发送</li>
<li>设备SDK允许获得解码后的逐帧，需要直接编码为H264，然后以RTSP发送</li>
</ol>
</li>
<li>流媒体层通常需要引入专门的流媒体服务器，这类服务器能够在内部进行各种流协议的转换，可以解除客户端对特定流协议的依赖</li>
<li>客户端和服务器端的传输方式，可以有TCP、HTTP、P2P（WebRTC）、WebSocket等多种。其中
<ol>
<li>直接的TCP协议浏览器是不支持的，这意味着RTSP/RTMP等协议，在浏览器端必须要有插件才可以使用</li>
<li>WebSocket通常配合JSMpeg或者MSE使用，由程序向JSMpeg/MSE不断Feed视频帧</li>
</ol>
</li>
<li>客户端解码展示的技术主要有三类：
<ol>
<li>浏览器内置的解码能力，主要通过video标签，MSE属于此类</li>
<li>JavaScript软解码，主要是JSMpeg、Broadway</li>
<li>插件机制，例如Chrome的NaCl</li>
</ol>
</li>
</ol>
<p>能够免于引入流媒体层的方案，需要：设备能直接暴露标准码流的RTSP端点，并且安装浏览器插件。缺点也很明显，一个是设备的访问密码暴露给了客户端，第二个是目前没有成熟、开源的插件可用。我相信主要原因是合理技术方向不在于此，没人愿意去开发。</p>
<p>直接使用设备层的RTSP端点，可能存在兼容性问题。一个是它发送的码流是否标准化，第二个是市场上有多少设备没有暴露RTSP端点。</p>
<p>客户端方面，JSMpeg是兼容性较好的方案，WebRTC/MSE都有部分平台不支持（但是桌面级的浏览器大部分支持）。JSMpeg的缺点是：</p>
<ol>
<li>如果基于WebGL渲染，受限于浏览器WebGL上下文最大数量，多画面可能无法渲染。某些流媒体服务器支持在服务器端合成多画面Grid，可以规避此缺点</li>
<li>如果基于Canvas2D渲染，画质较差（我的机器上还有莫名其妙的斜线）</li>
<li>对码流格式要求严格，仅仅支持MPEG-TS，此格式压缩比差，网络带宽占用大</li>
<li>性能相对较差，尽管使用了MPEG-TS这种简单的视频格式，基于JavaScript解码渲染仍然使客户端压力较大。我的机器（i7-4940MX / Quadro K5100M / Ubuntu 14.04 LTS）上会出现卡顿情况</li>
</ol>
<p>和JSMpeg类似的库是Broadway，后者能够进行Baseline的H.264解码。如果设备支持Baseline H.264输出，使用Broadway可以很好的解决服务器端转码导致的资源消耗问题。</p>
<div class="blog_h1"><span class="graybg">附录</span></div>
<div class="blog_h2"><span class="graybg">参考资料</span></div>
<ol>
<li><a href="https://developer.mozilla.org/en-US/Apps/Fundamentals/Audio_and_video_delivery">Audio and Video Delivery</a></li>
<li><a href="https://www.w3.org/TR/media-source/">W3C Recommendation - Media Source Extensions™</a></li>
<li><a href="https://webrtc.org">WebRTC Project Home</a></li>
<li><a href="https://imququ.com/post/html5-live-player-3.html">HTML5 视频直播（三）</a></li>
</ol>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/research-on-html5-video-surveillance">HTML5视频监控技术预研</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/research-on-html5-video-surveillance/feed</wfw:commentRss>
		<slash:comments>7</slash:comments>
		</item>
		<item>
		<title>Framework7知识集锦</title>
		<link>https://blog.gmem.cc/framework7-faq</link>
		<comments>https://blog.gmem.cc/framework7-faq#comments</comments>
		<pubDate>Fri, 19 Feb 2016 08:39:12 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[HTML]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Mobile]]></category>
		<category><![CDATA[F7]]></category>
		<category><![CDATA[FAQ]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=11067</guid>
		<description><![CDATA[<p>常见问题 注意点 从页面A导航到B，导航后，A的DOM保留；回退后，B的DOM默认移除 页面回调、事件中均可以访问页面数据，这意味着页面已经解析完毕，如果使用Template7模板，此时模板已经转换为目标HTML。因此尝试在页面回调、事件中为Template7模板提供上下文，均是不可取的 在preprocess回调中，不能进行路由，会导致错误 通过data-context指定页面上下文无效 导航到子页面时报错：Uncaught SyntaxError: Unexpected token 原因：F7要求规范化的JSON格式，所有对象属性必须用双引号包围。</p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/framework7-faq">Framework7知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">常见问题</span></div>
<div class="blog_h3"><span class="graybg">注意点</span></div>
<ol>
<li>从页面A导航到B，导航后，A的DOM保留；回退后，B的DOM默认移除</li>
<li>页面回调、事件中均可以访问页面数据，这意味着页面已经解析完毕，如果使用Template7模板，此时模板已经转换为目标HTML。因此尝试在页面回调、事件中为Template7模板提供上下文，均是不可取的</li>
<li>在preprocess回调中，不能进行路由，会导致错误</li>
</ol>
<div class="blog_h3"><span class="graybg">通过data-context指定页面上下文无效</span></div>
<p>导航到子页面时报错：Uncaught SyntaxError: Unexpected token</p>
<p>原因：F7要求规范化的JSON格式，所有对象属性必须用双引号包围。</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/framework7-faq">Framework7知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/framework7-faq/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Framework7学习笔记（三）：高级</title>
		<link>https://blog.gmem.cc/framework7-study-note-3</link>
		<comments>https://blog.gmem.cc/framework7-study-note-3#comments</comments>
		<pubDate>Tue, 16 Feb 2016 07:51:54 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[HTML]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Mobile]]></category>
		<category><![CDATA[F7]]></category>
		<category><![CDATA[学习笔记]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=10972</guid>
		<description><![CDATA[<p>Template7 Template7是一个移动优先（mobile-first）的模板引擎，其使用Handlebars风格的语法。 T7非常轻量，速度很快。F7已经内置T7，无需包含额外的JS文件。T7也可以单独使用。 使用T7时，性能瓶颈会出现在编译阶段——把字符串编译为纯粹的JavaScript函数时，亦即调用[crayon-69e29a413ac09346524109-i/] 时。因此，不要编译同一个模板多次。 语法 变量 [crayon-69e29a413ac0d765847740-i/]  输出当前上下文中title变量的值 [crayon-69e29a413ac0f348204097-i/] 输出父上下文中title变量的值 [crayon-69e29a413ac11556376356-i/] 输出父父上下文中title变量的值 [crayon-69e29a413ac13996359880-i/] 输出当前上下文对象 [crayon-69e29a413ac15852492280-i/]  输出当前上下文中person变量name属性的值 [crayon-69e29a413ac17371117012-i/] 输出父上下文中person变量name属性的值 [crayon-69e29a413ac19448889413-i/] 访问额外数据变量，这些数据变量可以在助手中访问 块表达式 [crayon-69e29a413ac1c662375737-i/] 块表达式开始 [crayon-69e29a413ac1e439158955-i/] 块表达式结束 [crayon-69e29a413ac20180218578-i/] 反转块表达式开始 [crayon-69e29a413ac22860524384-i/] 块表达式开始，提供一个哈希参数（Hash argument），此参数的名/值分别为name/value 助手 <a class="read-more" href="https://blog.gmem.cc/framework7-study-note-3">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/framework7-study-note-3">Framework7学习笔记（三）：高级</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">Template7</span></div>
<p>Template7是一个移动优先（mobile-first）的模板引擎，其使用<a href="http://handlebarsjs.com/">Handlebars</a>风格的语法。</p>
<p>T7非常轻量，速度很快。F7已经内置T7，无需包含额外的JS文件。T7也可以单独使用。</p>
<p>使用T7时，<span style="background-color: #c0c0c0;">性能瓶颈</span>会出现在编译阶段——把字符串编译为纯粹的JavaScript函数时，亦即调用<pre class="crayon-plain-tag">Template7.compile()</pre> 时。因此，<span style="background-color: #c0c0c0;">不要编译同一个模板多次</span>。</p>
<div class="blog_h2"><span class="graybg">语法</span></div>
<div class="blog_h3"><span class="graybg">变量</span></div>
<ol>
<li><pre class="crayon-plain-tag">{{title}}</pre>  输出当前上下文中title变量的值</li>
<li><pre class="crayon-plain-tag">{{../title}}</pre> 输出父上下文中title变量的值</li>
<li><pre class="crayon-plain-tag">{{../../title}}</pre> 输出父父上下文中title变量的值</li>
<li><pre class="crayon-plain-tag">{{this}}</pre> 输出当前上下文对象</li>
<li><pre class="crayon-plain-tag">{{person.name}}</pre>  输出当前上下文中person变量name属性的值</li>
<li><pre class="crayon-plain-tag">{{../person.name}}</pre> 输出父上下文中person变量name属性的值</li>
<li><pre class="crayon-plain-tag">{{@index}}</pre> 访问额外数据变量，这些数据变量可以在助手中访问</li>
</ol>
<div class="blog_h3"><span class="graybg">块表达式</span></div>
<ol>
<li><pre class="crayon-plain-tag">{{#helperName}}</pre> 块表达式开始</li>
<li><pre class="crayon-plain-tag">{{/helperName}}</pre> 块表达式结束</li>
<li><pre class="crayon-plain-tag">{{else}}</pre> 反转块表达式开始</li>
<li><pre class="crayon-plain-tag">{{#helperName name="value"}}</pre> 块表达式开始，提供一个哈希参数（Hash argument），此参数的名/值分别为name/value</li>
</ol>
<div class="blog_h3"><span class="graybg">助手</span></div>
<p>举例：<pre class="crayon-plain-tag">{{join myArray delimiter=", "}} </pre> 表示知晓join助手，传递上下文变量myArray以及哈希参数<span style="color: #000000;">delimiter。</span></p>
<div class="blog_h2"><span class="graybg">内置助手</span></div>
<div class="blog_h3"><span class="graybg">{{#each}}...{{else}}...{{/each}}</span></div>
<p>用于遍历传入的数组的元素/对象的属性。在此助手内部，可以访问额外变量：</p>
<ol>
<li> <pre class="crayon-plain-tag">@index</pre> 仅在遍历数组时有效，当前迭代的索引</li>
<li><pre class="crayon-plain-tag">@first</pre> 仅在遍历数组时有效，当前是否第一个元素</li>
<li><pre class="crayon-plain-tag">@last</pre> 仅在遍历数组时有效，当前是否最后一个元素</li>
<li><pre class="crayon-plain-tag">@key</pre> 仅在遍历对象时有效，当前属性的名称</li>
</ol>
<p>遍历数组的例子：</p>
<pre class="crayon-plain-tag">&lt;script type="text/template7" id="example"&gt;
    &lt;p&gt;People list: &lt;/p&gt;
    &lt;ul&gt;
        {{#each people}}
        &lt;li&gt;{{firstName}} {{lastName}}&lt;/li&gt;
        {{else}}
        &lt;li&gt;No people&lt;/li&gt;   
        {{/each}} 
    &lt;/ul&gt;
&lt;/script&gt;

&lt;script type="text/javascript"&gt;
    app.templates.example( {
        people : [ {
            firstName : 'Alex',
            lastName : 'Wong'
        }, {
            firstName : 'Meng',
            lastName : 'Lee'
        }, ]
    } );
&lt;/script&gt;
&lt;!-- 输出如下： --&gt;
&lt;p&gt;People list: &lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;Alex Wong&lt;/li&gt;
  &lt;li&gt;Meng Lee&lt;/li&gt;
&lt;/ul&gt;   

&lt;script type="text/javascript"&gt;
    app.templates.example( {} );
&lt;/script&gt;
&lt;!-- 输出如下： --&gt;
&lt;p&gt;People list: &lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;No people&lt;/li&gt;
&lt;/ul&gt;</pre>
<p>遍历对象的例子：</p>
<pre class="crayon-plain-tag">&lt;script type="text/template7" id="example"&gt;
    &lt;p&gt;People list: &lt;/p&gt;
    &lt;ul&gt;
        {{#each alex}}
        &lt;li&gt;{{@key}}: {{this}}&lt;/li&gt;
        {{/each}} 
    &lt;/ul&gt;
&lt;/script&gt;

&lt;script type="text/javascript"&gt;
    app.templates.example( {
        alex : {
            firstName : 'Alex'
            lastName : 'Wong'
        }
    } );
&lt;/script&gt;
&lt;!-- 输出如下： --&gt;
&lt;p&gt;People list:&lt;/p&gt;
&lt;ul&gt;
    &lt;li&gt;firstName: Alex&lt;/li&gt;
    &lt;li&gt;lastName: Wong&lt;/li&gt;
&lt;/ul&gt;</pre>
<div class="blog_h3"><span class="graybg">{{#if}}...{{else}}...{{/if}}</span></div>
<p> 用于进行分支判断，只有传入的上下文变量<span style="background-color: #c0c0c0;">不是false</span>时，才会渲染if分支中的内容。下面是一个例子：</p>
<pre class="crayon-plain-tag">&lt;script type="text/template7" id="example"&gt;
    &lt;a href="#" {{#if active}}class="active"{{/if}}&gt;{{title}}&lt;/a&gt;
&lt;/script&gt;

&lt;script type="text/javascript"&gt;
    app.templates.example( {
        active : true,
        title : 'Link'
    } );
&lt;/script&gt;
&lt;!-- 输出如下： --&gt;
&lt;a href="#" class="active"&gt;Link&lt;/a&gt;</pre>
<div class="blog_h3"><span class="graybg">{{#unless}}...{{else}}...{{/unless}} </span></div>
<p>与上一个助手类似，但是在传入的上下文变量<span style="background-color: #c0c0c0;">是false时</span>，渲染unless分支的内容。 </p>
<div class="blog_h3"><span class="graybg">{{#with}}...{{/with}}</span></div>
<p>用于修改上下文对象。下面是一个例子：</p>
<pre class="crayon-plain-tag">&lt;script type="text/template7" id="example"&gt;
    {{#with a}}
    &lt;a href="#" {{#if active}}class="active"{{/if}}&gt;{{title}}&lt;/a&gt;
    {{/with}}
&lt;/script&gt;

&lt;script type="text/javascript"&gt;
    app.templates.example( {
        a : {
            active : true,
            title : 'Link'
        }
    } );
&lt;/script&gt;
&lt;!-- 输出如下： --&gt;
&lt;a href="#" class="active"&gt;Link&lt;/a&gt;</pre>
<div class="blog_h3"><span class="graybg">{{#variableName}}...{{/variableName}}</span></div>
<p>如果块表达式中的助手名称是上下文中的变量，则效果与<pre class="crayon-plain-tag">{{#each variableName}}</pre> 一致。</p>
<div class="blog_h3"><span class="graybg">{{join delimiter=""}}</span></div>
<p>基于分隔符把数组元素连接为字符串。</p>
<div class="blog_h3"><span class="graybg">{{escape}} </span></div>
<p>返回转移后的HTML字符串，HTML特殊字符被转写为<pre class="crayon-plain-tag">&amp;**;</pre> 形式。</p>
<div class="blog_h3"><span class="graybg">{{js "expression"}}</span></div>
<p>立即执行一个JavaScript表达式，并把返回值输出。</p>
<p>要访问当前上下文对象，必须使用<pre class="crayon-plain-tag">this</pre> 。</p>
<div class="blog_h2"><span class="graybg">定制新助手</span></div>
<p>T7允许通过下面的方法注册新的助手：</p>
<pre class="crayon-plain-tag">/**
 * @param name 助手的名称
 * @param helper 助手函数，用于处理上下文
 */
Template7.registerHelper(name, helper);
/**
 * 助手函数规格，在此函数中，this指向表达式的执行上下文
 * @param condition 包含传递进来的上下文或条件，例如{{#if true}}中condition为true
 * @param options 包含若干属性和方法：
 *      hash 传递给助手的哈希参数对象
 *      fn 调用此方法，把块表达式的内容传递给编译器处理
 *      inverse 调用此方法，把反转块表达式（{{else}}）的内容传递给编译器处理
 *      data 包含额外的表达式数据，例如@index
 * @return 可选，助手输出的内容
 */
function helper(condition, options) {}</pre>
<p>若要解除注册，可以调用：<pre class="crayon-plain-tag">Template7.unregisterHelper(name)</pre> 。</p>
<div class="blog_h3"><span class="graybg">示例</span></div>
<pre class="crayon-plain-tag">/* 简单的join助手 */
Template7.registerHelper( 'join', function( arr, options ) {
    if ( typeof arr === 'function' ) arr = arr.call( this );
    //返回值直接输出到助手在模板中的位置
    return arr.join( options.hash.delimiter );
} );

/* ifeq助手：{{#ifeq var1 var2}}..{{else}}..{{/if}} */
Template7.registerHelper( 'ifeq', function( var1, var2, options ) {
    if ( var1 == var2 ) {
        return options.fn( this, options.data );
    }
    else {
        return options.inverse( this, options.data );
    }
} );</pre>
<div class="blog_h2"><span class="graybg">全局上下文</span></div>
<p>全局上下文可以在任何地方访问，使用<pre class="crayon-plain-tag">Template7.global</pre> 可以指定全局上下文，例如：</p>
<pre class="crayon-plain-tag">Template7.global = {
    os : 'iOS',
    browser : 'Chrome',
    username : 'Alex'
};</pre>
<p>访问全局上下文，必须使用前缀<pre class="crayon-plain-tag">{{@global.}}</pre> 。</p>
<div class="blog_h2"><span class="graybg">根上下文</span></div>
<p>某些时候需要访问传递给模板的根上下文对象，此时可以使用<pre class="crayon-plain-tag">{{@root}}</pre> 变量。例如：</p>
<pre class="crayon-plain-tag">&lt;script type="text/template7" id="example"&gt;
{{#each persons}}
    &lt;h2&gt;{{name}}&lt;/h2&gt;
    {{#if @root.showHobby}}
        &lt;h3&gt;Hobby:&lt;/h3&gt;
        &lt;ul&gt;
            {{#each hobby}}
                &lt;li&gt;{{this}}&lt;/li&gt;
            {{/each}}
        &lt;/ul&gt;
    {{/if}}
{{/each}} 
&lt;/script&gt;

&lt;script type="text/javascript"&gt;
    app.templates.example( {
        persons : [ {
            name : 'Alex',
            hobby : [ 'Clectronics', 'Biology' ]
        }, {
            name : 'Meng',
            hobby : [ 'Accountancy' ]
        },

        ],
        showHobby : true
    } );
&lt;/script&gt;</pre>
<div class="blog_h2"><span class="graybg">自动编译</span></div>
<p>在特殊标签中定义的模板，可以被F7自动编译：</p>
<pre class="crayon-plain-tag">&lt;!-- 
    type="text/template7" 告诉F7这是一个T7模板，可以被自动编译
    id="myTemplate" 指定模板的标识符，后续可以依据此标识符访问
--&gt;
&lt;script type="text/template7" id="iTemplate"&gt;
    &lt;p&gt;Hello, my name is {{name}} and i am {{age}} years old&lt;/p&gt;
&lt;/script&gt;</pre>
<p>需要在应用初始化参数中启用自动编译功能：</p>
<pre class="crayon-plain-tag">var app = new Framework7( {
    // 在初始化阶段自动编译模板
    precompileTemplates : true,
} );</pre>
<div class="blog_h3"><span class="graybg">访问预编译模板</span></div>
<p>所有编译好的模板会存放到对象<pre class="crayon-plain-tag">Template7.templates</pre> 中，<pre class="crayon-plain-tag">app.templates</pre> 是此对象的别名。在应用初始化后，可以这样使用模板：</p>
<pre class="crayon-plain-tag">var iHTML = Template7.templates.iTemplate( {
    name : 'Alex Wong',
    age : 30
} );</pre>
<div class="blog_h2"><span class="graybg">片段（Partials）</span></div>
<p>片段就是普通模板，只是它们供其它模板调用。通过片段，可以实现模板重用。通过下面的方法可以注册/解除注册片段：</p>
<pre class="crayon-plain-tag">/**
 * 注册片段
 * 
 * @param name 片段的名称
 * @param template 片段的内容，字符串
 */
Template7.registerPartial( name, template );
/**
 * 解除注册片段
 * 
 * @param name 片段的名称
 */
Template7.unregisterPartial( name );</pre>
<p>通过特殊的助手：<pre class="crayon-plain-tag">{{&gt; "partialName"}}</pre> 可以调用既有的片段。 </p>
<div class="blog_h3"><span class="graybg">递归片段</span></div>
<p>T7片段的一个强大特性是，<span style="background-color: #c0c0c0;">片段可以调用自身</span>。下面是一个嵌套评论（针对评论的回复）的例子：</p>
<pre class="crayon-plain-tag">/* 此模板只包含一个对片段的调用 */
var template = '{{&gt; "comments"}}';
/* 注册片段  */
Template7.registerPartial(
    'comments', 
    '&lt;ul&gt;' + 
        '{{#each comments}}' +
            '&lt;li&gt;' +
            '&lt;h2&gt;{{author}}&lt;/h2&gt;' +
            '&lt;p&gt;{{text}}&lt;/p&gt;' +
            '{{#if comments}}{{&gt; "comments"}}{{/if}}' + //如果有针对评论的评论，则递归调用 
            '&lt;/li&gt;' +
        '{{/each}}' +
    '&lt;/ul&gt;'
);
// 编译模板
var compiledTemplate = Template7.compile( template );

// 渲染模板
var output = compiledTemplate( {
    comments : [ {
        author : 'Meng Lee',
        text : 'Good article!',
        comments : [ {
            author : 'Alex Wong',
            text : 'Thanks!'
        }, {
            author : 'CaiCai Wong',
            text : 'Very funny.'
        } ]
    }, {
        author : 'WenJun Wong',
        text : 'Not good!'
    } ]
} );</pre>
<p>输出结果如下： </p>
<pre class="crayon-plain-tag">&lt;ul class="comments"&gt;
    &lt;li&gt;
        &lt;h2&gt;Meng Lee&lt;/h2&gt;
        &lt;p&gt;Good article!&lt;/p&gt;
        &lt;ul class="comments"&gt;
            &lt;li&gt;
                &lt;h2&gt;Alex Wong&lt;/h2&gt;
                &lt;p&gt;Thanks!&lt;/p&gt;
            &lt;/li&gt;
            &lt;li&gt;
                &lt;h2&gt;CaiCai Wong&lt;/h2&gt;
                &lt;p&gt;Very funny.&lt;/p&gt;
            &lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;
        &lt;h2&gt;WenJun Wong&lt;/h2&gt;
        &lt;p&gt;Not good!&lt;/p&gt;
    &lt;/li&gt;
&lt;/ul&gt;</pre>
<div class="blog_h2"><span class="graybg"><a id="template7-pages"></a>Template7页面</span></div>
<p>在<a href="/framework7-study-note-1#navigation">Framework7学习笔记（一）</a>中我们提到过F7处理页面的4种方式，最后一种Template7页面在这里讲解。</p>
<p>F7允许把新的Ajax页面、Dynamic页面<span style="background-color: #c0c0c0;">作为Template7模板来解析</span>，要启用此功能，需要提供应用初始化参数：</p>
<pre class="crayon-plain-tag">var myApp = new Framework7({
    template7Pages: true
});</pre>
<div class="blog_h3"><span class="graybg">模板上下文</span></div>
<p>要解析模板，必须提供必要的上下文（数据）对象。可以通过应用初始化参数template7Data设置全局的上下文：</p>
<pre class="crayon-plain-tag">var myApp = new Framework7({
    template7Pages: true,
    //指定上下文对象
    template7Data: {
        // 下面这个上下文专用于URL为about.html的页面（模板）
        'url:about.html': {
            name: 'John Doe',
            age: 38,
            company: 'Apple',
            position: 'Developer'
        },
        // 下面这个上下文专用于名称为contacts（data-page="contacts"）的页面（模板）
        'page:contacts': {
            tel: '(999)-111-22-33',
            email: 'contact@john.doe'
        },
 
        // 简单数据对象（Plain data object）
        'languages': {
            'frontend': [
                { name:'JavaScript', desc: 'Most commonly used as part of web browsers' },
                { name:'CSS' ,desc: 'Style sheet language used for describing the look and formatting of a document'},
            ],
            'backend': [
                { name: 'PHP', desc: 'Server-side scripting language designed for web development' },
                { name: 'Ruby', desc: 'Dynamic, reflective, object-oriented, general-purpose programming language'}
            ]
        }
    }
});</pre>
<p>可以看到，上下文对象分为三种：</p>
<ol>
<li>url:*，用于特定URL的页面（模板）的上下文</li>
<li>page:*，用于特定名称页面（模板）的上下文</li>
<li>简单数据对象：用于提供可供不同页面（模板）重用的、定制化的上下文</li>
</ol>
<p>在<span style="background-color: #c0c0c0;">任何时候你都可以修改</span>模板上下文对象，你可以通过全局对象<pre class="crayon-plain-tag">Templates.data</pre> 或者其别名<pre class="crayon-plain-tag">app.template7Data</pre> 修改之。</p>
<div class="blog_h3"><span class="graybg">简单数据对象</span></div>
<p>template7Data中提供的简单数据对象，用于提供复杂的、定制化的上下文。利用普通链接和<pre class="crayon-plain-tag">data-context-name</pre> 属性，你可以指定该链接要加载的页面（模板）所使用的上下文对象，此对象可以是一个简单数据对象，或者其<span style="background-color: #c0c0c0;">点号导航的子属性</span>（[]语法不支持）。</p>
<p>下面的代码示意了如何使用简单数据对象作为模板上下文：</p>
<pre class="crayon-plain-tag">&lt;!-- 模板languages.html将使用上下文app.template7Data.languages来渲染  --&gt;
&lt;a href="languages.html" class="item-link item-content" data-context-name="languages"&gt;
    &lt;div class="item-inner"&gt;
        &lt;div class="item-title"&gt;Languages&lt;/div&gt;
    &lt;/div&gt;
&lt;/a&gt;</pre><br />
<pre class="crayon-plain-tag">&lt;div class="page" data-page="languages"&gt;
  &lt;div class="page-content"&gt;
    &lt;div class="content-block-title"&gt;Frontend&lt;/div&gt;
    &lt;div class="list-block"&gt;
      &lt;ul&gt;
        &lt;!-- 遍历app.template7Data.languages.frontend --&gt;
        {{#each this.frontend}}
        &lt;li&gt;
          &lt;!-- 嵌套的模板，支持基于点号导航（languages.frontend.1是合法的）来访问子对象 --&gt;
          &lt;a href="language-details.html" class="item-link item-content" data-context-name="languages.frontend.{{@index}}"&gt;
              &lt;div class="item-inner"&gt;
                &lt;div class="item-title"&gt;{{this.name}}&lt;/div&gt;
              &lt;/div&gt;
          &lt;/a&gt;
        &lt;/li&gt;
        {{/each}}
      &lt;/ul&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;</pre><br />
<pre class="crayon-plain-tag">&lt;div class="page" data-page="language-details"&gt;
  &lt;div class="page-content"&gt;
    &lt;div class="content-block-title"&gt;{{name}}&lt;/div&gt;
    &lt;div class="content-block"&gt;
      &lt;p&gt;{{desc}}&lt;/p&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;</pre>
<p>你也可以通过导航/路由 API来指定上下文对象：</p>
<pre class="crayon-plain-tag">mainView.router.load({
    url: 'language-details.html',
    contextName: 'languages.frontend.0'
})</pre>
<div class="blog_h3"><span class="graybg">定制的上下文</span></div>
<p>除了通过template7Data指定的全局上下文以外，你还可以使用即时指定的定制上下文。HTML方式：</p>
<pre class="crayon-plain-tag">&lt;!-- 赋值串行化的JSON对象给data-context属性 --&gt;
&lt;a href='contacts.html' data-context='{"tel": "(999)-111-22-33", "email": "contact@john.doe"}'&gt;Contacts&lt;/a&gt;</pre>
<p>JavaScript方式：</p>
<pre class="crayon-plain-tag">mainView.router.load( {
    url : 'contacts.html',
    context : {
        tel : '(999)-111-22-33',
        email : 'contact@john.doe'
    }
} );</pre>
<div class="blog_h3"><span class="graybg">直接加载模板</span></div>
<p>F7会自动编译<pre class="crayon-plain-tag">&lt;script id="templateName" type="text/template7"&gt;</pre> 声明的模板。要将其作为Template7页面使用，可以通过HTML方式：</p>
<pre class="crayon-plain-tag">&lt;a href="#" data-template="templateName"&gt;&lt;/a&gt;
&lt;a href="#" data-template="templateName" data-context='{"name": "value"}'&gt;&lt;/a&gt;</pre>
<p>或者JavaScript方式：</p>
<pre class="crayon-plain-tag">mainView.router.load( {
    template : Template7.templates.templateName,
    context : {
        name : 'value'
    }
} );</pre>
<div class="blog_h3"><span class="graybg">传递URL参数 </span></div>
<p>特殊的上下文属性<pre class="crayon-plain-tag">url_query</pre> 用于访问URL上的GET参数：</p>
<pre class="crayon-plain-tag">&lt;a href="person.html?firstname=Alex&amp;lastname=Wong&amp;age=30"&gt;Alex&lt;/a&gt;</pre><br />
<pre class="crayon-plain-tag">&amp;nbsp;&lt;div class="page" data-page="person"&gt;
    &lt;div class="page-content"&gt;
        &lt;p&gt;Hello, my name is {{url_query.firstname}} {{url_query.lastname}}. I am {{url_query.age}} years old.&lt;/p&gt;
    &lt;/div&gt;
&lt;/div&gt;</pre>
<div class="blog_h1"><span class="graybg">插件API</span></div>
<p> Framework7提供了简单易用的插件API，利用这些API你可以扩展自己的F7插件。</p>
<div class="blog_h2"><span class="graybg">注册新插件</span></div>
<p>只需要为<pre class="crayon-plain-tag">Framework7.prototype.plugins</pre> 添加一个属性，即可注册新插件。例如：</p>
<pre class="crayon-plain-tag">/**
 * myPlugin 新插件的名字
 * app 已初始化的F7应用对象
 * params 可选的插件参数
 */
Framework7.prototype.plugins.myPlugin = function( app, params ) {
    //插件代码
};</pre>
<p>要引用新插件，只需要将其JS文件包含到HTML中，位于F7主JS文件之后：</p>
<pre class="crayon-plain-tag">&lt;script src="path/to/framework7.js"&gt;&lt;/script&gt;
&lt;script src="path/to/framework7.myplugin.js"&gt;&lt;/script&gt;</pre>
<div class="blog_h2"><span class="graybg">向插件传递参数</span></div>
<p>在F7应用初始化的时候，可以向插件传递参数：</p>
<pre class="crayon-plain-tag">var myApp = new Framework7( {
    pushState : true,
    /*
     * 为myPlugin传递参数
     */
    myPlugin : {
        foo : 'bar'
    }
} );</pre>
<div class="blog_h2"><span class="graybg">编写插件代码</span></div>
<p>F7插件系统包含三种扩展机制：</p>
<ol>
<li>hooks：与通常的回调类似，由F7在框架的很多核心部分调用。可以在特定时刻/场景执行插件代码、处理数据</li>
<li>prevents：用于阻止F7默认行为，例如阻止默认页面动画而使用自己的</li>
<li>processes：类似于预处理器，每个处理方法可以接收被处理数据</li>
</ol>
<p>插件函数的返回值必须是一个对象，其包括hooks、prevents和processes属性。例如：</p>
<pre class="crayon-plain-tag">return {
    hooks : {
        appInit : function(){}
    }
};</pre>
<div class="blog_h3"><span class="graybg">Hooks</span></div>
<p>可用的钩子包括：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 35%; text-align: center;">钩子</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>appInit</td>
<td>当应用完全初始化后调用</td>
</tr>
<tr>
<td>navbarInit (navbar, pageData)</td>
<td>和navbarInit事件一样</td>
</tr>
<tr>
<td>pageInit (pageData)</td>
<td>和navbarInit事件/回调一样</td>
</tr>
<tr>
<td>pageBeforeInit (pageData)</td>
<td>和pageBeforeInit事件/回调一样</td>
</tr>
<tr>
<td>pageBeforeAnimation (pageData)</td>
<td>和pageBeforeAnimation事件/回调一样</td>
</tr>
<tr>
<td>pageAfterAnimation (pageData)</td>
<td>和pageAfterAnimation事件/回调一样</td>
</tr>
<tr>
<td>pageBeforeRemove (pageData)</td>
<td>和pageBeforeRemove事件/回调一样</td>
</tr>
<tr>
<td>addView (view)</td>
<td>当用户通过app.addView()添加新的视图时触发，入参是已实例化的视图对象</td>
</tr>
<tr>
<td>loadPage (view, url, content)</td>
<td>在新页面开始被加载时调用，此时页面尚未加入到DOM中</td>
</tr>
<tr>
<td>goBack (view, url, preloadOnly)</td>
<td>在返回操作开始前调用 </td>
</tr>
<tr>
<td>swipePanelSetTransform (views, panel, percentage)</td>
<td>侧面板滑出/滑入的过程中调用 </td>
</tr>
</tbody>
</table>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/framework7-study-note-3">Framework7学习笔记（三）：高级</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/framework7-study-note-3/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Framework7学习笔记（二）：组件</title>
		<link>https://blog.gmem.cc/framework7-study-note-2</link>
		<comments>https://blog.gmem.cc/framework7-study-note-2#comments</comments>
		<pubDate>Thu, 21 Jan 2016 14:12:05 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[HTML]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Mobile]]></category>
		<category><![CDATA[F7]]></category>
		<category><![CDATA[学习笔记]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=10767</guid>
		<description><![CDATA[<p>导航栏和工具栏 为F7应用添加导航栏和工具栏之前，需要决定使用何种导航/工具栏布局。F7支持三种导航/工具栏布局。 导航/工具栏的布局类型 “静态”布局 这是最少使用的一种布局，使用该布局时，导航/工具栏随着页面滚动，它们仅仅是页面的一部分，每个页面拥有自己的导航/工具栏： [crayon-69e29a413c820468511489/] “固定”布局 与静态布局类似，每个页面拥有自己的导航/工具栏，但是导航/工具栏不随页面滚动，它们分别固定在顶部/底部，始终可见。启用固定布局很简单，只需要在页面元素上添加样式类： [crayon-69e29a413c826176425240/] “贯穿”布局 这是最常使用的一种方式，一个视图中的所有页面共享导航/工具栏。该布局支持动态导航栏（Dynamic Navbar）： [crayon-69e29a413c829529645086/] 混合布局 F7支持在一个视图内混合使用多种布局方式。例如你可以使用贯穿的导航栏 + 固定的工具栏： [crayon-69e29a413c82b552189260/] 不使用导航/工具栏 你也可以不使用导航栏和（或）工具栏，只要不声明它们、同时不在page/pages/view元素上添加冲突的样式类：navbar-fixed、navbar-through、toolbar-fixed、 toolbar-through即可。 导航栏 <a class="read-more" href="https://blog.gmem.cc/framework7-study-note-2">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/framework7-study-note-2">Framework7学习笔记（二）：组件</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">导航栏和工具栏</span></div>
<p>为F7应用添加导航栏和工具栏之前，需要决定使用何种导航/工具栏布局。F7支持三种导航/工具栏布局。</p>
<div class="blog_h2"><span class="graybg">导航/工具栏的布局类型</span></div>
<div class="blog_h3"><span class="graybg">“静态”布局</span></div>
<p>这是最少使用的一种布局，使用该布局时，导航/工具栏<span style="background-color: #c0c0c0;">随着页面滚动</span>，它们仅仅是页面的一部分，每个页面拥有自己的导航/工具栏：</p>
<pre class="crayon-plain-tag">&lt;!-- 这是一个页面 --&gt;
&lt;div data-page="index" class="page"&gt;
    &lt;div class="page-content"&gt;
        &lt;!-- 顶部导航栏开始 --&gt;
        &lt;div class="navbar"&gt;
            &lt;div class="navbar-inner"&gt;
                &lt;div class="center"&gt;Awesome App&lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;!-- 导航栏结束--&gt;
        &lt;p&gt;页面主体内容&lt;/p&gt;
        &lt;!-- 底部工具栏开始 --&gt;
        &lt;div class="toolbar"&gt;
            &lt;div class="toolbar-inner"&gt;Hello&lt;/div&gt;
        &lt;/div&gt;
        &lt;!-- 底部工具栏结束--&gt;
    &lt;/div&gt;
&lt;/div&gt;</pre>
<div class="blog_h3"><span class="graybg">“固定”布局</span></div>
<p>与静态布局类似，每个页面拥有自己的导航/工具栏，但是导航/工具栏不随页面滚动，它们分别固定在顶部/底部，始终可见。启用固定布局很简单，只需要在页面元素上添加样式类：</p>
<pre class="crayon-plain-tag">&lt;div data-page="index" class="page navbar-fixed toolbar-fixed"&gt;</pre>
<div class="blog_h3"><span class="graybg">“贯穿”布局</span></div>
<p>这是最常使用的一种方式，一个视图中的<span style="background-color: #c0c0c0;">所有页面共享</span>导航/工具栏。该布局支持动态导航栏（Dynamic Navbar）：</p>
<pre class="crayon-plain-tag">&lt;div class="view view-main"&gt;
    &lt;!-- 视图唯一的导航栏 --&gt;
    &lt;div class="navbar"&gt;&lt;div class="navbar-inner"&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;!-- 导航栏结束--&gt;
    &lt;!-- 页面容器（pages）元素需要额外的"navbar-through"、"toolbar-through"样式类，分别表示启用贯穿导航栏、工具栏 --&gt;
    &lt;div class="pages navbar-through toolbar-through"&gt;
        &lt;!-- 页面 --&gt;
        &lt;div data-page="index" class="page"&gt;&lt;div class="page-content"&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
    &lt;!-- 视图唯一的工具栏 --&gt;
    &lt;div class="toolbar"&gt;&lt;/div&gt;
    &lt;!--工具栏结束--&gt;
&lt;/div&gt;</pre>
<div class="blog_h3"><span class="graybg">混合布局</span></div>
<p>F7支持在一个视图内混合使用多种布局方式。例如你可以使用贯穿的导航栏 + 固定的工具栏：</p>
<pre class="crayon-plain-tag">&lt;div class="view view-main"&gt;
    &lt;!-- 视图唯一的贯穿导航栏 --&gt;
    &lt;div class="navbar"&gt;&lt;div class="navbar-inner"&gt;&lt;div class="center"&gt;&lt;/div&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;!-- 为所有页面声明：使用贯穿导航栏、固定工具栏 --&gt;
    &lt;div class="pages navbar-through toolbar-fixed"&gt;
        &lt;!-- 一个页面定义  --&gt;
        &lt;div data-page="index" class="page"&gt;
            &lt;div class="page-content"&gt;&lt;/div&gt;
            &lt;!-- 固定工具栏，因此每个页面都需要定义自己的 --&gt;
            &lt;div class="toolbar"&gt;&lt;div class="toolbar-inner"&gt;Hello&lt;/div&gt;&lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;</pre>
<div class="blog_h3"><span class="graybg">不使用导航/工具栏</span></div>
<p>你也可以不使用导航栏和（或）工具栏，只要<span style="background-color: #c0c0c0;">不声明它们、同时不在</span>page/pages/view元素上添加冲突的样式类：navbar-fixed、navbar-through、toolbar-fixed、 toolbar-through即可。</p>
<div class="blog_h2"><span class="graybg">导航栏</span></div>
<p>所谓导航栏，是位于屏幕顶部的，包含页面标题、导航元素的一个区域。<span style="background-color: #c0c0c0;">导航栏分为三个部分（左、中、右）</span>，这三个部分都可以包含任意HTML，但是F7建议这样使用导航栏： </p>
<ol>
<li>左侧部分：用于放置返回链接，可以是图标或者文本</li>
<li>中间部分：显示页面标题</li>
<li>右侧部分：类似于左侧，放置按钮或链接</li>
</ol>
<div class="blog_h3"><span class="graybg">导航栏内部布局</span></div>
<p>导航栏内部的结构很简单且自描述：</p>
<pre class="crayon-plain-tag">&lt;div class="navbar"&gt;
    &lt;div class="navbar-inner"&gt;
        &lt;div class="left"&gt;Left&lt;/div&gt;
        &lt;!-- 注意，center具有最低的宽度优先级，当空间不够时，center的内容自动被省略，确保左右正常显示 --&gt;
        &lt;!-- 如果center中存放文本，无法显示的内容使用省略号代替，如果放置了自定义元素，则需要小心处理 --&gt;
        &lt;div class="center"&gt;Center&lt;/div&gt;
        &lt;div class="right"&gt;Right&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;</pre>
<div class="blog_h3"><span class="graybg">导航栏与链接</span></div>
<p>可以在导航栏中添加多种链接：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">链接类型</td>
<td style="text-align: center;">说明与示例</td>
</tr>
</thead>
<tbody>
<tr>
<td>文本链接</td>
<td>
<pre class="crayon-plain-tag">&lt;div class="navbar"&gt;
    &lt;div class="navbar-inner"&gt;
        &lt;!-- 要在左右侧添加链接，仅需要为a元素添加link类 --&gt;
        &lt;div class="left"&gt;&lt;a href="#" class="link"&gt;左侧文本链接&lt;/a&gt;&lt;/div&gt;
        &lt;div class="center"&gt;Center&lt;/div&gt;
        &lt;div class="right"&gt;&lt;a href="#" class="link"&gt;右侧文本链接&lt;/a&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;</pre>
</td>
</tr>
<tr>
<td>多个链接</td>
<td>
<pre class="crayon-plain-tag">&lt;!-- 每个区域都可以包含多个链接 --&gt;
&lt;div class="left"&gt;
    &lt;a href="#" class="link"&gt;左侧文本链接1&lt;/a&gt;
    &lt;a href="#" class="link"&gt;左侧文本链接2&lt;/a&gt;
&lt;/div&gt;</pre>
</td>
</tr>
<tr>
<td>图标+文本链接</td>
<td>
<pre class="crayon-plain-tag">&lt;div class="left"&gt;
    &lt;a href="#" class="link"&gt; 
        &lt;!-- 用样式类icon标注图标，该样式类定义了背景图片 --&gt;
        &lt;i class="icon icon-back"&gt;&lt;/i&gt; 
        &lt;!-- 使用图标+文本链接时，必须把文本包装在span元素内 --&gt;
        &lt;span&gt;Back&lt;/span&gt;
    &lt;/a&gt;
&lt;/div&gt;</pre>
</td>
</tr>
<tr>
<td>图标链接</td>
<td>
<pre class="crayon-plain-tag">&lt;div class="left"&gt;
    &lt;!-- 对于只有图标的链接，需要添加icon-only类，该类确保链接占据44x44像素 --&gt;
    &lt;a href="#" class="link icon-only"&gt;
        &lt;i class="icon icon-back"&gt;&lt;/i&gt;
    &lt;/a&gt;
&lt;/div&gt;</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">与导航栏相关的方法</span></div>
<p>对于已初始化的视图，我们可以用JavaScript API操控其导航栏：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 30%; text-align: center;">方法</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>app.hideNavbar(navbar)</td>
<td rowspan="2">隐藏或显示指定的导航栏。navbar为代表目标导航栏的HTMLElement或者CSS选择器（字符串）</td>
</tr>
<tr>
<td>app.showNavbar(navbar)</td>
</tr>
<tr>
<td>view.hideNavbar()</td>
<td rowspan="2">隐藏或显示视图view的导航栏。示例：<br />
<pre class="crayon-plain-tag">var app = new Framework7();
var $ = Framework7.$;
var mainView = app.addView('.view-main');
$('.hide-navbar').on('click', function () {
    mainView.hideNavbar();
});
$('.show-navbar').on('click', function () {
    mainView.showNavbar();
});</pre>
</td>
</tr>
<tr>
<td>view.showNavbar()</td>
</tr>
<tr>
<td>view.sizeNavbars(viewContainer)</td>
<td>仅iOS主题。重新计算导航栏的位置。示例：<pre class="crayon-plain-tag">app.sizeNavbars('.view-main')</pre> </td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">自动隐藏导航栏</span></div>
<p>使用贯穿布局时，F7允许对某些Ajax页面自动隐藏/显示导航栏：</p>
<pre class="crayon-plain-tag">&lt;!-- 为此页面声明一个空白导航栏 --&gt;
&lt;div class="navbar"&gt;&lt;div class="navbar-inner"&gt;&lt;/div&gt;&lt;/div&gt;
&lt;!-- 同时为页面添加"no-navbar"样式类 --&gt;
&lt;div data-page="about" class="page no-navbar"&gt;
    &lt;div class="page-content"&gt;&lt;/div&gt;
&lt;/div&gt;</pre>
<div class="blog_h2"><span class="graybg">动态导航栏</span></div>
<p>注意动态导航栏仅适用于iOS主题 + 贯穿布局。所谓动态导航栏，在页面切换时，其内容具有<span style="background-color: #c0c0c0;">滑动和淡入淡出效果</span>。</p>
<div class="blog_h3"><span class="graybg">动态导航栏布局</span></div>
<p>动态导航栏的内部布局和普通导航栏没太大区别，只是你可以为导航栏的左中右三个部分添加额外的样式类。默认的，F7为这三个部分添加淡入淡出效果，如果要启用滑动效果，你可以添加样式类：</p>
<pre class="crayon-plain-tag">&lt;!-- 每个页面可以指定自己的动态导航栏，默认具有淡入淡出切换效果 --&gt;
&lt;!-- 动态导航栏声明在页面外部（从其它html加载为页面、或者创建动态页面时） --&gt;
&lt;div class="navbar"&gt;
    &lt;div class="navbar-inner"&gt;
        &lt;div class="left"&gt;
            &lt;a href="#" class="link"&gt;Left&lt;/a&gt;
        &lt;/div&gt;
        &lt;!-- 为中间部分添加滑动效果，注意整个应用的风格应当统一 --&gt;
        &lt;div class="center sliding"&gt;Home&lt;/div&gt;
        &lt;div class="right"&gt;
            &lt;a href="#" class="link"&gt;Right&lt;/a&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;div class="page" data-page="about"&gt;页面内容&lt;/div&gt;</pre>
<div class="blog_h3"><span class="graybg">给Back Icon添加动画</span></div>
<p>设置应用初始化参数<pre class="crayon-plain-tag">animateNavBackIcon: true</pre> 可以为后退按钮添加更多的动画效果，从而更像iOS 7的风格。</p>
<div class="blog_h3"><span class="graybg">动态导航栏事件</span></div>
<p>F7为动态导航栏添加了几种事件，这些事件的源（Target） 都是<pre class="crayon-plain-tag">&lt;div class="navbar-inner"&gt;</pre> ：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">事件</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>navbarBeforeInit</td>
<td>插入一个新的导航栏到DOM时，触发该事件</td>
</tr>
<tr>
<td>navbarInit</td>
<td>初始化一个导航栏后，触发该事件</td>
</tr>
<tr>
<td>navbarReinit</td>
<td>当缓存的导航栏变得再次可见时，触发该事件</td>
</tr>
<tr>
<td>navbarBeforeRemove</td>
<td>当导航栏即将从DOM中移除时，触发该事件</td>
</tr>
</tbody>
</table>
<p>下面的代码演示了该事件的使用：</p>
<pre class="crayon-plain-tag">$$(document).on('navbarInit', function (e) {
    var navbar = e.detail.navbar;
    // 可以访问导航栏对象的以下属性：
    // navbarContainer 链接到导航栏容器元素
    // navbarInnerContainer 链接到导航栏内部容器元素
    var page = e.detail.page
});</pre>
<div class="blog_h2"><span class="graybg">子导航栏（Sub Navbar） </span></div>
<p>当你需要在导航栏添加额外的元素时，可以使用子导航栏。当导航栏隐藏时，子导航栏仍然<span style="background-color: #c0c0c0;">保持可见</span>。 </p>
<p>在线演示：<a href="http://framework7.taobao.org/docs-demos/subnavbar.html">http://framework7.taobao.org/docs-demos/subnavbar.html</a></p>
<div class="blog_h3"><span class="graybg">子导航栏布局</span></div>
<pre class="crayon-plain-tag">&lt;div class="navbar-inner"&gt;
    &lt;div class="left"&gt;&lt;/div&gt;
    &lt;div class="center"&gt;&lt;/div&gt;
    &lt;div class="right"&gt;&lt;/div&gt;
    &lt;!-- 子导航栏 --&gt;
    &lt;div class="subnavbar"&gt;
        &lt;!-- 子导航栏内容，这里存放了三个按钮    --&gt;
        &lt;div class="buttons-row"&gt;
            &lt;a href="#tab1" class="button active"&gt;Tab 1&lt;/a&gt;
            &lt;a href="#tab2" class="button"&gt;Tab 2&lt;/a&gt;
            &lt;a href="#tab3" class="button"&gt;Tab 3&lt;/a&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;</pre>
<div class="blog_h2"><span class="graybg">工具栏</span></div>
<p>工具栏是屏幕上的一个固定区域，可以包含导航元素。不像导航栏，工具栏不包含任何部分，内部可以直接是若干链接。</p>
<div class="blog_h3"><span class="graybg">工具栏布局</span></div>
<pre class="crayon-plain-tag">&lt;div class="toolbar"&gt;
    &lt;div class="toolbar-inner"&gt;
        &lt;a href="#" class="link"&gt;Link 1&lt;/a&gt;
        &lt;a href="#" class="link"&gt;Link 2&lt;/a&gt;
        &lt;a href="#" class="link"&gt;Link 3&lt;/a&gt;
    &lt;/div&gt;
&lt;/div&gt;</pre>
<p>默认的，工具栏内的元素<span style="background-color: #c0c0c0;">均匀的分布</span>在工具栏中。 </p>
<div class="blog_h3"><span class="graybg">与工具栏相关的方法</span></div>
<p>以下方法可用于操控已初始化视图中的工具栏：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 28%; text-align: center;">方法</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>app.hideToolbar(toolbar)</td>
<td rowspan="2">隐藏或者显示指定的工具栏。<br />toolbar：目标工具栏对应的HTMLElement或者String（CSS选择器）</td>
</tr>
<tr>
<td>app.showToolbar(toolbar)</td>
</tr>
<tr>
<td>view.hideToolbar()</td>
<td rowspan="2">隐藏或者显示当前视图的工具栏</td>
</tr>
<tr>
<td>view.showToolbar()</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">自动隐藏工具栏</span></div>
<p>如果某个Ajax页面不需要显示工具栏，可以在页面上添加样式类：</p>
<pre class="crayon-plain-tag">&lt;!-- no-toolbar表示为该页面隐藏工具栏 --&gt;
&lt;div data-page="about" class="page no-toolbar"&gt;</pre>
<div class="blog_h3"><span class="graybg">底部工具栏</span></div>
<p>仅用于Material主题，对于iOS主题工具栏默认就在底部。</p>
<p>Material主题常常把工具栏/页签栏置于屏幕上面<span style="background-color: #c0c0c0;">，导航栏下面</span>。如果要改变此行为，可以为工具栏添加额外的样式类：</p>
<pre class="crayon-plain-tag">&lt;!--  toolbar-bottom导致Material主题的工具栏放置于屏幕底部 --&gt;
&lt;div class="toolbar toolbar-bottom"&gt;
    &lt;div class="toolbar-inner"&gt;&lt;/div&gt;
&lt;/div&gt;</pre>
<div class="blog_h2"><span class="graybg">页签栏</span></div>
<p>页签栏只是工具栏的特例，它包含图标（或者图标+文本），而不仅仅是文本。 </p>
<div class="blog_h3"><span class="graybg">页签栏布局</span></div>
<p>与工具栏类似，只是多一个样式类声明：</p>
<pre class="crayon-plain-tag">&lt;!-- 比工具栏多一个tabbar的样式类 --&gt;
&lt;div class="toolbar tabbar"&gt;
    &lt;div class="toolbar-inner"&gt;
        &lt;a href="#tab1" class="tab-link active"&gt;
            &lt;i class="icon demo-icon-1"&gt;&lt;/i&gt;
        &lt;/a&gt;
        &lt;a href="#tab2" class="tab-link"&gt;
            &lt;i class="icon demo-icon-2"&gt;&lt;/i&gt;
        &lt;/a&gt;
    &lt;/div&gt;
&lt;/div&gt;</pre>
<p>默认的，页签栏内的元素（链接）均匀的分布在页签栏中。但是要注意以下两点：</p>
<ol>
<li>在iPhone中，每个元素的大小一致，为屏幕宽度/元素个数</li>
<li>在iPad中，所有元素的被居中显示，元素最小宽度105px</li>
</ol>
<div class="blog_h3"><span class="graybg">为图标添加角标</span></div>
<pre class="crayon-plain-tag">&lt;div class="toolbar tabbar"&gt;
    &lt;div class="toolbar-inner"&gt;
        &lt;a href="#tab2" class="tab-link"&gt;
            &lt;i class="icon demo-icon-2"&gt;
                &lt;!-- 只需要图标元素内放置一个span元素，并添加badge样式，即可增加角标 --&gt;
                &lt;span class="badge bg-red"&gt;5&lt;/span&gt;&lt;!-- 红色背景角标，数字5 --&gt;
            &lt;/i&gt;
        &lt;/a&gt;
        &lt;a href="#tab4" class="tab-link"&gt;
            &lt;i class="icon demo-icon-4"&gt;
                &lt;span class="badge bg-green"&gt;15&lt;/span&gt;&lt;!-- 绿色背景角标，数字15 --&gt;
            &lt;/i&gt;
        &lt;/a&gt;
    &lt;/div&gt;
&lt;/div&gt;</pre>
<div class="blog_h3"><span class="graybg">图标+文本标签</span></div>
<p>类似的，为图标添加一个兄弟元素，可以显示文本标签：</p>
<pre class="crayon-plain-tag">&lt;a href="#tab1" class="tab-link active"&gt;
    &lt;i class="icon demo-icon-1"&gt;&lt;/i&gt;
    &lt;!-- 文本标签，默认位于图标下方 --&gt;
    &lt;span class="tabbar-label"&gt;Label 1&lt;/span&gt;
&lt;/a&gt;</pre>
<div class="blog_h3"><span class="graybg">可滚动页签栏</span></div>
<p>当页签栏中的元素非常多，屏幕宽度不足以显示时，应该使用可滚动页签栏，当手指划过页签栏时，会自动滚动以显示不可见元素：</p>
<pre class="crayon-plain-tag">&lt;!-- 只需要添加tabbar-scrollable样式类即可 --&gt;
&lt;div class="toolbar tabbar tabbar-scrollable"&gt;&lt;/div&gt;</pre>
<div class="blog_h3"><span class="graybg">与页签栏相关的方法</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 28%; text-align: center;">方法</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>app.hideToolbar(toolbar)</td>
<td rowspan="2">这两个方法同样适用于页签栏</td>
</tr>
<tr>
<td>app.showToolbar(toolbar)</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">自动隐藏页签栏</span></div>
<p>如果某个Ajax页面不需要显示页签栏，可以在页面上添加样式类：</p>
<pre class="crayon-plain-tag">&lt;!-- no-tabbar表示为该页面隐藏页签栏 --&gt;
&lt;div data-page="about" class="page no-tabbar"&gt;</pre>
<div class="blog_h2"><span class="graybg">滚动时隐藏导航/工具栏 </span></div>
<p>F7支持当页面<span style="background-color: #c0c0c0;">向下滚动时，自动隐藏</span>导航/工具栏。</p>
<p>要全局性的启用该功能，可以配置以下应用初始化参数：</p>
<ol>
<li>hideNavbarOnPageScroll：滚动时隐藏导航栏</li>
<li>hideToolbarOnPageScroll：滚动时隐藏工具栏</li>
<li>hideTabbarOnPageScroll：滚动时隐藏页签栏</li>
<li>showBarsOnPageScrollEnd ：滚动到页面底部时显示所有栏</li>
</ol>
<p>要针对某些特定页面启用该功能，可以在<pre class="crayon-plain-tag">&lt;div class="page-content"&gt;</pre> 元素上添加以下样式类：</p>
<ol>
<li>hide-bars-on-scroll</li>
<li> hide-navbar-on-scroll </li>
<li>hide-toolbar-on-scroll </li>
<li>hide-tabbar-on-scroll </li>
</ol>
<p>在全局启用时，可以使用下面的样式类，针对特定页面禁用此功能：</p>
<ol>
<li>keep-bars-on-scroll</li>
<li>keep-navbar-on-scroll</li>
<li>keep-toolbar-on-scroll</li>
<li>keep-tabbar-on-scroll </li>
</ol>
<div class="blog_h1"><span class="graybg">搜索栏</span></div>
<p>搜索栏可以对列表视图（List View）执行搜索，或者作为自定义搜索实现的视觉元素。</p>
<p>在线演示：<a href="http://framework7.taobao.org/docs-demos/searchbar.html">http://framework7.taobao.org/docs-demos/searchbar.html</a></p>
<div class="blog_h2"><span class="graybg">搜索栏布局</span></div>
<p>搜索栏应该放置在<pre class="crayon-plain-tag">.page</pre> 和<pre class="crayon-plain-tag">.page-content</pre> 元素之间：</p>
<pre class="crayon-plain-tag">&lt;!-- 页面 --&gt;
&lt;div class="page"&gt;
    &lt;!-- 搜索栏 --&gt;
    &lt;form class="searchbar"&gt;&lt;!-- 搜索栏容器元素，推荐使用form --&gt;
        &lt;div class="searchbar-input"&gt;
            &lt;input type="search" placeholder="Search"&gt;&lt;!-- 搜索栏 --&gt;
            &lt;a href="#" class="searchbar-clear"&gt;&lt;/a&gt;&lt;!-- 可选元素，用于清空输入值的按钮，可选 --&gt;
        &lt;/div&gt;
        &lt;a href="#" class="searchbar-cancel"&gt;Cancel&lt;/a&gt;&lt;!-- 取消搜索，隐藏搜索栏，清空结果过滤，可选 --&gt;
    &lt;/form&gt;
    &lt;!-- 搜索栏overlay，深色的、覆盖在页面内容上面的一层内容，可选 --&gt;
    &lt;div class="searchbar-overlay"&gt;&lt;/div&gt;
    
    &lt;!-- 页面内容部分 --&gt;
    &lt;div class="page-content"&gt;
        &lt;!-- searchbar-not-found：如果找不到匹配的元素，显示此div --&gt;
        &lt;div class="content-block searchbar-not-found"&gt;Nothing found&lt;/div&gt;
        &lt;!-- list-block-search：被搜索的列表块 --&gt;
        &lt;!-- searchbar-found：如果找到匹配的元素，显示此div --&gt;
        &lt;div class="list-block list-block-search searchbar-found"&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;</pre>
<div class="blog_h2"><span class="graybg">通过JS初始化搜索栏</span></div>
<p>除了搜索栏布局，我们还需要调用API初始化它：</p>
<pre class="crayon-plain-tag">/**
 * 初始化一个搜索栏
 * @param searchbarContainer 搜索栏容器元素，HTMLElement或者CSS选择器
 * @param parameters 搜索栏初始化参数
 * @return 已初始化的搜索栏实例
 */
app.searchbar(searchbarContainer, parameters);

//示例：
var mySearchbar = app.searchbar( '.searchbar', {
    searchList : '.list-block-search',
    searchIn : '.item-title'
} );</pre>
<div class="blog_h2">通过HTML初始化搜索栏</div>
<p>你也可以通过HTML初始化搜索栏：</p>
<pre class="crayon-plain-tag">&lt;div class="page"&gt;
    &lt;!-- 为搜索栏添加searchbar-ini属性，则F7自动初始化之 --&gt;
    &lt;!-- data-*属性提供初始化参数，连字符小写风格 --&gt;
    &lt;form class="searchbar searchbar-init" 
        data-search-list=".list-block-search" 
        data-search-in=".item-title" 
        data-found=".searchbar-found" 
        data-not-found=".searchbar-not-found"&gt;
        &lt;div class="searchbar-input"&gt;
            &lt;input type="search" placeholder="Search"&gt;
            &lt;a href="#" class="searchbar-clear"&gt;&lt;/a&gt;
        &lt;/div&gt;
        &lt;a href="#" class="searchbar-cancel"&gt;Cancel&lt;/a&gt;
    &lt;/form&gt;
    &lt;div class="searchbar-overlay"&gt;&lt;/div&gt;
    &lt;div class="page-content"&gt;
        &lt;div class="content-block searchbar-not-found"&gt;&lt;/div&gt;
        &lt;div class="list-block list-block-search searchbar-found"&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;</pre>
<p>通过下面的方式可以获得DOM关联的搜索栏实例：</p>
<pre class="crayon-plain-tag">var searchbar = $$('.searchbar')[0].f7Searchbar; //通过该属性访问
searchbar.search('Hello world');</pre>
<div class="blog_h2"><span class="graybg">搜索栏初始化参数</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">参数</td>
<td style="text-align: center;">类型和默认值</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>searchList</td>
<td>string / HTMLElement</td>
<td>被搜索的列表块（list block），HTMLElement或者CSS选择器</td>
</tr>
<tr>
<td>searchIn</td>
<td>string = '.item-title'</td>
<td>CSS选择器，指定列表视图元素的字段，搜索在该字段上进行，通常我们通过元素的标题搜索（.item-title）</td>
</tr>
<tr>
<td>found</td>
<td>string / HTMLElement</td>
<td>搜索结果可用时显示的元素的CSS选择器或者DOM，如果不指定，默认使用页面内的.searchbar-found元素</td>
</tr>
<tr>
<td>notFound</td>
<td>string / HTMLElement</td>
<td>类似上面，搜索结果不可用时显示的元素，默认使用页面内的.searchbar-not-found元素</td>
</tr>
<tr>
<td>overlay</td>
<td>string / HTMLElement</td>
<td>搜索栏overlay元素，默认使用页面内的.searchbar-overlay元素</td>
</tr>
<tr>
<td>ignore</td>
<td>string = '.searchbar-ignore'</td>
<td>CSS选择器，哪些元素被搜索栏忽略，总是显示在搜索结果中</td>
</tr>
<tr>
<td>customSearch</td>
<td>boolean = false</td>
<td>如果设置为true，则搜索栏不去搜索searchList指定的列表块。你可以定制自己的搜索逻辑，并手工处理数据展示</td>
</tr>
<tr>
<td>hideDividers</td>
<td>boolean = true</td>
<td>如果列表分隔器没有匹配的条目，则隐藏分隔器</td>
</tr>
<tr>
<td>hideGroups</td>
<td>boolean = true</td>
<td>如果组内没有匹配的条目，则隐藏组标题</td>
</tr>
<tr>
<td>onSearch</td>
<td>function (s)</td>
<td>搜索时（输入值改变）触发此回调</td>
</tr>
<tr>
<td>onEnable</td>
<td>function (s)</td>
<td>当搜索栏被启用时，触发此回调</td>
</tr>
<tr>
<td>onDisable</td>
<td>function (s)</td>
<td>当搜索栏被禁用（例如点击取消按钮、或者触碰searchbar-overlay元素）时，触发此回调</td>
</tr>
<tr>
<td>onClear</td>
<td>function (s)</td>
<td>当用户点击了搜索栏的clear元素时触发此回调</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">搜索栏方法、属性与事件</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">方法/属性/事件</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>params</td>
<td>搜索栏的所有初始化参数</td>
</tr>
<tr>
<td>query</td>
<td>当前搜索条件，即输入框的内容</td>
</tr>
<tr>
<td>searchList</td>
<td>搜索列表块对应的DOM元素</td>
</tr>
<tr>
<td>container</td>
<td>搜索栏容器DOM元素</td>
</tr>
<tr>
<td>input</td>
<td>搜索输入框DOM元素</td>
</tr>
<tr>
<td>active</td>
<td>boolean，当前搜索框是否处于激活状态</td>
</tr>
<tr>
<td>search(query)</td>
<td>依据入参条件执行搜索</td>
</tr>
<tr>
<td>enable()</td>
<td>启用搜索栏</td>
</tr>
<tr>
<td>disable()</td>
<td>禁用搜索栏</td>
</tr>
<tr>
<td>clear()</td>
<td>清空查询条件并更新搜素结果</td>
</tr>
<tr>
<td>destroy()</td>
<td>销毁该搜索栏实例</td>
</tr>
<tr>
<td>⚡search</td>
<td rowspan="4">这些事件的target都是搜索栏关联的<pre class="crayon-plain-tag">&lt;div class="list-block"&gt;</pre> 元素。分别在以下时机触发：
<ol>
<li><span style="color: #333333; font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: small;"><span style="line-height: 22px;">搜索框的值改变时</span></span></li>
<li>点击搜索栏的clear元素，清空搜索框时</li>
<li>搜索栏被激活时</li>
<li>搜索栏去激活时</li>
</ol>
</td>
</tr>
<tr>
<td>⚡clearSearch</td>
</tr>
<tr>
<td>⚡enableSearch</td>
</tr>
<tr>
<td>⚡disableSearch</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">状态栏</span></div>
<p> 状态栏是指设备屏幕顶端的一个窄条，包含信标、电量、时间等信息。F7框架中和状态栏相关的主题是状态栏overlay。</p>
<div class="blog_h2"><span class="graybg">状态栏overlay</span></div>
<p><span style="background-color: #c0c0c0;">iOS7+</span>系统允许你创建全屏应用程序，但是状态栏可能覆盖应用程序的内容。F7能够处理该问题，当应用程序处于全屏状态时，F7会自动检测到并添加<pre class="crayon-plain-tag">with-statusbar-overlay</pre> 样式类到<pre class="crayon-plain-tag">&lt;html&gt;</pre> 元素上，当应用程序退出全屏时，则去掉该样式类。</p>
<p>该样式类自动在应用程序顶端添加20像素的补白，从而防止状态栏覆盖掉用程序内容。</p>
<div class="blog_h3"><span class="graybg">改变状态栏背景色</span></div>
<p>你可以控制<span style="background-color: #c0c0c0;">全屏应用状态栏的背景色</span>：</p>
<pre class="crayon-plain-tag">&lt;style&gt;
    /*修改状态栏背景色*/
    .statusbar-overlay {
        background: pink;
    }
&lt;/style&gt;
&lt;body&gt;
  &lt;div class="statusbar-overlay"&gt;&lt;/div&gt;
&lt;/div&gt;</pre>
<p><pre class="crayon-plain-tag">.statusbar-overlay</pre> 这个div总是<span style="background-color: #c0c0c0;">固定在屏幕顶部，默认隐藏</span>。当应用程序进入全屏状态、且<pre class="crayon-plain-tag">&lt;html&gt;</pre> 元素上具有<pre class="crayon-plain-tag">with-statusbar-overlay</pre> 样式时<span style="background-color: #c0c0c0;">自动显示</span>。</p>
<p>注意：</p>
<ol>
<li>Home界面中的Web应用，其状态栏总是白色，无法改变</li>
<li>Apache Cordova 应用的状态栏默认总是黑色，F7无法修改。你可以使用Cordova插件<pre class="crayon-plain-tag">cordova-plugin-statusbar</pre> 来定制状态栏颜色</li>
</ol>
<div class="blog_h1"><span class="graybg">侧边栏</span></div>
<p>侧边栏是从左侧或者右侧滑动进入屏幕，并且覆盖在页面主体内容上方的组件。</p>
<div class="blog_h2"><span class="graybg">侧边栏布局</span></div>
<pre class="crayon-plain-tag">&lt;body&gt;
    &lt;!-- 需要增加此元素，以便侧面板能够覆盖在应用主体内容上方（z-index） --&gt;
    &lt;div class="panel-overlay"&gt;&lt;/div&gt;
    &lt;!-- 左侧面板，打开时特效：移动应用主体内容 --&gt;
    &lt;div class="panel panel-left panel-reveal"&gt;&lt;/div&gt;
    &lt;!-- 右侧面板，打开时特效：覆盖应用主体内容  --&gt;
    &lt;div class="panel panel-right panel-cover"&gt;&lt;/div&gt;
&lt;/body&gt;</pre>
<div class="blog_h2"><span class="graybg">打开/关闭面板</span></div>
<div class="blog_h3"><span class="graybg">通过HTML</span></div>
<pre class="crayon-plain-tag">&lt;body&gt;
    &lt;div class="panel-overlay"&gt;&lt;/div&gt;
    &lt;div class="panel panel-left panel-reveal"&gt;
        &lt;div class="content-block"&gt;
            &lt;!--  close-panel类用于关闭面板 --&gt;
            &lt;p&gt;&lt;a href="#" class="close-panel"&gt;关闭当前面板&lt;/a&gt;&lt;/p&gt;
            &lt;!--  open-panel类用于打开面板，data-panel用于指定操作目标 --&gt;
            &lt;p&gt;&lt;a href="#" data-panel="right" class="open-panel"&gt;打开右侧面板&lt;/a&gt;&lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/body&gt;</pre>
<div class="blog_h3"><span class="graybg">通过JavaScript </span></div>
<pre class="crayon-plain-tag">/**
 * 打开面板
 * @param position 打开哪一侧面板，"left" 或 "right"
 */
app.openPanel(position);
/**
 * 关闭当前打开的面板
 */
app.closePanel();</pre>
<div class="blog_h2"><span class="graybg">面板相关事件</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">事件</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>⚡open</td>
<td rowspan="4">这些事件的target都是<pre class="crayon-plain-tag">&lt;div class="panel"&gt;</pre> 元素。分别在以下时机触发：
<ol>
<li>面板打开动画开始时</li>
<li>面板打开动画完成时</li>
<li>面板关闭动画开始时</li>
<li>面板关闭动画完成时</li>
</ol>
</td>
</tr>
<tr>
<td>⚡opened</td>
</tr>
<tr>
<td>⚡close</td>
</tr>
<tr>
<td>⚡closed</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">通过滑动手势打开面板</span></div>
<p>F7支持通过滑动手势打开面板，但是<span style="background-color: #c0c0c0;">只有左侧或者右侧面板中的一个</span>可以启用该手势：</p>
<pre class="crayon-plain-tag">var app = new Framework7( {
    swipePanel : 'left' //让左侧面板支持滑动手势
} );</pre>
<p>其它与面板相关的应用初始化参数包括：swipePanelCloseOpposite、 swipePanelOnlyClose、swipePanelActiveArea、swipePanelNoFollow、swipePanelThreshold。</p>
<div class="blog_h2"><span class="graybg">判断面板是否打开</span></div>
<p>当面板处于打开状态时，F7会给<pre class="crayon-plain-tag">&lt;body&gt;</pre> 元素添加额外的样式类：<pre class="crayon-plain-tag">with-panel-[position]-[effect]</pre> ：</p>
<ol>
<li>position区分左右面板，取值left或right</li>
<li>effect表示面板打开效果，取值reveal或cover</li>
</ol>
<p>下面的代码测试当前是否以覆盖方式打开了左侧面板：</p>
<pre class="crayon-plain-tag">if ($$('body').hasClass('with-panel-left-cover')) {
    console.log('左侧面板处于打开状态')
}</pre>
<div class="blog_h1"><span class="graybg">内容块</span></div>
<p>内容块主要用于为<span style="background-color: #c0c0c0;">文本内容</span>添加<span style="background-color: #c0c0c0;">额外的格式或边距</span>： </p>
<pre class="crayon-plain-tag">&lt;div class="page-content"&gt;
    &lt;p&gt;页面中普通的，位于内容块之外的文本&lt;/p&gt;

    &lt;!-- 内容块 --&gt;
    &lt;div class="content-block"&gt;
        &lt;p&gt;内容块中的文本颜色改变，且和四周有额外的边距&lt;/p&gt;
    &lt;/div&gt;

    &lt;!-- 内容块 + inner容器 --&gt;
    &lt;div class="content-block"&gt;
        &lt;div class="content-block-inner"&gt;内容块背景颜色改变&lt;/div&gt;
    &lt;/div&gt;

    &lt;div class="content-block-title"&gt;为内容块添加标题&lt;/div&gt;
    &lt;div class="content-block"&gt;&lt;p&gt;&lt;/p&gt;&lt;/div&gt;

    &lt;!-- 带有内凹效果的内容块，在iOS主题中表现为圆角边框、额外的边距--&gt;
    &lt;div class="content-block inset"&gt;
        &lt;div class="content-block-inner"&gt;&lt;p&gt;&lt;/p&gt;&lt;/div&gt;
    &lt;/div&gt;
    &lt;!-- 仅在面板电脑中有内凹效果的内容块 --&gt;
    &lt;div class="content-block tablet-inset"&gt;
        &lt;div class="content-block-inner"&gt;&lt;p&gt;&lt;/p&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;</pre>
<div class="blog_h1"><span class="graybg"> 布局网格（Layout Grid）</span></div>
<p>使用 F7的布局网格，我们可以灵活的在页面中定位自己的组件。</p>
<div class="blog_h2"><span class="graybg">网格布局</span></div>
<pre class="crayon-plain-tag">&lt;!-- 布局表格以行包装列的形式出现 --&gt;
&lt;div class="row"&gt;&lt;!-- 包含两列的一行 --&gt;
    &lt;!-- 每个单元格被添加样式类col-*，*表示占据的宽度百分比 --&gt;
    &lt;div class="col-50"&gt;50%&lt;/div&gt;
    &lt;div class="col-50"&gt;50%&lt;/div&gt;
&lt;/div&gt;
&lt;!-- 默认的，单元格之间有15像素的空隙，添加no-gutter则无空隙 --&gt;
&lt;div class="row no-gutter"&gt;&lt;!-- 包含三列的一行 --&gt;
    &lt;div class="col-33"&gt;33%&lt;/div&gt;
    &lt;div class="col-33"&gt;33%&lt;/div&gt;
    &lt;div class="col-33"&gt;33%&lt;/div&gt;
&lt;/div&gt;</pre>
<div class="blog_h2"><span class="graybg">单元格样式类</span></div>
<p>用于指定网格的单元格的宽度的样式类包括：</p>
<ol>
<li><pre class="crayon-plain-tag">col-*</pre> ：其中*为5/10/15...90/95.100，数字代表占据的相对宽度。设置样式类为col-auto的单元格，平均分配剩余的宽度</li>
<li><pre class="crayon-plain-tag">tablet-*</pre> ：与上面类似，用于平板类型（窗口宽度大于768像素时）</li>
</ol>
<div class="blog_h2"><span class="graybg">为单元格统一添加样式</span></div>
<p>默认情况下，F7没有给单元格添加任何样式，可以参考如下的CSS语法统一添加：</p>
<pre class="crayon-plain-tag">div[class*="col-"] {
  background: #fff;
  text-align: center;
  color: #000;
  border: 1px solid #ddd;
  padding: 5px;
}
.row {
  margin-bottom: 15px;
}</pre>
<div class="blog_h1"><span class="graybg">覆盖类组件（Overlays）</span></div>
<p> 这一大类的F7组件都在z-index方向<span style="background-color: #c0c0c0;">覆盖在应用主体内容之上</span>，它们都属于<span style="background-color: #c0c0c0;">临时视图</span>（Temporary Views）的一部分。</p>
<div class="blog_h2"><span class="graybg">模态组件（Modal）</span></div>
<p>模态是一系列包含了简短内容的小面板，类似于<span style="background-color: #c0c0c0;">居中于屏幕的弹出窗口</span>。模态通常用于向用户提问，或者给用户发出通知。</p>
<div class="blog_h3"><span class="graybg">预定义模态组件</span></div>
<p>F7预定义了一些常用的模态组件，例如警告框、确认框等。这些模态组件的标题、按钮文本的<span style="background-color: #c0c0c0;">默认值</span>可以通过modalTitle、modalPreloaderTitle 、modalButtonOk、modalButtonCancel 等应用初始化参数修改。</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 20%; text-align: center;">组件</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>信息框（Alert）</td>
<td>类似于window.alert()，可以具有标题、文本、点击后的回调。函数签名：<br />
<pre class="crayon-plain-tag">/**
 * @param text 信息内容
 * @param title 可选的标题
 * @param callbackOk 点击OK按钮后执行的回调函数
 * @return 动态创建的模态HTML元素
 */
app.alert(text, [title, callbackOk]);</pre>
</td>
</tr>
<tr>
<td>确认框（Confirm）</td>
<td>与信息框类似，但是具有确定、取消两个按钮，点击后可以执行不同的回调。函数签名：<br />
<pre class="crayon-plain-tag">/**
 * @param text 提示内容
 * @param title 提示标题
 * @param callbackOk 点击OK按钮时执行的回调
 * @param callbackCancel 点击Cancel按钮时执行的回调
 */
app.confirm(text, [title, callbackOk, callbackCancel]);</pre>
</td>
</tr>
<tr>
<td>提示框（Prompt）</td>
<td>与确认框类似，但是还让用户输入一些文本。函数签名：<br />
<pre class="crayon-plain-tag">/**
 * @param text 提示内容
 * @param title 提示标题
 * @param callbackOk 点击OK按钮时执行的回调
 * @param callbackCancel 点击Cancel按钮时执行的回调
 */
app.prompt(text, [title, callbackOk, callbackCancel]);
/**
 * 上面两个回调函数的签名
 * @param value 用户在文本框输入的值
 */
function callback(value);</pre>
</td>
</tr>
<tr>
<td>登录和密码框</td>
<td>可以进行用户身份验证的模态窗口。函数签名：<br />
<pre class="crayon-plain-tag">/**
 * @param text 登录框内容
 * @param title 登录框标题
 * @param callbackOk 点击OK按钮时执行的回调
 * @param callbackCancel 点击Cancel按钮时执行的回调
 */
app.modalLogin( text, [ title, callbackOk, callbackCancel ] );
/**
 * 上述两个回调函数的签名
 * @param username 用户输入的用户名
 * @param password 用户输入的密码
 */
function callback(username, password);

/**
 * @param text 密码框内容
 * @param title 密码框标题
 * @param callbackOk 点击OK按钮时执行的回调
 * @param callbackCancel 点击Cancel按钮时执行的回调
 */
app.modalPassword( text, [ title, callbackOk, callbackCancel ] );
/**
 * 上述两个回调函数的签名
 * @param password 用户输入的密码
 */
function callback(password);</pre>
</td>
</tr>
<tr>
<td>预加载器（Preloader）</td>
<td>用于提示用户：由于某些后台操作（例如XHR）正在执行，应用需要阻塞用户操作。<br />iOS主题下的默认UI效果是：旋转菊花 + 文本。<br />F7提供了两个函数来操控预加载器：<br />
<pre class="crayon-plain-tag">/**
 * 显示预加载器
 * @param title 预加载标题，默认从应用初始化参数modalPreloaderTitle中获得
 */
app.showPreloader([title]);
/**
 * 隐藏预加载器
 */
app.hidePreloader();</pre>
</td>
</tr>
<tr>
<td>指示器（Indicator）</td>
<td>和预加载类似，但是UI只有一个旋转菊花图标，没有文本。F7提供两个函数来操控指示器：<br />
<pre class="crayon-plain-tag">app.showIndicator();// 显示指示器
app.hideIndicator();// 隐藏指示器</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">扩展模态组件</span></div>
<p>上表中所有预定义模态组件，实际上都属于“快捷方式”，它们最终都是调用下面的函数实现功能的： </p>
<pre class="crayon-plain-tag">/**
 * 新建一个模态组件
 * @param parameters 初始化参数
 * @return 动态创建的代表了模态组件的HTMLElement
 */
app.modal(parameters);</pre>
<p>定制的模块组件的内容可以相当丰富，下面是两个例子：</p>
<pre class="crayon-plain-tag">var modal = app.modal( {
    title : '照片墙',
    text : '你觉得我的照片怎么样',
    //文本后的内容被替换为Swiper Slider组件
    afterText : 
        '&lt;div class="swiper-container" style="width: auto; margin:5px -15px -15px"&gt;' + 
            '&lt;div class="swiper-pagination"&gt;&lt;/div&gt;' + 
            '&lt;div class="swiper-wrapper"&gt;' + 
                '&lt;div class="swiper-slide"&gt;&lt;img src="..." height="150" style="display:block"&gt;&lt;/div&gt;' + 
                '&lt;div class="swiper-slide"&gt;&lt;img src="..." height="150" style="display:block"&gt;&lt;/div&gt;' + 
            '&lt;/div&gt;' + 
        '&lt;/div&gt;',
    buttons : [ {
            text : '一般般'
        }, {
            text : '太好了',
            bold : true,
            onClick : function() {
                app.alert( '谢谢！' );
            }
        }
    ]
} );
app.swiper( $$( modal ).find( '.swiper-container' ), {
    pagination : '.swiper-pagination'
} );

/**
 * 该模态组件显示两个页签
 */
app.modal( {
    //标题被设置为切换页签的按钮
    title : '&lt;div class="buttons-row"&gt;' + 
                '&lt;a href="#tab1" class="button active tab-link"&gt;页签一&lt;/a&gt;' + 
                '&lt;a href="#tab2" class="button tab-link"&gt;页签二&lt;/a&gt;' +
             '&lt;/div&gt;',
    //文本被设置为两个页签
    text : '&lt;div class="tabs"&gt;' + 
               '&lt;div class="tab active" id="tab1"&gt;页签一的内容&lt;/div&gt;' + 
               '&lt;div class="tab" id="tab2"&gt;页签二的内容&lt;/div&gt;' + 
           '&lt;/div&gt;',
    buttons : [ {
        text : '确定',
        bold : true
    } ]
} );</pre>
<div class="blog_h3"><span class="graybg">模态初始化参数</span></div>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 25%; text-align: center;">参数</td>
<td style="width: 30%; text-align: center;">类型与默认值</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>title</td>
<td>string</td>
<td>可选的模态组件标题，可以是HTML片段</td>
</tr>
<tr>
<td>text</td>
<td>string</td>
<td>可选的模态组件文本，可以是HTML片段</td>
</tr>
<tr>
<td>afterText</td>
<td>string</td>
<td>可选的，在text之后展示的额外文本，可以是HTML片段</td>
</tr>
<tr>
<td>buttons</td>
<td>Button[]</td>
<td>模态按钮的数组，每个元素均是如下格式的对象：<br />
<pre class="crayon-plain-tag">{
    //按钮的文本，可以是HTML
    string text;
    //是否用粗体显示按钮文本
    boolean bold = false;
    //点击该按钮后，模态组件是否自动关闭
    boolean close = true;
    //点击该按钮时执行的回调
    function onClick();
}</pre>
</td>
</tr>
<tr>
<td>verticalButtons</td>
<td>boolean = false</td>
<td>是否纵向排列模态按钮</td>
</tr>
<tr>
<td>onClick</td>
<td>function(modelEl, index)</td>
<td>
<p>用户点击 任何模态按钮后执行的函数：</p>
<ol>
<li>modelEl 模态窗口对应的HTMLElement</li>
<li>index 被点击按钮的索引</li>
</ol>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">关闭模态组件</span></div>
<pre class="crayon-plain-tag">/**
 * 关闭一个模态窗口
 * @param modal 代表模态组件的CSS选择器或者HTMLElement
 */
app.closeModal(modal);</pre>
<div class="blog_h3"><span class="graybg">模态相关的事件</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">方法/事件</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>⚡open</td>
<td rowspan="4">
<p>这些事件的target均为<pre class="crayon-plain-tag">&lt;div class="modal"&gt;</pre> 元素，分别在以下时机触发：</p>
<ol>
<li>模态打开动画开始时</li>
<li>模态打开动画结束时</li>
<li>模态关闭动画开始时</li>
<li>模态关闭动画结束时</li>
</ol>
</td>
</tr>
<tr>
<td>⚡opened</td>
</tr>
<tr>
<td>⚡close</td>
</tr>
<tr>
<td>⚡closed</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">模态模板</span></div>
<p>通过传递应用初始化参数<pre class="crayon-plain-tag">modalTemplate</pre> ，可以为模态组件定制HTML模板， 该参数必须是Template7模板，下面是一个例子：</p>
<pre class="crayon-plain-tag">&lt;div class="modal {{#unless buttons}}modal-no-buttons{{/unless}}"&gt;&lt;!-- 如果定义了按钮 --&gt;
  &lt;div class="modal-inner"&gt;
    &lt;!--  如果定义了标题 --&gt;
    {{#if title}}
      &lt;div class="modal-title"&gt;{{title}}&lt;/div&gt;
    {{/if}}
    &lt;!--  如果定义了文本 --&gt;
    {{#if text}}
       &lt;div class="modal-text"&gt;{{text}}&lt;/div&gt;
    {{/if}}
    &lt;!--  如果定义了文本后内容 --&gt;
    {{#if afterText}}
      {{afterText}}
    {{/if}}
  &lt;/div&gt;
  {{#if buttons}}
    &lt;div class="modal-buttons"&gt;
      &lt;!-- 遍历所有按钮 --&gt;
      {{#each buttons}}
        &lt;span class="modal-button {{#if bold}}modal-button-bold{{/if}}"&gt;{{text}}&lt;/span&gt;
      {{/each}}
    &lt;/div&gt;
  {{/if}}
&lt;/div&gt;</pre>
<div class="blog_h2"><span class="graybg">Popup弹窗</span></div>
<p>这类弹窗<span style="background-color: #c0c0c0;">覆盖</span>F7应用的主体内容上面，默认从<span style="background-color: #c0c0c0;">屏幕底部滑动进入</span>。在<span style="background-color: #c0c0c0;">手机上具有全屏效果</span>，在平板上默认630像素宽高。</p>
<div class="blog_h3"><span class="graybg">Popup布局</span></div>
<pre class="crayon-plain-tag">&lt;body&gt;
  &lt;!-- 额外的样式类tablet-fullscreen让Popup在平板上也全屏 --&gt;
  &lt;div class="popup tablet-fullscreen"&gt;&lt;/div&gt;
&lt;/body&gt;</pre>
<div class="blog_h3"><span class="graybg">通过HTML打开/关闭Popup</span></div>
<pre class="crayon-plain-tag">&lt;body&gt;
    &lt;div class="page-content"&gt;
        &lt;div class="content-block"&gt;
            &lt;!-- 样式类open-popup用于打开Popup弹窗（可用于任何HTML元素，不仅仅是a），data-popup指定Popup的CSS选择器 --&gt;
            &lt;p&gt;&lt;a href="#" data-popup=".popup-about" class="open-popup"&gt;Open About Popup &lt;/a&gt;&lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;!-- 额外的样式类popup-about相当于该弹窗的标识符 --&gt;
    &lt;div class="popup popup-about"&gt;
        &lt;div class="content-block"&gt;
            &lt;!-- 样式类close-popup用于关闭当前打开的弹窗 --&gt;
            &lt;p&gt;&lt;a href="#" class="close-popup"&gt;Close popup&lt;/a&gt;&lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/body&gt;</pre>
<div class="blog_h3"><span class="graybg">通过JavaScript打开/关闭Popup</span></div>
<pre class="crayon-plain-tag">/**
 * 打开Popup弹窗
 * @param popup HTMLElement或者String（CSS选择器）
 */
app.popup(popup);
/**
 * 关闭Popup弹窗
 * @param popup
 *     HTMLElement或者String（CSS选择器）
 *     如果省略该参数，则关闭任何打开的Popup或其它模态组件
 */
app.closeModal(popup);</pre>
<div class="blog_h3"><span class="graybg">Popup弹窗相关的事件</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">方法/事件</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>⚡open</td>
<td rowspan="4">
<p>这些事件的target均为<pre class="crayon-plain-tag">&lt;div class="popup"&gt;</pre> 元素，分别在以下时机触发：</p>
<ol>
<li>Popup弹窗打开动画开始时</li>
<li>Popup弹窗打开动画结束时</li>
<li>Popup弹窗关闭动画开始时</li>
<li>Popup弹窗关闭动画结束时</li>
</ol>
</td>
</tr>
<tr>
<td>⚡opened</td>
</tr>
<tr>
<td>⚡close</td>
</tr>
<tr>
<td>⚡closed</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">动态Popup弹窗</span></div>
<p>F7提供了一个动态创建、打开Popup弹窗的函数：</p>
<pre class="crayon-plain-tag">/**
 * 动态打开一个Popup弹窗
 * @param popupHTML Popup的HTML内容
 * @param removeOnClose 默认true，是否在关闭时移除Popup的DOM
 */
app.popup(popupHTML, removeOnClose);</pre>
<p>下面是一个例子：</p>
<pre class="crayon-plain-tag">$$('.create-popup').on('click', function () {
  var popupHTML = 
     '&lt;div class="popup"&gt;'+
       '&lt;div class="content-block"&gt;'+
         '&lt;p&gt;Popup created dynamically.&lt;/p&gt;'+
         '&lt;p&gt;&lt;a href="#" class="close-popup"&gt;关闭&lt;/a&gt;&lt;/p&gt;'+
       '&lt;/div&gt;'+
     '&lt;/div&gt;';
  app.popup(popupHTML);
});</pre>
<div class="blog_h2"><span class="graybg">Popover弹窗</span></div>
<p>Popover类似于ExtJS的Tips，是一个展示<span style="background-color: #c0c0c0;">临时信息</span>的较小的弹出窗口。当用户触碰空白处时，Popover自动关闭。Popover默认宽度是320px。</p>
<p>在<span style="background-color: #c0c0c0;">小屏幕上，不建议使用</span>Popover，最好使用操作列表（Action Sheet）代替。</p>
<div class="blog_h3"><span class="graybg">Popover的布局</span></div>
<pre class="crayon-plain-tag">&lt;body&gt;
    &lt;div class="popover"&gt;
        &lt;!-- Popover的小箭头 --&gt;
        &lt;div class="popover-angle"&gt;&lt;/div&gt;
        &lt;!-- Popover的内容 --&gt;
        &lt;div class="popover-inner"&gt;
            &lt;!-- 这里面可以包含任何HTML内容 --&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/body&gt;</pre>
<div class="blog_h3"><span class="graybg">通过HTML打开/关闭Popover</span></div>
<pre class="crayon-plain-tag">&lt;body&gt;
    &lt;div class="page-content"&gt;
        &lt;div class="content-block"&gt;
            &lt;!-- open-popover用于打开Popover弹窗 --&gt;
            &lt;p&gt;&lt;a href="#" data-popover=".popover-about" class="open-popover"&gt;打开&lt;/a&gt;&lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="popover popover-about"&gt;
        &lt;div class="popover-angle"&gt;&lt;/div&gt;
        &lt;div class="popover-inner"&gt;
            &lt;!-- close-popover用于关闭目前打开的Popover弹窗 --&gt;
            &lt;div class="content-block"&gt;&lt;a href="#" class="close-popover"&gt;关闭&lt;/a&gt;&lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/body&gt;</pre>
<p>通过HTML打开的Popover，其显示位置会自动计算（在<span style="background-color: #c0c0c0;">打开Popover的元素的周围</span>），以确保正确显示。</p>
<div class="blog_h3"><span class="graybg">通过JavaScript打开/关闭Popover</span></div>
<pre class="crayon-plain-tag">/**
 * 打开一个Popover弹窗
 * @param popover 目标弹窗的HTMLElement或者CSS选择器
 * @param target 针对的元素，弹窗会在此元素的周围定位
 */
app.popover(popover, target);
/**
 * 关闭一个Popover弹窗
 * @param popover 
 *     目标弹窗的HTMLElement或者CSS选择器
 *     如果忽略此参数，则任何打开的Popover都被关闭
 */
app.closeModal(popover);</pre>
<div class="blog_h3"><span class="graybg">动态Popover弹窗</span></div>
<p>和Popover类似，Popover也可以动态创建和展示： </p>
<pre class="crayon-plain-tag">/**
 * 动态创建并打开一个Popover弹窗
 * 
 * @param popover
 *          Popover的HTML内容
 * @param target
 *          针对的元素，弹窗会在此元素的周围定位
 * @param removeOnClose
 *          默认true，是否在关闭时移除Popover的DOM
 */
app.popover( popoverHTML, target, removeOnClose );</pre>
<div class="blog_h3"><span class="graybg">Popover弹窗相关的事件</span></div>
<p>与Popup类似，事件的Target是<pre class="crayon-plain-tag">&lt;div class="popover"&gt;</pre> 元素。</p>
<div class="blog_h2"><span class="graybg">操作列表（Action Sheet）</span></div>
<p>操作列表通常表现为从<span style="background-color: #c0c0c0;">屏幕底部滑动进入</span>的、包含若干<span style="background-color: #c0c0c0;">纵向排列按钮</span>的弹窗。操作列表可以把<span style="background-color: #c0c0c0;">按钮分成若干组</span>。可以通过操作列表，让用户确认潜在危险的操作。</p>
<p><span style="background-color: #c0c0c0;">不建议在大屏幕（平板）上使用</span>操作列表，最好用Popover代替。</p>
<div class="blog_h3"><span class="graybg">创建和打开操作列表</span></div>
<p>操作列表属于动态组件，只能通过JavaScript创建和打开：</p>
<pre class="crayon-plain-tag">/**
 * 创建并打开由若干按钮组构成的操作列表
 * @param groups 相当于button[][]，按钮组的数组，每个按钮组则是button的数组
 * @return 动态创建的、代表操作列表的HTMLElement
 */
app.actions( groups );
/**
 * 创建并打开由若干按钮构成的操作列表（单个组）
 * @param buttons 按钮的数组
 * @return 动态创建的、代表操作列表的HTMLElement
 */
app.actions( buttons );</pre>
<div class="blog_h3"><span class="graybg">按钮初始化参数</span></div>
<p>上述groups、buttons的最内层元素是按钮的配置对象： </p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 15%; text-align: center;">参数</td>
<td style="text-align: center;">类型和默认值</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>text</td>
<td>string</td>
<td>按钮的文本，可以是HTML</td>
</tr>
<tr>
<td>bold</td>
<td>boolean = false</td>
<td>可选，设置按钮文本为粗体</td>
</tr>
<tr>
<td>color</td>
<td>string</td>
<td>可选，按钮的文字颜色，可以是10种内置颜色的英文名称</td>
</tr>
<tr>
<td>bg</td>
<td>string</td>
<td>可选，按钮的北京颜色 ，可以是10种内置颜色的英文名称</td>
</tr>
<tr>
<td>label</td>
<td>boolean = true</td>
<td>如果设置为true，则当前项<span style="background-color: #c0c0c0;">作为按钮组的标题</span>，而不是按钮</td>
</tr>
<tr>
<td>disabled</td>
<td>boolean = false</td>
<td>设置为true则按钮被标记为禁用</td>
</tr>
<tr>
<td>onClick</td>
<td>function()</td>
<td>点击按钮后，执行的回调</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">把操作列表转换为Popover</span></div>
<p>由于屏幕大小的原因，F7不建议：在手机上使用Popover；或者在平板上使用Action Sheet。为了让单份代码能够同时适用手机和平板，F7提供了操作列表到Popover的转换机制：</p>
<pre class="crayon-plain-tag">/**
 * 这两个函数，在手机上创建的是操作列表，而在平板上创建的则是Popover
 * @target HTMLElement或者CSS选择器，当作为Popover时，在哪个元素周围弹出
 * @groups 操作列表/Popover包含的按钮组的数组
 * @buttons 操作列表/Popover包含的按钮的数组
 */
app.actions(target, groups);
app.actions(target, buttons);</pre>
<div class="blog_h3"><span class="graybg">关闭操作列表</span></div>
<p>默认的，操作列表在以下情况下自动关闭：</p>
<ol>
<li>当其中某个按钮被点击时</li>
<li>当触碰操作列表外部的屏幕空间时（可通过应用初始化参数modalActionsCloseByOutside定制） </li>
</ol>
<p> 如果要手工关闭操作列表，可以调用：<pre class="crayon-plain-tag">app.closeModal(actionSheet)</pre> ，该方法前面的章节已有说明。</p>
<div class="blog_h3"><span class="graybg">操作列表相关事件</span></div>
<p>与Popup、Popover类似，操作列表具有4个相关事件，这些事件的Target均为<pre class="crayon-plain-tag">&lt;div class="actions-modal"&gt;</pre> 元素。</p>
<div class="blog_h3"><span class="graybg">操作列表模板</span></div>
<p>通过传递应用初始化参数<pre class="crayon-plain-tag">modalActionsTemplate</pre> ，可以为操作列表定制HTML模板， 该参数必须是Template7模板，下面的模板展现了操作列表的默认布局：</p>
<pre class="crayon-plain-tag">&lt;div class="actions-modal"&gt;
  &lt;!-- this表示单个组 --&gt;
  {{#each this}}
    &lt;div class="actions-modal-group"&gt;
      &lt;!-- this表示单个按钮 --&gt;
      {{#each this}}
          {{#if label}}
            &lt;span class="actions-modal-label"&gt;{{text}}&lt;/span&gt;&lt;!-- 作为标题 --&gt;
          {{else}}
            &lt;div class="actions-modal-button {{#if color}}color-{{color}}{{/if}} {{#if bold}}actions-modal-button-bold{{/if}}"&gt;{{text}}&lt;/div&gt;
          {{/if}}
      {{/each}}
    &lt;/div&gt;
  {{/each}}
&lt;/div&gt;</pre>
<p>如果你使用操作列表到Popover的转换，则还可以传递应用初始化参数<pre class="crayon-plain-tag">modalActionsToPopoverTemplate</pre>  ，从而定制目标Popover的HTML模板，下面是默认的模板：</p>
<pre class="crayon-plain-tag">&lt;div class="popover actions-popover"&gt;
  &lt;div class="popover-inner"&gt;
    &lt;!-- 每个按钮组转换为一个列表块 --&gt;
    {{#each this}}
    &lt;div class="list-block"&gt;
      &lt;ul&gt;
        &lt;!-- 每个按钮转换为一个列表条目 --&gt;
        {{#each this}}
        {{#if label}}
        &lt;li class="actions-popover-label {{#if color}}color-{{color}}{{/if}} {{#if bold}}actions-popover-bold{{/if}}"&gt;{{text}}&lt;/li&gt;
        {{else}}
        &lt;li&gt;&lt;a href="#" class="item-link list-button {{#if color}}color-{{color}}{{/if}} {{#if bg}}bg-{{bg}}{{/if}} {{#if bold}}actions-popover-bold{{/if}} {{#if disabled}}disabled{{/if}}"&gt;{{text}}&lt;/a&gt;&lt;/li&gt;
        {{/if}}
        {{/each}}
      &lt;/ul&gt;
    &lt;/div&gt;
    {{/each}}
  &lt;/div&gt;
&lt;/div&gt;</pre>
<div class="blog_h2"><span class="graybg">登录屏幕</span></div>
<p>大部分应用程序都有登录功能， 因此F7内置了登录屏幕组件。</p>
<div class="blog_h3"><span class="graybg">登录屏幕的布局</span></div>
<pre class="crayon-plain-tag">&lt;body&gt;
  &lt;!-- 登录屏幕应当是body的直接子元素 --&gt;
  &lt;div class="login-screen"&gt;
      &lt;!-- 所有子元素都是可选的 --&gt;
      &lt;!-- 默认的视图-页面布局 --&gt;
      &lt;div class="view"&gt;
        &lt;div class="page"&gt;
          &lt;!-- 页面内容包含额外的login-screen-content类 --&gt;
          &lt;div class="page-content login-screen-content"&gt;
            &lt;div class="login-screen-title"&gt;登录屏幕标题&lt;/div&gt;
            &lt;!-- 登录表单 --&gt;
            &lt;form&gt;
              &lt;div class="list-block"&gt;
                &lt;ul&gt;
                  &lt;li class="item-content"&gt;
                    &lt;div class="item-inner"&gt;
                      &lt;div class="item-title label"&gt;用户名&lt;/div&gt;
                      &lt;div class="item-input"&gt;
                        &lt;input type="text" name="username" placeholder="用户名"&gt;
                      &lt;/div&gt;
                    &lt;/div&gt;
                  &lt;/li&gt;
                  &lt;li class="item-content"&gt;
                    &lt;div class="item-inner"&gt;
                      &lt;div class="item-title label"&gt;密码&lt;/div&gt;
                      &lt;div class="item-input"&gt;
                        &lt;input type="password" name="password" placeholder="密码"&gt;
                      &lt;/div&gt;
                    &lt;/div&gt;
                  &lt;/li&gt;
                &lt;/ul&gt;
              &lt;/div&gt;
              &lt;div class="list-block"&gt;
                &lt;ul&gt;
                  &lt;li&gt;
                    &lt;a href="#" class="item-link list-button"&gt;登录&lt;/a&gt;
                  &lt;/li&gt;
                &lt;/ul&gt;
                &lt;div class="list-block-labe"&gt;与登录相关的提示信息&lt;/div&gt;
              &lt;/div&gt;
            &lt;/form&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
  &lt;/div&gt;
&lt;/body&gt;</pre>
<div class="blog_h3"><span class="graybg">打开/关闭登录屏幕</span></div>
<p>HTML方式：</p>
<pre class="crayon-plain-tag">&lt;a href="#" class="open-login-screen"&gt;打开登录屏幕&lt;/a&gt;
&lt;a href="#" class="close-login-screen"&gt;关闭登录屏幕&lt;/a&gt;</pre>
<p>JavaScript方式：</p>
<pre class="crayon-plain-tag">/**
 * 打开登录屏幕
 */
app.loginScreen();
/**
 * 关闭登录屏幕
 * @param loginScreen 登录屏幕的TMLElement或者CSS选择器
 */
app.closeModal(loginScreen);</pre>
<div class="blog_h3"><span class="graybg">登录屏幕相关事件</span></div>
<p>与Popup、Popover、ActionSheet类似，登录屏幕具有4个相关事件，这些事件的Target均为<pre class="crayon-plain-tag">&lt;div class="login-screen"&gt;</pre> 元素。 </p>
<div class="blog_h3"><span class="graybg">从登录屏幕启动应用</span></div>
<p>某些应用要求先登录后使用，我们可以为登录屏幕添加<pre class="crayon-plain-tag">modal-in</pre> 类，这样F7应用加载后，立即显示登录屏幕：</p>
<pre class="crayon-plain-tag">&lt;body&gt;
    &lt;div class="login-screen modal-in"&gt;&lt;/div&gt;
&lt;/body&gt;</pre>
<div class="blog_h2"><span class="graybg">模态选取器（Picker Modal）</span></div>
<p>这是一种特殊的Overlay组件，可以用于实现定制的选取器。其<span style="background-color: #c0c0c0;">内容不受限制</span>，UI默认表现为从屏幕底部滑入的弹窗，右上侧有一个关闭按钮，类似于手机虚拟键盘。</p>
<div class="blog_h3"><span class="graybg">模态选取器布局</span></div>
<pre class="crayon-plain-tag">&lt;body&gt;
    &lt;!-- 模态选取器容器元素 --&gt;
    &lt;div class="picker-modal"&gt;
        &lt;!-- 可选的工具栏 --&gt;
        &lt;div class="toolbar"&gt;
            &lt;div class="toolbar-inner"&gt;
                &lt;div class="left"&gt;&lt;/div&gt;
                &lt;div class="right"&gt;
                    &lt;a href="#" class="link close-picker"&gt;完成&lt;/a&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;!-- 选取器内部元素 --&gt;
        &lt;div class="picker-modal-inner"&gt;
            &lt;!-- 选取器内容，任意自定义HTML --&gt;
            &lt;div class="content-block"&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/body&gt;</pre>
<div class="blog_h3"><span class="graybg">打开/关闭选取器</span></div>
<p>HTML方式：</p>
<pre class="crayon-plain-tag">&lt;a href="#" data-picker=".picker-1" class="link open-picker"&gt;打开选取器&lt;/a&gt;
&lt;a href="#" data-picker=".picker-1" class="close-picker"&gt;关闭选取器&lt;/a&gt;
&lt;div class="picker-modal picker-1"&gt;选取器实例&lt;/div&gt;</pre>
<p>JavaScript方式： </p>
<pre class="crayon-plain-tag">/**
 * 打开选取器
 * 
 * @param picker
 *            选取器的HTMLElement或者CSS选择器
 */
app.pickerModal( picker );
/**
 * 关闭选取器
 * 
 * @param picker
 *            选取器的HTMLElement或者CSS选择器
 *            如果不指定则关闭任何打开的模态窗口
 */
app.closeModal( picker );</pre>
<div class="blog_h3"><span class="graybg">动态创建选取器</span></div>
<pre class="crayon-plain-tag">/**
 * 动态创建并打开选取器
 * @param pickerHTML 选取器的HTML
 * @param removeOnClose 是否在关闭选取器时将其从DOM中移除
 */
app.pickerModal(pickerHTML, removeOnClose);</pre>
<div class="blog_h3"><span class="graybg">模态选取器相关事件</span></div>
<p>与Popup、Popover、ActionSheet、登录屏幕类似，模态选取器具有4个相关事件，这些事件的Target均为<pre class="crayon-plain-tag">&lt;div class="picker-modal"&gt;</pre> 元素。 </p>
<div class="blog_h1"><span class="graybg">预加载指示器（Preloader Indicator）</span></div>
<p>F7自带了基于SVG和CSS动画的的预加载提示器，可以随意的放大缩小，其UI效果是旋转菊花。预加载指示器具有两个版本，分别用于浅色、深色背景。</p>
<p>以<span style="background-color: #c0c0c0;">模态方式</span>使用预加载指示器，可以通过上一章提到的“预加载器”、“指示器”。</p>
<pre class="crayon-plain-tag">&lt;!-- 默认预加载器  --&gt;
&lt;span class="preloader"&gt;&lt;/span&gt;
&lt;!-- 白色版本的预加载器，用于深色背景 --&gt;
&lt;span class="preloader preloader-white"&gt;&lt;/span&gt;
&lt;!-- 指定大小 --&gt;</pre>
<div class="blog_h1"><span class="graybg"> 进度条（Progress Bar）</span></div>
<p>除了预加载指示器以外，F7还提供另外一种用于提示“正在处理”活动的组件——进度条。进度条分为确定的（Determinate，显示实际进度）、无限的（Infinite）两种。</p>
<div class="blog_h2"><span class="graybg">进度条布局</span></div>
<pre class="crayon-plain-tag">&lt;!-- 确定的进度条，20%完成进度 --&gt;
&lt;div class="progressbar" data-progress="20"&gt;
    &lt;span&gt;&lt;/span&gt;
&lt;/div&gt;
&lt;!-- 也可以用span元素 --&gt;
&lt;span class="progressbar" data-progress="50"&gt;
    &lt;span&gt;&lt;/span&gt;
&lt;/span&gt;

&lt;!-- 无限的进度条 --&gt;
&lt;div class="progressbar-infinite"&gt;&lt;/div&gt;
&lt;span class="progressbar-infinite"&gt;&lt;/span&gt;
&lt;!-- 多彩效果 --&gt;
&lt;div class="progressbar-infinite color-multi"&gt;&lt;/div&gt;</pre>
<div class="blog_h2"><span class="graybg">进度条JavaScript API</span></div>
<pre class="crayon-plain-tag">/**
 * 设置确定进度条的进度
 * @param container 进度条的容器的HTMLElement或者CSS选择器，默认body
 * @param progress 目标进度，0-100
 * @param speed 到达目标进度的动画耗时，单位ms
 * @return 返回进度条容器元素
 */
app.setProgressbar(container, progress, speed);
/**
 * 隐藏进度条
 * @param container 进度条的容器的HTMLElement或者CSS选择器，默认body
 */
app.hideProgressbar(container);
/**
 * 创建或显示（如果已经创建）进度条
 * @param container 进度条的容器的HTMLElement或者CSS选择器，默认body
 * @param progress 目标进度，0-100
 * @param color 进度条颜色名称
 * @return 返回进度条容器元素
 */
app.showProgressbar(container, progress, color);

//外加两个快捷方法
//在指定容器内打开（显示）某种颜色的无限进度条
app.showProgressbar(container, color);
//在body内打开（显示）color颜色的进度条，当前进度progress
app.showProgressbar(progress, color);</pre>
<div class="blog_h1"><span class="graybg">列表视图（List View,Table View）</span></div>
<p>列表视图是多用途、强大的UI组件，在iOS应用中经常可以看到。列表视图在一个可滚动的区域内<span style="background-color: #c0c0c0;">显示多行数据</span>，可选的，它讲数据<span style="background-color: #c0c0c0;">分为若干组/段</span>。我们可以用列表视图来：</p>
<ol>
<li>在层次型数据结构（树形）内进行导航</li>
<li>展现列表中的数据、选项</li>
<li>显示详细信息</li>
</ol>
<div class="blog_h2"><span class="graybg">列表视图的布局</span></div>
<pre class="crayon-plain-tag">&lt;!-- 列表块（List block）：列表块元素是列表视图的包装（wrapper）元素 --&gt;
&lt;!-- 注意不要把list-block放在content-block内 --&gt;
&lt;div class="list-block"&gt;
    &lt;!-- ul内是列表视图的全部元素 --&gt;
    &lt;ul&gt;
        &lt;!-- li代表列表视图的元素（条目），其内容相当复杂而灵活 --&gt;
        &lt;li&gt;
            &lt;!-- item-content为必需，作为item-media、item-inner的包装器--&gt;
            &lt;div class="item-content"&gt;
                &lt;!-- item-media为可选，用于图标、图片等媒体元素 --&gt;
                &lt;div class="item-media"&gt;
                    &lt;i class="icon my-icon"&gt;&lt;/i&gt;
                &lt;/div&gt;
                &lt;!-- item-inner为必需，作为item-title、item-after的包装器 --&gt;
                &lt;div class="item-inner"&gt;
                    &lt;!-- 必需，单行的元素标题 --&gt;
                    &lt;div class="item-title"&gt;列表元素的标题&lt;/div&gt;
                    &lt;!-- 可选，标题后内容，可以是标记（badge）、开关、按钮等任意HTML，默认右侧对齐 --&gt;
                    &lt;div class="item-after"&gt;列表元素的标签&lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/li&gt;
    &lt;/ul&gt;
    &lt;!-- 可以在列表块的结尾处，添加一个列表块标签 --&gt;
    &lt;div class="list-block-label"&gt;列表块标签&lt;/div&gt;
&lt;/div&gt;</pre>
<p> 如果li元素内没有其它内容，你可以把item-content类标注到li元素上，少写一个div：</p>
<pre class="crayon-plain-tag">&lt;li class="item-content"&gt;
    &lt;div class="item-media"&gt;
        &lt;i class="icon my-icon"&gt;&lt;/i&gt;
    &lt;/div&gt;
    &lt;div class="item-inner"&gt;
        &lt;div class="item-title"&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/li&gt;</pre>
<div class="blog_h3"><span class="graybg">链接元素</span></div>
<p>很多时候需要在点击列表条目后，<span style="background-color: #c0c0c0;">链接到其它页面</span>，这时需要为<pre class="crayon-plain-tag">&lt;div class="item-content"&gt;</pre> 添加额外的包装元素：</p>
<pre class="crayon-plain-tag">&lt;li&gt;
    &lt;!-- 额外的包装元素 --&gt;
    &lt;a href="#" class="item-link"&gt; &lt;!-- 导致列表右侧出现额外的箭头（链接）标记 --&gt;
        &lt;div class="item-content"&gt;
            &lt;div class="item-media"&gt;&lt;/div&gt;
            &lt;div class="item-inner"&gt;&lt;/div&gt;
        &lt;/div&gt;
    &lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
    &lt;!-- 如果li下没有其它元素，则可以把a、div合并 --&gt;
    &lt;a href="#" class="item-link item-content"&gt;
        &lt;div class="item-media"&gt;...&lt;/div&gt;
        &lt;div class="item-inner"&gt;...&lt;/div&gt;
    &lt;/a&gt;
&lt;/li&gt; </pre>
<div class="blog_h3"><span class="graybg">列表分隔器</span></div>
<p>可以插入类似下面的li元素，将列表分为两个部分：</p>
<pre class="crayon-plain-tag">&lt;li class="item-divider"&gt;Divider title here&lt;/li&gt;</pre>
<div class="blog_h3"><span class="graybg">分组的列表</span></div>
<p>可以将不同的列表元素进行分组：</p>
<pre class="crayon-plain-tag">&lt;div class="list-block"&gt;
  &lt;!-- 第一个分组 --&gt;
  &lt;div class="list-group"&gt;
    &lt;ul&gt;
      &lt;!--  分组的标题 --&gt;
      &lt;li class="list-group-title"&gt;&lt;/li&gt;
      &lt;li class="item-content"&gt;&lt;/li&gt;
    &lt;/ul&gt;
  &lt;/div&gt;
  &lt;!-- 第二个分组--&gt;
  &lt;div class="list-group"&gt;
    &lt;ul&gt;
      &lt;li class="list-group-title"&gt;&lt;/li&gt;
      &lt;li class="item-content"&gt;
      &lt;/li&gt;
    &lt;/ul&gt;
  &lt;/div&gt;
&lt;/div&gt;</pre>
<div class="blog_h3"><span class="graybg">Inset效果的列表块</span></div>
<p>列表块可以是Inset（非全宽度、圆角矩形边框）的：</p>
<pre class="crayon-plain-tag">&lt;div class="list-block inset"&gt;
  &lt;ul&gt;&lt;/ul&gt;
&lt;/div&gt;
&lt;!-- 仅在平板电脑上启用Inset效果 --&gt;
&lt;div class="list-block tablet-inset"&gt;
  &lt;ul&gt;&lt;/ul&gt;
&lt;/div&gt;</pre>
<div class="blog_h2"><span class="graybg">联系人列表</span></div>
<p>该组件是<span style="background-color: #c0c0c0;">分组列表的特例</span>，用于显示联系人的列表，其布局如下：</p>
<pre class="crayon-plain-tag">&lt;!-- page-content元素必须有额外的contacts-content类 --&gt;
&lt;div class="page-content contacts-content"&gt;
    &lt;!-- list-block元素必须有额外的contacts-block类 --&gt;
    &lt;div class="list-block contacts-block"&gt;
        &lt;div class="list-group"&gt;
            &lt;ul&gt;
                &lt;li class="list-group-title"&gt;汪&lt;/li&gt;
                &lt;li&gt;
                    &lt;div class="item-content"&gt;
                        &lt;div class="item-inner"&gt;
                            &lt;div class="item-title"&gt;汪震&lt;/div&gt;
                        &lt;/div&gt;
                    &lt;/div&gt;
                &lt;/li&gt;
                &lt;li&gt;
                    &lt;div class="item-content"&gt;
                        &lt;div class="item-inner"&gt;
                            &lt;div class="item-title"&gt;汪静好&lt;/div&gt;
                        &lt;/div&gt;
                    &lt;/div&gt;
                &lt;/li&gt;
            &lt;/ul&gt;
        &lt;/div&gt;
        &lt;div class="list-group"&gt;
            &lt;ul&gt;
                &lt;li class="list-group-title"&gt;李&lt;/li&gt;
                &lt;li&gt;
                    &lt;div class="item-content"&gt;
                        &lt;div class="item-inner"&gt;
                            &lt;div class="item-title"&gt;李晓蒙&lt;/div&gt;
                        &lt;/div&gt;
                    &lt;/div&gt;
                &lt;/li&gt;
            &lt;/ul&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;</pre>
<div class="blog_h2"><span class="graybg">媒体列表视图</span></div>
<p>该组件是列表视图的扩展，用于显示更复杂的数据，其布局如下：</p>
<pre class="crayon-plain-tag">&lt;!-- 媒体列表有额外的media-list元素 --&gt;
&lt;div class="list-block media-list"&gt;
    &lt;ul&gt;
        &lt;li&gt;
            &lt;!-- 必需的item-content，和列表视图一样，作为item-media、item-inner的包装器 --&gt;
            &lt;div class="item-content"&gt;
                &lt;!-- 可选的item-media --&gt;
                &lt;div class="item-media"&gt;
                    &lt;img src="path/to/img.jpg"&gt;
                &lt;/div&gt;
                &lt;div class="item-inner"&gt;&lt;!-- 必须 --&gt;
                    &lt;div class="item-title-row"&gt;&lt;!-- 类似与列表视图 --&gt;
                        &lt;div class="item-title"&gt;元素标题&lt;/div&gt;
                        &lt;div class="item-after"&gt;元素标签&lt;/div&gt;
                    &lt;/div&gt;
                    &lt;!--子标题，单行文本 --&gt;
                    &lt;div class="item-subtitle"&gt;子标题&lt;/div&gt;
                    &lt;!--占两行的额外文本 --&gt;
                    &lt;div class="item-text"&gt;额外文本内容&lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/li&gt;
    &lt;/ul&gt;
&lt;/div&gt;</pre>
<div class="blog_h2"><span class="graybg">可滑动（Swipeable ）列表 </span></div>
<p>该组件是列表视图的扩展，支持滑动手势（例如滑动以删除， swipe-to-delete），滑动后可以显示<span style="background-color: #c0c0c0;">针对列表元素的隐藏按钮</span>。 可滑动列表的布局如下：</p>
<pre class="crayon-plain-tag">&lt;div class="list-block"&gt;
    &lt;ul&gt;
        &lt;!-- li元素上需要额外的swipeout类 --&gt;
        &lt;li class="swipeout"&gt;
            &lt;!-- swipeout-content、swipeout-actions-left/right必须是直接子元素 --&gt;
            &lt;!-- 元素内容元素外部通常包裹swipeout-content元素 --&gt;
            &lt;div class="swipeout-content"&gt;
                &lt;!-- 如果只有item-content这一个子元素，那么它可以和父元素合并 --&gt;
                &lt;div class="item-content"&gt;
                    &lt;div class="item-media"&gt;&lt;/div&gt;
                    &lt;div class="item-inner"&gt;&lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;
            &lt;!-- 左侧操作集，手指向右滑动时的显示 --&gt;
            &lt;div class="swipeout-actions-left"&gt;
                &lt;!-- 这里可以是按钮、链接--&gt;
                &lt;a href="#"&gt;操作1&lt;/a&gt;
                &lt;a href="#"&gt;操作2&lt;/a&gt;
            &lt;/div&gt;
            &lt;!-- 右侧操作集，手指向左滑动时的显示 --&gt;
            &lt;div class="swipeout-actions-right"&gt;
                &lt;a href="#"&gt;操作3&lt;/a&gt;
                &lt;!--  swipeout-close，点击后关闭（隐藏）操作集 --&gt;
                &lt;a class="swipeout-close" href="#"&gt;操作4&lt;/a&gt;
            &lt;/div&gt;
        &lt;/li&gt;
    &lt;/ul&gt;
&lt;/div&gt;</pre>
<div class="blog_h3"><span class="graybg">滑动以删除（Swipe To Delete） </span></div>
<p>F7对滑动删除这一经常使用的特性内置了支持，你只需要在操作集中增加<pre class="crayon-plain-tag">&lt;a class="swipeout-delete"&gt;</pre> 按钮即可。</p>
<pre class="crayon-plain-tag">&lt;!-- 添加data-confirm，则在删除前会提示用户确认 --&gt;
&lt;!-- 设置data-close-on-cancel为true，当用户点击确认框的取消按钮时，自动关闭当前列表元素的操作集 --&gt;
&lt;a href="#" 
   class="swipeout-delete" 
   data-confirm="真的要删除吗" 
   data-confirm-title="请确认" 
   data-close-on-cancel="true" &gt;删除&lt;/a&gt;</pre>
<div class="blog_h3"><span class="graybg">长滑（Overswipe）时直接执行操作</span></div>
<p>F7支持当滑动距离过长时，自动执行：</p>
<ol>
<li>向左长滑时，自动执行右侧操作集的最后一个按钮上的动作</li>
<li>向右长滑时，自动执行左侧操作集的第一个按钮上的动作</li>
</ol>
<p>启用长滑时，F7<span style="background-color: #c0c0c0;">通过脚本</span>触发相应按钮的<span style="background-color: #c0c0c0;">click事件</span>。在长滑执行期间，目标按钮会添加额外的<pre class="crayon-plain-tag">swipeout-overswipe-active</pre> 样式类。</p>
<p>启用长滑的示例代码如下：</p>
<pre class="crayon-plain-tag">&lt;div class="swipeout-actions-left"&gt;
    &lt;!-- 左侧的第一个按钮，可以添加swipeout-overswipe以启用长滑 --&gt;
    &lt;a href="#" class="swipeout-overswipe bg-green reply"&gt;回复&lt;/a&gt;
    &lt;a href="#" class="bg-blue forward"&gt;转发&lt;/a&gt;
&lt;/div&gt;
&lt;div class="swipeout-actions-right"&gt;
    &lt;a href="#" class="mark bg-orange"&gt;标记&lt;/a&gt;
    &lt;!-- 右侧的最后一个按钮，可以添加swipeout-overswipe以启用长滑 --&gt;
    &lt;a href="#" class="swipeout-delete swipeout-overswipe"&gt;删除&lt;/a&gt;
&lt;/div&gt;</pre>
<div class="blog_h3"><span class="graybg">滑动列表相关的方法、属性和事件</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">方法/属性/事件</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>app.swipeoutOpen()</td>
<td>
<pre class="crayon-plain-tag">/**
 * 打开（滑动动画）并显示目标列表条目的操作集
 * @param el 列表条目的HTMLElement（li）或CSS选择器
 * @param direction 显示左侧还是右侧的操作集，left/right
 * @param callback 滑动动画执行完毕时执行的回调
 */
function swipeoutOpen( el, direction, callback );</pre>
</td>
</tr>
<tr>
<td>app.swipeoutClose()</td>
<td>
<pre class="crayon-plain-tag">/**
 * 关闭（滑动动画）目标列表条目并隐藏操作集
 * @param el 列表条目的HTMLElement（li）或CSS选择器
 * @param callback 滑动动画执行完毕时执行的回调
 */
function swipeoutClose( el, callback );</pre>
</td>
</tr>
<tr>
<td>app.swipeoutDelete()</td>
<td>
<pre class="crayon-plain-tag">/**
 * 滑动并删除目标列表条目
 * @param el 列表条目的HTMLElement（li）或CSS选择器
 * @param callback 动画执行完毕，即将把li元素从DOM中移除前执行的回调
 */
function swipeoutDelete( el, callback );</pre>
</td>
</tr>
<tr>
<td>app.swipeoutOpenedEl</td>
<td>该属性指向当前处于打开状态的列表条目的HTMLElement</td>
</tr>
<tr>
<td>⚡swipeout</td>
<td rowspan="7">
<p>这些事件的Target都是<pre class="crayon-plain-tag">&lt;li class="swipeout"&gt;</pre> 元素</p>
<ol>
<li>swipeout：手指滑动列表条目的过程时触发，<pre class="crayon-plain-tag">event.detail.progress</pre> 属性存放当前滑动进度的百分比值</li>
<li>open：打开条目时，滑动动画开始时触发</li>
<li>opened：打开条目时，滑动动画结束时触发</li>
<li>close：关闭条目时，滑动动画开始时触发</li>
<li>closed：关闭条目时，滑动动画结束时触发</li>
<li>deleted：删除条目时，滑动动画结束后，即将从DOM中删除li元素前触发</li>
</ol>
</td>
</tr>
<tr>
<td>⚡open</td>
</tr>
<tr>
<td>⚡opened</td>
</tr>
<tr>
<td>⚡close</td>
</tr>
<tr>
<td>⚡closed</td>
</tr>
<tr>
<td>⚡delete</td>
</tr>
<tr>
<td>⚡deleted</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">可排序列表</span></div>
<p>该组件是列表视图的扩展，支持调整条目的顺序，其布局如下：</p>
<pre class="crayon-plain-tag">&lt;!-- 列表块元素上需要额外的sortable样式类 --&gt;
&lt;div class="list-block sortable"&gt;
    &lt;ul&gt;
        &lt;li&gt;
            &lt;div class="item-content"&gt;
                &lt;div class="item-media"&gt;&lt;/div&gt;
                &lt;div class="item-inner"&gt;&lt;/div&gt;
            &lt;/div&gt;
            &lt;!-- 一个可拖拽的小图标，用来调整条目的顺序，默认隐藏 --&gt;
            &lt;div class="sortable-handler"&gt;&lt;/div&gt;
        &lt;/li&gt;
    &lt;/ul&gt;
&lt;/div&gt;</pre>
<div class="blog_h3"><span class="graybg">启用/禁用排序</span></div>
<p>通过HTML：</p>
<pre class="crayon-plain-tag">&lt;!-- data-sortable指定目标可排序列表的CSS选择器  --&gt;
&lt;a href="#" data-sortable=".sortable" class="open-sortable"&gt;启用排序&lt;/a&gt;
&lt;a href="#" data-sortable=".sortable" class="close-sortable"&gt;禁用排序&lt;/a&gt;
&lt;a href="#" data-sortable=".sortable" class="toggle-sortable"&gt;切换启禁用状态&lt;/a&gt;</pre>
<p>通过JavaScript：</p>
<pre class="crayon-plain-tag">/**
 * @param sortableContainer
 *            可排序列表（&lt;div class="list-block sortable"&gt;）的HTMLElement或者CSS选择器 
 *            如果不指定，F7尝试寻找第一个可排序列表
 */
app.sortableOpen( sortableContainer ); // 启用
app.sortableClose( sortableContainer ); // 禁用
app.sortableToggle( sortableContainer ); // 切换</pre>
<div class="blog_h3"><span class="graybg">可排序列表相关事件</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">事件</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>⚡open</td>
<td rowspan="2">这两个事件的Target均为<pre class="crayon-plain-tag">&lt;div class="list-block sortable"&gt;</pre> 元素。分别在启用、禁用可排序功能时触发</td>
</tr>
<tr>
<td>⚡close</td>
</tr>
<tr>
<td>⚡sort</td>
<td>该事件的Target为<pre class="crayon-plain-tag">&lt;li&gt;</pre> ，当用户释放拖拽小图标时触发</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">虚列表（Virtual List）</span></div>
<p>使用虚列表，我们可以在不影响性能的前提下展现具有大量元素的列表。虚列表和搜索栏、无限滚动、下拉刷新、滑动删除等F7组件完全兼容 。</p>
<div class="blog_h3"><span class="graybg">虚列表布局</span></div>
<pre class="crayon-plain-tag">&lt;!-- 额外的virtual-list样式类 --&gt;
&lt;div class="list-block virtual-list"&gt;
    &lt;!-- 内部保持空白 --&gt;
&lt;/div&gt;</pre>
<div class="blog_h3"><span class="graybg">虚列表的初始化</span></div>
<p>需要调用JavaScript API来初始化虚列表的内容：</p>
<pre class="crayon-plain-tag">/**
 * 初始化一个虚列表
 * @param listBlockContainer 列表块（list-block）的HTMLElement或者CSS选择器
 * @param parameters 虚列表初始化参数
 * @return 已初始化的初始化实例
 */
app.virtualList( listBlockContainer, parameters );</pre>
<p>注意列表块元素在此时必须已经存在于DOM中，如果当前不是首页，你应当在pageInit事件内初始化虚列表。 </p>
<div class="blog_h3"><span class="graybg">虚列表初始化参数</span></div>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 25%; text-align: center;">参数</td>
<td style="text-align: center;">类型和默认值</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>items</td>
<td>array</td>
<td>列表条目的数组。不使用模板时，其元素是HTML片段；使用模板时，其元素一般是纯数据（JSON对象）</td>
</tr>
<tr>
<td>rowsBefore</td>
<td>number</td>
<td>在当前屏幕滚动位置之前（即屏幕底部）渲染的条目的数量，默认是单屏可容纳行数的2倍</td>
</tr>
<tr>
<td>rowsAfter</td>
<td>number</td>
<td>在当前屏幕滚动位置之后渲染的条目的数量，默认是单屏可容纳行数的1倍</td>
</tr>
<tr>
<td>cols</td>
<td>number = 1</td>
<td>每行显示的条目数量，当使用动态高度（height参数为函数）的虚列表时，此选项不兼容</td>
</tr>
<tr>
<td>height</td>
<td>number = 44 /<br />function(item) </td>
<td>每个条目的高度，如果指定函数，则高度为函数的返回值<br />注意，该参数并<span style="background-color: #c0c0c0;">不会设置条目的高度样式</span></td>
</tr>
<tr>
<td>template</td>
<td>string / function</td>
<td>Template7字符串模板或者Template已编译模板（函数形式），用来<span style="background-color: #c0c0c0;">渲染单个条目</span>。该目标必须包含条目的完整布局，包括li元素</td>
</tr>
<tr>
<td>renderItem</td>
<td>function(index, item)</td>
<td>该函数可以代替template来渲染每个条目</td>
</tr>
<tr>
<td>dynamicHeightBufferSize</td>
<td>number = 1</td>
<td>用于动态高度列表，控制缓冲大小（buffer size）</td>
</tr>
<tr>
<td>cache</td>
<td>boolean = true</td>
<td>是否启用已渲染条目的DOM缓存。如果列表条目中包含用户交互元素（按钮、表单元素等）或者列表条目可能被修改，可以启用</td>
</tr>
<tr>
<td>updatableScroll</td>
<td>boolean</td>
<td>在滚动时，设备是否更新、处理滚动事件。默认对于所有iOS 8-为fasle</td>
</tr>
<tr>
<td>showFilteredItemsOnly</td>
<td>boolean = false</td>
<td>是否仅显示<pre class="crayon-plain-tag">filter()</pre> 过滤后的元素</td>
</tr>
<tr>
<td>searchByItem</td>
<td>function(query, index, item)</td>
<td>搜索栏使用的搜索判断函数，入参包括查询条件、待判断条目及其索引。如果条目匹配搜索，则该函数应当返回true</td>
</tr>
<tr>
<td>searchAll</td>
<td>function(query, items)</td>
<td>搜索栏使用的搜索函数，你必须遍历items，并返回匹配的item数组</td>
</tr>
<tr>
<td>onItemBeforeInsert</td>
<td>function(list, item)</td>
<td>回调，条目被插入到虚拟文档片段（virtual document fragment）中时调用<br />文档片段相当于包含了本次<span style="background-color: #c0c0c0;">待渲染的列表条目的临时DOM</span>，下同</td>
</tr>
<tr>
<td>onBeforeClear</td>
<td>function(list, fragment)</td>
<td>回调，当前DOM列表将被移除前调用</td>
</tr>
<tr>
<td>onItemsBeforeInsert</td>
<td>function(list, fragment)</td>
<td>回调，当前DOM列表将被移除后，新的文档片段被插入前调研与</td>
</tr>
<tr>
<td>onItemsAfterInsert</td>
<td>function(list, fragment)</td>
<td>回调，新的文档片段被插入后调用</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">虚列表的方法和属性</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%; text-align: center;">方法/属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>items</td>
<td>虚列表包含的条目</td>
</tr>
<tr>
<td>filteredItems</td>
<td>过滤后的条目 </td>
</tr>
<tr>
<td>domCache</td>
<td>条目的DOM缓存 </td>
</tr>
<tr>
<td>params</td>
<td>所有初始化参数 </td>
</tr>
<tr>
<td>listBlock</td>
<td>list-block的Dom7元素实例 </td>
</tr>
<tr>
<td>pageContent</td>
<td>page-content的Dom7元素实例 </td>
</tr>
<tr>
<td>currentFromIndex</td>
<td>当前正在渲染的第一个条目的索引 </td>
</tr>
<tr>
<td>currentToIndex</td>
<td>当前正在渲染的最后一个条目的索引</td>
</tr>
<tr>
<td>reachEnd</td>
<td>如果当前已经渲染了最后一个条目，则为true </td>
</tr>
<tr>
<td>filterItems(indexes)</td>
<td>仅显示indexes数组中包含的条目 </td>
</tr>
<tr>
<td>resetFilter()</td>
<td>禁用过滤器，显示全部条目 </td>
</tr>
<tr>
<td>appendItem(item)</td>
<td rowspan="2">添加条目到虚列表的尾部  </td>
</tr>
<tr>
<td>appendItems(items)</td>
</tr>
<tr>
<td>prependItem(item)</td>
<td rowspan="2">添加条目到虚列表的头部</td>
</tr>
<tr>
<td>prependItems(items)</td>
</tr>
<tr>
<td>replaceItem(index, items)</td>
<td>从指定索引开始替换条目</td>
</tr>
<tr>
<td>replaceAllItems(items)</td>
<td>替换全部条目 </td>
</tr>
<tr>
<td>moveItem(oldIdx, newIdx)</td>
<td>将虚拟条目从oldIndex移动到newIdx </td>
</tr>
<tr>
<td>insertItemBefore(index, item)</td>
<td>在指定索引前面插入元素</td>
</tr>
<tr>
<td>deleteItem(index)</td>
<td rowspan="2">删除指定索引上的元素  </td>
</tr>
<tr>
<td>deleteItems(indexes)</td>
</tr>
<tr>
<td>deleteAllItems()</td>
<td>删除全部元素</td>
</tr>
<tr>
<td>clearCache()</td>
<td>清空DOM缓存 </td>
</tr>
<tr>
<td>destroy()</td>
<td>销毁虚列表的实例，解除所有事件注册 </td>
</tr>
<tr>
<td>update()</td>
<td>更新虚列表，<span style="background-color: #c0c0c0;">重新计算列表的size</span>、<span style="background-color: #c0c0c0;">重新渲染</span>虚列表 </td>
</tr>
<tr>
<td>scrollToItem(index)</td>
<td>滚动虚拟列表到指定的索引 </td>
</tr>
</tbody>
</table>
<p> 注意，上述添加、删除、改变列表元素的方法，其操作结果会<span style="background-color: #c0c0c0;">立即反应到视图上</span>。</p>
<div class="blog_h1"><span class="graybg"> 手风琴与可折叠（Accordion / Collapsible）</span></div>
<div class="blog_h2"><span class="graybg">手风琴布局</span></div>
<p>通用的手风琴布局如下：</p>
<pre class="crayon-plain-tag">&lt;!-- 手风琴条目列表 --&gt;
&lt;div class="accordion-list"&gt;
    &lt;!-- 手风琴条目 --&gt;
    &lt;div class="accordion-item"&gt;
        &lt;!-- 触碰后展开/折叠条目内容，必需 --&gt;
        &lt;div class="accordion-item-toggle"&gt;&lt;/div&gt;
        &lt;!-- 默认隐藏的条目内容，必需 --&gt;
        &lt;div class="accordion-item-content"&gt;&lt;/div&gt;
    &lt;/div&gt;
    &lt;!-- 处于展开状态的手风琴条目，只能有一个 --&gt;
    &lt;div class="accordion-item accordion-item-expanded"&gt;
        &lt;div class="accordion-item-toggle"&gt;&lt;/div&gt;
        &lt;div class="accordion-item-content"&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;</pre>
<div class="blog_h2"><span class="graybg">可折叠布局</span></div>
<p>手风琴布局实质上是多个<span style="background-color: #c0c0c0;">联动的可折叠条目的集合</span> ，可收折叠的手风琴条目可以脱离<pre class="crayon-plain-tag">accordion-list</pre> 单独使用：</p>
<pre class="crayon-plain-tag">&lt;div class="accordion-item"&gt;
    &lt;div class="accordion-item-toggle"&gt;&lt;/div&gt;
    &lt;div class="accordion-item-content"&gt;&lt;/div&gt;
&lt;/div&gt;</pre>
<div class="blog_h2"><span class="graybg">手风琴列表视图</span></div>
<p>可以把手风琴布局和列表视图结合起来使用：</p>
<pre class="crayon-plain-tag">&lt;div class="list-block accordion-list"&gt;
    &lt;ul&gt;
        &lt;li class="accordion-item"&gt;&lt;!-- 使用li代替通用手风琴布局中的div --&gt;
            &lt;a href="" class="item-link item-content"&gt;&lt;!-- 使用item-link代替accordion-item-toggle --&gt;
                &lt;div class="item-inner"&gt;
                    &lt;div class="item-title"&gt;条目标题&lt;/div&gt;
                &lt;/div&gt;
            &lt;/a&gt;
            &lt;div class="accordion-item-content"&gt;条目内容&lt;/div&gt;
        &lt;/li&gt;
    &lt;/ul&gt;
&lt;/div&gt;</pre>
<div class="blog_h2"><span class="graybg">定制手风琴</span></div>
<p> 可以通过如下方式修改手风琴条目展开、折叠时的样式：</p>
<pre class="crayon-plain-tag">&lt;div class="content-block accordion-list custom-accordion"&gt;
    &lt;div class="accordion-item"&gt;
        &lt;div class="accordion-item-toggle"&gt;
            &lt;!--  折叠时的图标，显示一个+号 --&gt;
            &lt;i class="icon icon-plus"&gt;+&lt;/i&gt;
            &lt;!-- 展开时的图标，显示一个-号 --&gt;
            &lt;i class="icon icon-minus"&gt;-&lt;/i&gt;
            &lt;span&gt;条目标题&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class="accordion-item-content"&gt;
            &lt;p&gt;条目内容&lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;style&gt;
/* 设置折叠、展开图标的边框为圆形  */
.custom-accordion .icon-plus, .custom-accordion .icon-minus {
    display: inline-block;
    width: 22px;
    height: 22px;
    border: 1px solid #000;
    border-radius: 100%;
    line-height: 20px;
    text-align: center;
}
&lt;/style&gt;</pre>
<div class="blog_h2"><span class="graybg">手风琴相关方法</span></div>
<pre class="crayon-plain-tag">/**
 * @param item
 *            手风琴条目（accordion-item）的HTMLElement或CSS选择器
 */
app.accordionOpen( item ); // 展开手风琴条目
app.accordionClose( item ); // 折叠手风琴条目
app.accordionToggle( item );// 切换手风琴条目的展开/折叠状态</pre>
<div class="blog_h2"><span class="graybg">手风琴相关事件 </span></div>
<p>以下4个事件的Target均为<pre class="crayon-plain-tag">&lt;div class="accordion-item"&gt;</pre> 。触发时机分别为：</p>
<ol>
<li>open 条目打开动画开始时</li>
<li>opened 条目打开动画结束时</li>
<li>close 条目关闭动画开始时</li>
<li>closed 条目关闭动画结束时</li>
</ol>
<div class="blog_h1"><span class="graybg">卡片（Cards）</span></div>
<p>类似于列表视图，卡片也是包含和组织相关信息的常用方式。单张卡片常常包含一些相关的图片、文字、连接信息，作为更详细信息的入口，本站的首页就是卡片布局典型的例子。F7完整的支持卡片布局。</p>
<div class="blog_h2"><span class="graybg">卡片的布局</span></div>
<pre class="crayon-plain-tag">&lt;!-- 卡片容器 --&gt;
&lt;div class="card"&gt;
    &lt;!-- 卡片头部，常常显示卡片标题，可选 --&gt;
    &lt;div class="card-header"&gt;Header&lt;/div&gt;
    &lt;!-- 卡片主体部分，必需 --&gt;
    &lt;div class="card-content"&gt;
        &lt;!-- 可选的内部包装元素，添加额外的补白 --&gt;
        &lt;div class="card-content-inner"&gt;Card content&lt;/div&gt;
    &lt;/div&gt;
    &lt;!-- 卡片脚注，包含一些附加信息，链接/动作按钮等，可选 --&gt;
    &lt;div class="card-footer"&gt;Footer&lt;/div&gt;
&lt;/div&gt;</pre>
<p>注意，卡片头、脚使用flexbox布局（display:flex），在垂直方向默认居中对齐。如果你需要顶部/底部对齐，可以：</p>
<pre class="crayon-plain-tag">&lt;div class="card-header" valgin="top"&gt;
&lt;div class="card-footer" valgin="bottom"&gt;</pre>
<div class="blog_h3"><span class="graybg">作为列表条目的卡片</span></div>
<pre class="crayon-plain-tag">&lt;!-- 需要为列表块添加额外的样式类cards-list --&gt;
&lt;div class="list-block cards-list"&gt;
    &lt;ul&gt;
        &lt;li class="card"&gt;&lt;!-- 条目元素需要添加card类 --&gt;
            &lt;div class="card-header"&gt;Card Header&lt;/div&gt;
            &lt;div class="card-content"&gt;
                &lt;div class="card-content-inner"&gt;Card content&lt;/div&gt;
            &lt;/div&gt;
            &lt;div class="card-footer"&gt;Card footer&lt;/div&gt;
        &lt;/li&gt;
        &lt;li class="card"&gt;
            &lt;div class="card-header"&gt;Card Header&lt;/div&gt;
            &lt;div class="card-content"&gt;
                &lt;div class="card-content-inner"&gt;Card content&lt;/div&gt;
            &lt;/div&gt;
            &lt;div class="card-footer"&gt;Card footer&lt;/div&gt;
        &lt;/li&gt;
    &lt;/ul&gt;
&lt;/div&gt;</pre>
<div class="blog_h1"><span class="graybg">Chips</span></div>
<p> 该组件只适用于Material主题。</p>
<p>Chips在一个很小的内联块中显示复杂的信息，其中可以包含照片、小标题、文本。在视觉效果上，Chip表现为圆角小矩形：</p>
<p><img class="aligncenter size-full wp-image-10908" src="https://blog.gmem.cc/wp-content/uploads/2016/01/chips.png" alt="chips" width="314" height="36" /></p>
<div class="blog_h2"><span class="graybg">Chips的布局 </span></div>
<pre class="crayon-plain-tag">&lt;!-- 容器元素 --&gt;
&lt;div class="chip"&gt;
    &lt;!-- 媒体元素，指定一个图片/头像等，可选 --&gt;
    &lt;div class="chip-media"&gt;
        &lt;img src="avator/alex.png"&gt;
    &lt;/div&gt;
    &lt;!-- 文本标签 --&gt;
    &lt;div class="chip-label"&gt;Alex Wong&lt;/div&gt;
    &lt;!-- 删除按钮，可选 --&gt;
    &lt;a href="#" class="chip-delete"&gt;&lt;/a&gt;
&lt;/div&gt;</pre>
<div class="blog_h1"><span class="graybg">按钮</span></div>
<p>F7框架包含了大量开箱即用的按钮组件，只需要把合适的样式类添加到<span style="background-color: #c0c0c0;">链接/按钮/提交input</span>元素上即可。</p>
<p>F7中的按钮默认均为<pre class="crayon-plain-tag">display:block</pre> ，且<span style="background-color: #c0c0c0;">占据父容器100%宽度</span>。</p>
<div class="blog_h2"><span class="graybg">iOS主题按钮</span></div>
<p>普通按钮，默认具有蓝色边框和蓝色文字样式：</p>
<pre class="crayon-plain-tag">&lt;p&gt;&lt;a href="#" class="button"&gt;Button&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;a href="#" class="button"&gt;Button&lt;/a&gt;&lt;/p&gt;</pre>
<p>激活态按钮，默认具有蓝色背景和白色文字样式：</p>
<pre class="crayon-plain-tag">&lt;a href="#" class="button active"&gt;Active Button&lt;/a&gt;</pre>
<p>圆角按钮，具有更加圆润的边框：</p>
<pre class="crayon-plain-tag">&lt;p&gt;&lt;a href="#" class="button button-round"&gt;Round Button&lt;/a&gt;&lt;/p&gt;</pre>
<p>大按钮，比普通按钮更高：</p>
<pre class="crayon-plain-tag">&lt;a href="#" class="button button-big"&gt;Big Button &lt;/a&gt;</pre>
<p>按钮行，其中每个按钮具有相同的宽度，没有边距：</p>
<pre class="crayon-plain-tag">&lt;p class="buttons-row"&gt;
    &lt;a href="#" class="button"&gt;Button 1&lt;/a&gt;
    &lt;a href="#" class="button"&gt;Button 2&lt;/a&gt;
    &lt;a href="#" class="button"&gt;Button 3&lt;/a&gt;
&lt;/p&gt;</pre>
<p>填充按钮，看上去和激活态按钮类似，但是点击/触碰时有额外的样式效果：</p>
<pre class="crayon-plain-tag">&lt;a href="#" class="button button-fill"&gt;Fill Button &lt;/a&gt;</pre>
<p>彩色按钮：</p>
<pre class="crayon-plain-tag">&lt;!-- 彩色按钮，在按钮行上指定主题 --&gt;
&lt;p class="buttons-row theme-pink"&gt;
    &lt;a href="#" class="button active"&gt;Button 1&lt;/a&gt;
    &lt;a href="#" class="button"&gt;Button 2&lt;/a&gt;
    &lt;a href="#" class="button"&gt;Button 3&lt;/a&gt;
&lt;/p&gt;
&lt;!-- 彩色按钮，指定单个按钮的颜色类 --&gt;
&lt;a href="#" class="button button-fill color-green"&gt;Green&lt;/a&gt;</pre>
<p>由按钮组成的列表视图：</p>
<pre class="crayon-plain-tag">&lt;div class="content-block-title"&gt;Inset list block buttons&lt;/div&gt;
&lt;div class="list-block inset"&gt;
    &lt;ul&gt;
        &lt;li&gt;
            &lt;a href="#" class="item-link list-button"&gt;List Button 1&lt;/a&gt;
        &lt;/li&gt;
        &lt;li&gt;
            &lt;a href="#" class="item-link list-button"&gt;List Button 2&lt;/a&gt;
        &lt;/li&gt;
        &lt;li&gt;
            &lt;a href="#" class="item-link list-button"&gt;List Button 3&lt;/a&gt;
        &lt;/li&gt;
    &lt;/ul&gt;
&lt;/div&gt;</pre>
<p>使用网格布局对按钮进行排版，这样可以让<span style="background-color: #c0c0c0;">按钮之间有边距</span>： </p>
<pre class="crayon-plain-tag">&lt;div class="row"&gt;
    &lt;div class="col-50"&gt;
        &lt;a href="#" class="button button-big button-red"&gt;Cancel&lt;/a&gt;
    &lt;/div&gt;
    &lt;div class="col-50"&gt;
        &lt;a href="#" class="button button-big button-green"&gt;Submit&lt;/a&gt;
    &lt;/div&gt;
&lt;/div&gt;</pre>
<div class="blog_h2"><span class="graybg">Material主题按钮</span></div>
<p>普通按钮，与iOS主题类似，但是没有边框：</p>
<pre class="crayon-plain-tag">&lt;p&gt;&lt;a href="#" class="button"&gt;Button&lt;/a&gt;&lt;/p&gt;</pre>
<p>按钮行，与iOS主题类似，但是没有边框：</p>
<pre class="crayon-plain-tag">&lt;p class="buttons-row"&gt;
  &lt;a href="#" class="button"&gt;Button&lt;/a&gt;
  &lt;a href="#" class="button"&gt;Button&lt;/a&gt;
&lt;/p&gt;</pre>
<p>浮雕效果按钮（Raised Buttons） ，具有阴影效果：</p>
<pre class="crayon-plain-tag">&lt;a href="#" class="button button-raised"&gt;Button&lt;/a&gt;</pre>
<p>填充按钮，默认具有蓝色背景和白色文字：</p>
<pre class="crayon-plain-tag">&lt;a href="#" class="button button-fill button-raised"&gt;Button&lt;/a&gt;</pre>
<p>为Material波纹效果（从触碰点向周围扩散的动画）指定颜色：</p>
<pre class="crayon-plain-tag">&lt;a href="#" class="button color-green ripple-pink"&gt;Button&lt;/a&gt;</pre>
<p>大按钮、列表块按钮、彩色按钮与iOS主题类似。 </p>
<div class="blog_h1"><span class="graybg">浮动动作按钮</span></div>
<p>浮动动作按钮（FAB）仅适用于Material主题，在视觉效果上，它表现为浮动的（不随页面滚动，通常位于屏幕右下角）、圆形的的按钮 。触碰后，FAB可能会：</p>
<ol>
<li>变形：转换为一个Popover </li>
<li>快速拨号：弹出一组相关的按钮</li>
</ol>
<div class="blog_h2"><span class="graybg">浮动按钮的布局</span></div>
<pre class="crayon-plain-tag">&lt;!-- 当前页面 --&gt;
&lt;div class="page navbar-fixed"&gt;
    &lt;!-- 导航栏--&gt;
    &lt;div class="navbar"&gt;
        &lt;div class="navbar-inner"&gt;
            &lt;div class="center"&gt;&lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;!-- 浮动动作按钮 --&gt;
    &lt;a href="#" class="floating-button color-pink"&gt;
        &lt;i class="icon icon-plus"&gt;&lt;/i&gt;&lt;!-- 按钮图标 --&gt;
    &lt;/a&gt;
    &lt;!-- 可滚动的页面内容 --&gt;
    &lt;div class="page-content"&gt;
        &lt;div class="content-block"&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;</pre>
<div class="blog_h2"><span class="graybg">变形为Popover</span></div>
<p>如果你需要在触碰FAB后显示一个Popover，可以：</p>
<pre class="crayon-plain-tag">&lt;div class="page navbar-fixed"&gt;
    &lt;!-- open-popover 用于打开一个Popover --&gt;
    &lt;!-- floating-button-to-popover 在打开Popover时显示变形动画 --&gt;
    &lt;!-- data-popover指定目标Popover的CSS选择器 --&gt;
    &lt;a href="#" data-popover=".demo-popver" class="floating-button floating-button-to-popover open-popover color-purple"&gt;
        &lt;i class="icon icon-plus"&gt;&lt;/i&gt;
    &lt;/a&gt;
&lt;/div&gt;
&lt;!-- Popover --&gt;
&lt;div class="popover demo-popover"&gt;
    &lt;div class="popover-inner"&gt;
        &lt;div class="list-block"&gt;
            &lt;ul&gt;
                &lt;li&gt;
                    &lt;a href="#" class="item-content item-link"&gt;
                        &lt;div class="item-inner"&gt;
                            &lt;div class="item-title"&gt;Link&lt;/div&gt;
                        &lt;/div&gt;
                    &lt;/a&gt;
                &lt;/li&gt;
            &lt;/ul&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;</pre>
<div class="blog_h2 blog_h3">快速拨号（弹出一组按钮） </div>
<p>可以在触碰FAB后，向上动画弹出一组按钮。再次触碰FAB，则按相反的动画隐藏这些按钮。你应当保证按钮组元素的数量在3-6之间：</p>
<pre class="crayon-plain-tag">&lt;div class="page navbar-fixed"&gt;
    &lt;div class="navbar"&gt;
        &lt;div class="navbar-inner"&gt;
            &lt;div class="center"&gt;&lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;!-- 快速拨号包装元素 --&gt;
    &lt;div class="speed-dial"&gt;
        &lt;a href="#" class="floating-button"&gt;
            &lt;!-- 第一个按钮：快速拨号按钮组处于关闭状态时显示 --&gt;
            &lt;i class="icon icon-plus"&gt;&lt;/i&gt;
            &lt;!-- 第二个按钮：快速拨号按钮组处于打开状态时显示 --&gt;
            &lt;i class="icon icon-close"&gt;&lt;/i&gt;
        &lt;/a&gt;
        &lt;!-- 快速拨号按钮组 --&gt;
        &lt;div class="speed-dial-buttons"&gt;
            &lt;!-- 第一个按钮（最下面） --&gt;
            &lt;a href="#"&gt;
                &lt;i class="icon demo-icon-email"&gt;&lt;/i&gt;
            &lt;/a&gt;
            &lt;!-- 第二个按钮 --&gt;
            &lt;a href="#"&gt;
                &lt;i class="icon demo-icon-calendar"&gt;&lt;/i&gt;
            &lt;/a&gt;
            &lt;!-- 第三个按钮 --&gt;
            &lt;a href="#"&gt;
                &lt;i class="icon demo-icon-upload"&gt;&lt;/i&gt;
            &lt;/a&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="page-content"&gt;&lt;/div&gt;
&lt;/div&gt;</pre>
<div class="blog_h1"><span class="graybg">表单</span></div>
<p>F7支持大量的表单元素类型，表单元素通常在列表视图中组织。</p>
<div class="blog_h2"><span class="graybg">表单布局</span></div>
<pre class="crayon-plain-tag">&lt;!-- 表单中的元素（字段）往往放置到一个列表视图中 --&gt;
&lt;form id="formx" class="list-block"&gt; &lt;!-- 有时也使用div元素 --&gt;
    &lt;ul&gt;
        &lt;li&gt;
            &lt;div class="item-content"&gt;
                &lt;!-- 图标为可选元素 --&gt;
                &lt;div class="item-media"&gt;字段图标&lt;/div&gt;
                &lt;div class="item-inner"&gt;
                    &lt;!-- 标签为可选元素 --&gt;
                    &lt;div class="item-title label"&gt;字段标签&lt;/div&gt;
                    &lt;!-- 表单元素的包装元素 --&gt;
                    &lt;div class="item-input"&gt;
                        &lt;!-- 各类型字段的HTML标记存放在这里 --&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/li&gt;
    &lt;/ul&gt;
&lt;/form&gt;</pre>
<div class="blog_h2"><span class="graybg">支持的基本表单元素类型 </span></div>
<p>可以放在<pre class="crayon-plain-tag">&lt;div class="item-input"&gt;</pre> 内部的表单元素如下表：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 20%; text-align: center;">元素类型</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>各种文本元素</td>
<td>支持text、password、email、url、tel、date、number、datetime-local等类型：<br />
<pre class="crayon-plain-tag">&lt;div class="item-input"&gt;
    &lt;input type="text"&gt;
&lt;/div&gt;</pre>
</td>
</tr>
<tr>
<td>选择菜单</td>
<td>
<pre class="crayon-plain-tag">&lt;div class="item-input"&gt;
    &lt;select&gt;&lt;/select&gt;
&lt;/div&gt;</pre>
</td>
</tr>
<tr>
<td>多行文本</td>
<td>
<pre class="crayon-plain-tag">&lt;div class="item-input"&gt;
    &lt;textarea&gt;&lt;/textarea&gt;
&lt;/div&gt;</pre></p>
<p> 注意，作为列表条目时，需要额外的对齐属性：</p>
<pre class="crayon-plain-tag">&lt;li class="align-top"&gt;&lt;!-- 必须顶部对齐 --&gt;
    &lt;div class="item-content"&gt;
        &lt;div class="item-inner"&gt;
            &lt;div class="item-title label"&gt;&lt;/div&gt;
            &lt;div class="item-input"&gt;
                &lt;textarea&gt;&lt;/textarea&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/li&gt;</pre>
</td>
</tr>
<tr>
<td>可缩放多行文本</td>
<td>文本框的大小会随着其内容自动缩放：<br />
<pre class="crayon-plain-tag">&lt;div class="item-input"&gt;
    &lt;textarea class="resizable"&gt;&lt;/textarea&gt;
&lt;/div&gt;</pre>
</td>
</tr>
<tr>
<td>开关</td>
<td>需要额外的包装元素：<br />
<pre class="crayon-plain-tag">&lt;div class="item-input"&gt;
    &lt;label class="label-switch"&gt;&lt;!-- 额外的包装 --&gt;
        &lt;input type="checkbox"&gt;
        &lt;div class="checkbox"&gt;&lt;/div&gt;
    &lt;/label&gt;
&lt;/div&gt;</pre>
</td>
</tr>
<tr>
<td>滑块</td>
<td>可以选择一个范围的值，需要额外的包装元素：<br />
<pre class="crayon-plain-tag">&lt;div class="item-input"&gt;
    &lt;div class="range-slider"&gt;&lt;!-- 额外的包装 --&gt;
        &lt;input type="range" min="0" max="100" step="0.1"&gt;
    &lt;/div&gt;
&lt;/div&gt;</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">基本表单元素完整示例</span></div>
<p><pre class="crayon-plain-tag">&lt;form id="formx" class="list-block"&gt;
    &lt;ul&gt;
        &lt;!-- 文本输入 --&gt;
        &lt;li&gt;
            &lt;div class="item-content"&gt;
                &lt;div class="item-media"&gt;
                    &lt;i class="icon icon-form-name"&gt;&lt;/i&gt;
                &lt;/div&gt;
                &lt;div class="item-inner"&gt;
                    &lt;div class="item-title label"&gt;Name&lt;/div&gt;
                    &lt;!-- 普通文本框 --&gt;
                    &lt;div class="item-input"&gt;
                        &lt;input type="text" placeholder="Your name"&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/li&gt;
        &lt;li&gt;
            &lt;div class="item-content"&gt;
                &lt;div class="item-media"&gt;
                    &lt;i class="icon icon-form-email"&gt;&lt;/i&gt;
                &lt;/div&gt;
                &lt;div class="item-inner"&gt;
                    &lt;div class="item-title label"&gt;E-mail&lt;/div&gt;
                    &lt;div class="item-input"&gt;
                        &lt;!-- 电子邮件 --&gt;
                        &lt;input type="email" placeholder="E-mail"&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/li&gt;
        &lt;!-- 下拉选择菜单 --&gt;
        &lt;li&gt;
            &lt;div class="item-content"&gt;
                &lt;div class="item-media"&gt;
                    &lt;i class="icon icon-form-gender"&gt;&lt;/i&gt;
                &lt;/div&gt;
                &lt;div class="item-inner"&gt;
                    &lt;div class="item-title label"&gt;Gender&lt;/div&gt;
                    &lt;div class="item-input"&gt;
                        &lt;select&gt;
                            &lt;option&gt;Male&lt;/option&gt;
                            &lt;option&gt;Female&lt;/option&gt;
                        &lt;/select&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/li&gt;
        &lt;!-- 日期控件 --&gt;
        &lt;li&gt;
            &lt;div class="item-content"&gt;
                &lt;div class="item-media"&gt;
                    &lt;i class="icon icon-form-calendar"&gt;&lt;/i&gt;
                &lt;/div&gt;
                &lt;div class="item-inner"&gt;
                    &lt;div class="item-title label"&gt;Birth date&lt;/div&gt;
                    &lt;div class="item-input"&gt;
                        &lt;input type="date" placeholder="Birth day" value="2014-04-30"&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/li&gt;
        &lt;!-- 开关 --&gt;
        &lt;li&gt;
            &lt;div class="item-content"&gt;
                &lt;div class="item-media"&gt;
                    &lt;i class="icon icon-form-toggle"&gt;&lt;/i&gt;
                &lt;/div&gt;
                &lt;div class="item-inner"&gt;
                    &lt;div class="item-title label"&gt;Switch&lt;/div&gt;
                    &lt;div class="item-input"&gt;
                        &lt;label class="label-switch"&gt;
                            &lt;input type="checkbox"&gt;
                            &lt;div class="checkbox"&gt;&lt;/div&gt;
                        &lt;/label&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/li&gt;
        &lt;!-- 滑块 --&gt;
        &lt;li&gt;
            &lt;div class="item-content"&gt;
                &lt;div class="item-media"&gt;
                    &lt;i class="icon icon-form-settings"&gt;&lt;/i&gt;
                &lt;/div&gt;
                &lt;div class="item-inner"&gt;
                    &lt;div class="item-title label"&gt;Slider&lt;/div&gt;
                    &lt;div class="item-input"&gt;
                        &lt;div class="range-slider"&gt;
                            &lt;input type="range" min="0" max="100" value="50" step="0.1"&gt;
                        &lt;/div&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/li&gt;
        &lt;!-- 多行文本 --&gt;
        &lt;li class="align-top"&gt;
            &lt;div class="item-content"&gt;
                &lt;div class="item-media"&gt;
                    &lt;i class="icon icon-form-comment"&gt;&lt;/i&gt;
                &lt;/div&gt;
                &lt;div class="item-inner"&gt;
                    &lt;div class="item-title label"&gt;Textarea&lt;/div&gt;
                    &lt;div class="item-input"&gt;
                        &lt;textarea&gt;&lt;/textarea&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/li&gt;
    &lt;/ul&gt;
&lt;/form&gt;</pre>
<div class="blog_h2"><span class="graybg">复选框组/单选框组</span></div>
<p>这是一个列表视图的扩展，可用于创建复选框/单选框的分组。</p>
<div class="blog_h3"><span class="graybg">复选框组</span></div>
<pre class="crayon-plain-tag">&lt;div class="list-block"&gt;
    &lt;ul&gt;
        &lt;!-- 第一个复选框 --&gt;
        &lt;li&gt;
            &lt;!-- 列表条目的内容必须是label元素，且添加样式类label-checkbox --&gt;
            &lt;label class="label-checkbox item-content"&gt;
                &lt;!-- input必须是第一个元素 --&gt;
                &lt;input type="checkbox" name="my-checkbox" value="Books" checked="checked"&gt;&lt;!-- 默认选中 --&gt;
                &lt;!-- 图标 --&gt;
                &lt;div class="item-media"&gt;
                    &lt;i class="icon icon-form-checkbox"&gt;&lt;/i&gt;
                &lt;/div&gt;
                &lt;!-- 文本 --&gt;
                &lt;div class="item-inner"&gt;
                    &lt;div class="item-title"&gt;Books&lt;/div&gt;
                &lt;/div&gt;
            &lt;/label&gt;
        &lt;/li&gt;
        &lt;!-- 第二个复选框 --&gt;
        &lt;li&gt;
            &lt;label class="label-checkbox item-content"&gt;
                &lt;input type="checkbox" name="my-checkbox" value="Movies"&gt;
                &lt;div class="item-media"&gt;
                    &lt;i class="icon icon-form-checkbox"&gt;&lt;/i&gt;
                &lt;/div&gt;
                &lt;div class="item-inner"&gt;
                    &lt;div class="item-title"&gt;Movies&lt;/div&gt;
                &lt;/div&gt;
            &lt;/label&gt;
        &lt;/li&gt;
    &lt;/ul&gt;
&lt;/div&gt;</pre>
<div class="blog_h3"><span class="graybg">单选框组</span></div>
<p>Material主题支持额外的图标，iOS主题则不支持图标：</p>
<pre class="crayon-plain-tag">&lt;div class="list-block"&gt;
    &lt;ul&gt;
        &lt;!-- 第一个单选框 --&gt;
        &lt;li&gt;
            &lt;!-- 列表条目的内容必须是label元素，且添加样式类label-radio --&gt;
            &lt;label class="label-radio item-content"&gt;
                &lt;!-- input必须是第一个元素 --&gt;
                &lt;input type="radio" name="my-radio" value="Books" checked="checked"&gt;&lt;!-- 默认选中 --&gt;
                &lt;!-- 图标，仅Material主题支持 --&gt;
                &lt;div class="item-media"&gt;
                    &lt;i class="icon icon-form-radio"&gt;&lt;/i&gt;
                &lt;/div&gt;
                &lt;div class="item-inner"&gt;
                    &lt;div class="item-title"&gt;Books&lt;/div&gt;
                &lt;/div&gt;
            &lt;/label&gt;
        &lt;/li&gt;
        &lt;!-- 第二个单选框 --&gt;
        &lt;li&gt;
            &lt;label class="label-radio item-content"&gt;
                &lt;input type="radio" name="my-radio" value="Movies"&gt;
                &lt;div class="item-inner"&gt;
                    &lt;div class="item-title"&gt;Movies&lt;/div&gt;
                &lt;/div&gt;
            &lt;/label&gt;
        &lt;/li&gt;
    &lt;/ul&gt;
&lt;/div&gt;</pre>
<div class="blog_h3"><span class="graybg">与媒体列表视图联用</span></div>
<p>复选框组、单选框组可以和媒体列表视图一起工作，展现更丰富的内容。下面是一个示例：</p>
<pre class="crayon-plain-tag">&lt;div class="content-block-title"&gt;你最喜爱的歌曲是？&lt;/div&gt;
&lt;div class="list-block media-list"&gt;
    &lt;ul&gt;
        &lt;li&gt;
            &lt;label class="label-radio item-content"&gt;
                &lt;input type="radio" name="my-radio" checked&gt;
                &lt;div class="item-media"&gt;
                    &lt;img src="..." width="80"&gt;
                &lt;/div&gt;
                &lt;div class="item-inner"&gt;
                    &lt;div class="item-title-row"&gt;
                        &lt;div class="item-title"&gt;黄色潜水艇&lt;/div&gt;
                        &lt;div class="item-after"&gt;$15&lt;/div&gt;
                    &lt;/div&gt;
                    &lt;div class="item-subtitle"&gt;披头士&lt;/div&gt;
                    &lt;div class="item-text"&gt;披头士乐队60年代的一首著名歌曲&lt;/div&gt;
                &lt;/div&gt;
            &lt;/label&gt;
        &lt;/li&gt;
    &lt;/ul&gt;
&lt;/div&gt;</pre>
<div class="blog_h2"><span class="graybg">智能选择（Smart Select）</span></div>
<p>使用智能选择，并结合复选框组/单选框组，你可以轻易的把常规的下拉菜单（select）<span style="background-color: #c0c0c0;"> 转换为动态页面</span>。在Native的iOS应用中，经常可以看到类似的UI特性。</p>
<div class="blog_h3"><span class="graybg">智能选择的基本布局</span></div>
<pre class="crayon-plain-tag">&lt;form class="list-block"&gt;
    &lt;ul&gt;
        &lt;!-- 第一个智能选择组件 --&gt;
        &lt;li&gt;
            &lt;!-- 需要额外的 "smart-select" 样式类 --&gt;
            &lt;a href="#" class="item-link smart-select"&gt;
                &lt;select name="fruits"&gt;
                    &lt;option value="apple" selected&gt;苹果&lt;/option&gt;
                    &lt;option value="pineapple"&gt;橘子&lt;/option&gt;
                &lt;/select&gt;
                &lt;!-- 在列表视图中显示的内容 --&gt;
                &lt;div class="item-content"&gt;
                    &lt;div class="item-inner"&gt;
                        &lt;!-- 文本 --&gt;
                        &lt;div class="item-title"&gt;水果&lt;/div&gt;
                        &lt;!-- 哪个菜单选项处于选中状态 --&gt;
                        &lt;div class="item-after"&gt;苹果&lt;/div&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
            &lt;/a&gt;
        &lt;/li&gt;
        &lt;!-- 第二个智能选择组件 --&gt;
    &lt;/ul&gt;
&lt;/form&gt;</pre>
<p>注意智能选择只能在<span style="background-color: #c0c0c0;">已初始化的视图</span>中使用。 </p>
<div class="blog_h3"><span class="graybg">单搜索栏的智能选择</span></div>
<pre class="crayon-plain-tag">&lt;div class="list-block"&gt;
    &lt;ul&gt;
        &lt;!-- 
            带搜索栏的智能选择条目 
            data-searchbar 为此智能选择启用搜索栏
            data-searchbar-placeholder 搜索框的占位符文本
            data-searchbar-cancel 取消按钮的文本
        --&gt;
        &lt;li&gt;
            &lt;a href="#" class="item-link smart-select" data-searchbar="true" data-searchbar-placeholder="Search fruits"&gt;
            &lt;/a&gt;
        &lt;/li&gt;
    &lt;/ul&gt;
&lt;/div&gt;</pre>
<div class="blog_h3"><span class="graybg">定制子页面的标题和返回链接 </span></div>
<p>默认的，智能选择子页面的标题与父页面一致，返回按钮的文本默认则由应用初始化参数<pre class="crayon-plain-tag">smartSelectBackText</pre>  指定。这些文本都可以定制：</p>
<pre class="crayon-plain-tag">&lt;div class="list-block"&gt;
    &lt;ul&gt;
        &lt;!--
            data-page-title 子页面标题
            data-back-text 子页面返回按钮文本
        --&gt;
        &lt;li&gt;
            &lt;a href="#" class="item-link smart-select" data-page-title="美味的水果" data-back-text="返回"&gt;&lt;/a&gt;
        &lt;/li&gt;
    &lt;/ul&gt;
&lt;/div&gt;</pre>
<div class="blog_h3"><span class="graybg">在Popup中打开子页面</span></div>
<p>F7支持以Popup的方式打开智能选择组件，设置应用初始化参数<pre class="crayon-plain-tag">smartSelectOpenIn:'popup'</pre>  则默认所有智能选择均以Popup的方式打开。下面演示如何指定单个智能选择以Popup方式打开：</p>
<pre class="crayon-plain-tag">&lt;div class="list-block"&gt;
    &lt;ul&gt;
        &lt;li&gt;
            &lt;!-- data-open-in设置为popup，则以弹窗方式打开智能选择 --&gt;
            &lt;a href="#" class="item-link smart-select" data-open-in="popup"&gt; &lt;/a&gt;
        &lt;/li&gt;
    &lt;/ul&gt;
&lt;/div&gt;</pre>
<div class="blog_h3"><span class="graybg">以选取器（Picker）的方式打开子页面</span></div>
<p>F7还支持在选取器模态窗口，而不是页面中打开智能选择组件，设置应用初始化参数<pre class="crayon-plain-tag">smartSelectOpenIn:'picker'</pre>  则默认所有智能选择均以Picker的方式打开。下面演示如何指定单个智能选择以Picker方式打开：</p>
<pre class="crayon-plain-tag">&lt;div class="list-block"&gt;
    &lt;ul&gt;
        &lt;li&gt;
            &lt;!-- 
                data-open-in设置为picker，则以选取器方式打开智能选择 
                data-picker-height 指定选取器的高度
            --&gt;
            &lt;a href="#" class="item-link smart-select" data-open-in="picker" data-picker-height="200px"&gt;&lt;/a&gt;
        &lt;/li&gt;
    &lt;/ul&gt;
&lt;/div&gt;</pre>
<div class="blog_h3"><span class="graybg">为选项指定图标</span></div>
<pre class="crayon-plain-tag">&lt;a href="#" class="item-link smart-select"&gt;
    &lt;!-- 在select元素上添加data-option-image、data-option-icon，为所有选项指定默认图标 --&gt;
    &lt;select name="fruits" data-option-image=""&gt;
        &lt;!-- data-option-image 指定图标 --&gt;
        &lt;option value="apple" selected data-option-image=""&gt;苹果&lt;/option&gt;
        &lt;option value="pineapple" data-option-image=""&gt;菠萝&lt;/option&gt;
        &lt;!-- data-option-color 指定文本颜色 --&gt;
        &lt;option value="pear" data-option-color="orange" data-option-image&gt;梨&lt;/option&gt;
    &lt;/select&gt;
    &lt;div class="item-content"&gt;
        &lt;div class="item-inner"&gt;
            &lt;div class="item-title"&gt;水果&lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/a&gt;</pre>
<div class="blog_h3"><span class="graybg">多选和选项分组</span></div>
<pre class="crayon-plain-tag">&lt;div class="list-block"&gt;
    &lt;ul&gt;
        &lt;li&gt;
            &lt;a href="#" class="item-link smart-select"&gt;
                &lt;!-- multiple ：表示支持多选 --&gt;
                &lt;!-- maxlength：指定最多选中的选项个数 --&gt;
                &lt;select name="car" multiple maxlength="3"&gt;
                    &lt;!-- 使用optgroup，可以将选项分组，组名由label指定 --&gt;
                    &lt;optgroup label="日产"&gt;
                        &lt;option value="honda" selected&gt;本田&lt;/option&gt;
                        &lt;option value="lexus"&gt;雷克萨斯&lt;/option&gt;
                        &lt;option value="mazda"&gt;马自达&lt;/option&gt;
                        &lt;option value="nissan"&gt;尼桑&lt;/option&gt;
                        &lt;option value="toyota"&gt;丰田&lt;/option&gt;
                    &lt;/optgroup&gt;
                    &lt;optgroup label="德产"&gt;
                        &lt;option value="audi"&gt;奥迪&lt;/option&gt;
                        &lt;option value="bmw"&gt;宝马&lt;/option&gt;
                        &lt;option value="mercedes"&gt;奔驰&lt;/option&gt;
                        &lt;option value="volvo"&gt;沃尔沃&lt;/option&gt;
                    &lt;/optgroup&gt;
                &lt;/select&gt;
                &lt;div class="item-content"&gt;
                    &lt;div class="item-inner"&gt;
                        &lt;div class="item-title"&gt;汽车品牌&lt;/div&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
            &lt;/a&gt;
        &lt;/li&gt;
    &lt;/ul&gt;
&lt;/div&gt;</pre>
<div class="blog_h3"><span class="graybg">选择后自动关闭智能选择</span></div>
<pre class="crayon-plain-tag">&lt;div class="list-block"&gt;
    &lt;ul&gt;
        &lt;li&gt;
        &lt;!-- 设置data-back-on-select="true" 则智能选择可以自动关闭 --&gt;
            &lt;a href="#" class="item-link smart-select" data-back-on-select="true"&gt;
                &lt;select&gt;&lt;/select&gt;
                &lt;div class="item-content"&gt;&lt;/div&gt;
            &lt;/a&gt;
        &lt;/li&gt;
    &lt;/ul&gt;
&lt;/div&gt;</pre>
<div class="blog_h3">与虚列表联用</div>
<p>如果选项特别多（例如1000个），则可以使用虚列表：</p>
<pre class="crayon-plain-tag">&lt;div class="list-block"&gt;
    &lt;ul&gt;
        &lt;li&gt;
            &lt;!--
                data-virtual-list="true" 使用虚列表
                data-virtual-list-height="55"设置虚列表单条目的高度为55px
            --&gt;
            &lt;a href="#" class="item-link smart-select" data-virtual-list="true" data-virtual-list-height="55"&gt;
                &lt;select name="numbers"&gt;
                    &lt;option value="1"&gt;1&lt;/option&gt;
                    &lt;option value="2"&gt;2&lt;/option&gt;
                    ...
                    &lt;option value="100000"&gt;100000&lt;/option&gt;
                &lt;/select&gt;
                &lt;div class="item-content"&gt;&lt;/div&gt;
            &lt;/a&gt;
        &lt;/li&gt;
    &lt;/ul&gt;
&lt;/div&gt;</pre>
<div class="blog_h3"><span class="graybg">为智能选择设置色彩主题</span></div>
<p>可以为智能选择页面、Popup设置主题：</p>
<pre class="crayon-plain-tag">&lt;div class="list-block"&gt;
    &lt;ul&gt;
        &lt;li&gt;
            &lt;!-- data-navbar-theme="red" 设置导航栏为红色主题 --&gt;
            &lt;!-- data-form-theme="green" 设置表单为绿色主题 --&gt;
            &lt;a href="#" class="item-link smart-select" data-navbar-theme="red" data-form-theme="green"&gt;
                &lt;select name="car"&gt;&lt;/select&gt;
                &lt;div class="item-content"&gt;&lt;/div&gt;
            &lt;/a&gt;
        &lt;/li&gt;
    &lt;/ul&gt;
&lt;/div&gt;</pre>
<div class="blog_h3"><span class="graybg">设置selected的选项</span></div>
<pre class="crayon-plain-tag">&lt;div class="list-block"&gt;
    &lt;ul&gt;
        &lt;li&gt;
            &lt;a href="#" class="item-link smart-select"&gt;
                &lt;select name="fruits"&gt;
                    &lt;option value="apple"&gt;Apple&lt;/option&gt;&lt;!-- 被选中 --&gt;
                    &lt;option value="pineapple"&gt;Pineapple&lt;/option&gt;
                &lt;/select&gt;
                &lt;div class="item-content"&gt;
                    &lt;div class="item-inner"&gt;
                        &lt;div class="item-title"&gt;Fruit&lt;/div&gt;
                        &lt;!-- 在item-after元素上设置smart-select-value，则其文本对应的选项自动选中 --&gt;
                        &lt;div class="item-after smart-select-value"&gt;Apple&lt;/div&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
            &lt;/a&gt;
        &lt;/li&gt;
    &lt;/ul&gt;
&lt;/div&gt;</pre>
<div class="blog_h3"><span class="graybg">与智能选择相关的JavaScript API </span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%; text-align: center;">方法</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>app.smartSelectOpen()</td>
<td>
<pre class="crayon-plain-tag">/**
 * 打开一个智能选择页面/弹窗/选取器
 * @param smartSelect 目标.smart-select元素对应的HTMLElement或者CSS选择器
 */
function smartSelectOpen(smartSelect);</pre>
</td>
</tr>
<tr>
<td>app.smartSelectAddOption()</td>
<td>
<pre class="crayon-plain-tag">/**
 * 动态的为智能选择添加选项
 * @param select HTML或者CSS选择器，对应select或者optgroup元素
 * @param optionHTML 被插入的选项的完整HTML
 * @param index 新选项插入的索引，如果不指定则插入到最后
 */
function smartSelectAddOption(select, optionHTML, index)</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">禁用表单元素</span></div>
<p>F7支持为任何元素添加“禁用”样式：</p>
<ol>
<li>disabled样式类：可以为任何元素添加禁用样式</li>
<li>disabled属性：可以为表单元素添加禁用样式</li>
</ol>
<p>被禁用的元素具有较低的不透明度，而且<span style="background-color: #c0c0c0;">不接受任何touch/click事件</span>。</p>
<pre class="crayon-plain-tag">&lt;div class="list-block"&gt;
    &lt;ul&gt;
        &lt;!-- 此列表条目被禁用 --&gt;
        &lt;li class="item-content disabled"&gt;
            &lt;div class="item-inner"&gt;
                &lt;div class="item-title"&gt;&lt;/div&gt;
            &lt;/div&gt;
        &lt;/li&gt;
        &lt;!-- 此条目的表单元素被禁用 --&gt;
        &lt;li class="item-content"&gt;
            &lt;div class="item-inner"&gt;
                &lt;div class="item-input"&gt;
                    &lt;!-- 禁用的元素 --&gt;
                    &lt;input type="text" name="name" disabled&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/li&gt;
    &lt;/ul&gt;
&lt;/div&gt;</pre>
<div class="blog_h2"><span class="graybg">表单数据读取/载入</span></div>
<p>F7提供了一些便利的方法，用来收集表单数据为JSON，或者读取JSON并设置表单元素的值。</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 25%; text-align: center;">方法</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>app.formToJSON()</td>
<td>
<pre class="crayon-plain-tag">/**
 * 收集表单字段的值，并组成一个JSON对象
 * @param form 表单元素的HTMLElement或者CSS选择器
 * @returns 返回JSON对象
 */
function formToJSON( form );</pre></p>
<p> 注意：</p>
<ol>
<li>只有<span style="background-color: #c0c0c0;">具有name属性</span>的input/select/textarea等元素，其值才会包含在结果JSON。name属性作为<span style="background-color: #c0c0c0;">JSON字段的名称</span></li>
<li>复选框checkbox和多选的select，其值<span style="background-color: #c0c0c0;">存储为数组</span></li>
</ol>
</td>
</tr>
<tr>
<td>app.formFromJSON()</td>
<td>
<pre class="crayon-plain-tag">/**
 * 使用JSON填充表单
 * @param form 目标表单的HTMLElement或者CSS选择器
 * @param formData 用来填充的JSON
 */
function formFromJSON(form, formData);</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">表单数据的本地存储</span></div>
<p>F7支持自动存储/加载表单数据，即使是Ajax页面也支持。要启用本地存储，只需要：</p>
<ol>
<li>为表单添加额外的样式类：store-data</li>
<li>为表单设置id属性</li>
<li>为所有表单字段设置name属性，不设置的字段将被忽略</li>
</ol>
<p> JavaScript API不需要直接调用。下面是一个样例：</p>
<pre class="crayon-plain-tag">&lt;!-- 设置表单的id，添加store-data样式类 --&gt;
&lt;form id="my-form" class="list-block store-data"&gt;
    &lt;ul&gt;
        &lt;li&gt;
            &lt;div class="item-content"&gt;
                &lt;div class="item-inner"&gt;
                    &lt;div class="item-title label"&gt;姓名&lt;/div&gt;
                    &lt;div class="item-input"&gt;
                        &lt;!-- 确保表单元素具有name属性 --&gt;
                        &lt;input type="text" name="name" placeholder="请输入你的姓名"&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/li&gt;
        &lt;li&gt;
    &lt;/ul&gt;
&lt;/form&gt;</pre>
<div class="blog_h3"><span class="graybg">本地存储如何工作</span></div>
<ol>
<li>在监听到<pre class="crayon-plain-tag">pageInit</pre> 事件后，F7会调用<pre class="crayon-plain-tag">formFromJSON</pre> 函数，从HTML5的本地存储（Local Storage）中加载数据</li>
<li>在监听到表单元素的<pre class="crayon-plain-tag">change</pre> 事件后，F7会调用<pre class="crayon-plain-tag">formToJSON()</pre> 函数，存储最新的表单元素值到本地存储中</li>
</ol>
<p>任何一个表单都具有自己的本地存储键，键命名规则为<pre class="crayon-plain-tag">localStorage.f7form-[formID]</pre> ，其中formID就是表单的id属性。本地存储的值是串行化后的JSON对象。</p>
<div class="blog_h3"><span class="graybg">本地存储相关方法和事件</span></div>
<p>F7提供了一些API，可以用来管理表单的本地存储：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 45%; text-align: center;">方法/事件</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>app.formGetData(formId)</td>
<td>获得特定表单的本地存储，返回JSON对象</td>
</tr>
<tr>
<td>app.formDeleteData(formId)</td>
<td>删除特地表单的本地存储</td>
</tr>
<tr>
<td>formStoreData(formId, formJSON)</td>
<td>以formJSON替换特定表单的本地存储</td>
</tr>
<tr>
<td>⚡formFromJSON</td>
<td>formFromJSON()方法被调用后触发</td>
</tr>
<tr>
<td>⚡formToJSON</td>
<td>formToJSON()方法被调用后触发</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">基于XHR的表单提交</span></div>
<p>F7支持基于Ajax的自动表单提交，自动提交有两种方式</p>
<ol>
<li>当用户点击submit按钮时，或者表单的submit事件被手工触发时</li>
<li>当用户修改表单字段值时，或者表单/表单元素的change事件被手工触发时</li>
</ol>
<div class="blog_h3"><span class="graybg">基于submit事件的自动提交</span></div>
<pre class="crayon-plain-tag">&lt;!--
添加ajax-submit样式类，则表单支持submit事件时自动提交
action 存放接受请求的地址
method 使用的HTTP方法
enctype HTML的内容类型（Content type）
--&gt;
&lt;form action="/save" method="GET" class="ajax-submit" enctype="application/x-www-form-urlencoded"&gt;&lt;/form&gt;</pre>
<div class="blog_h3"><span class="graybg">基于change事件的自动提交</span> </div>
<p>与上面类似：</p>
<pre class="crayon-plain-tag">&lt;form action="/save" method="GET" class="ajax-submit-onchange"&gt;&lt;/form&gt;</pre>
<div class="blog_h3"><span class="graybg">与XHR表单提交相关的事件</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">事件</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>⚡submitted</td>
<td rowspan="3">
<p>这些事件的Target均为<pre class="crayon-plain-tag">&lt;form class="ajax-submit"&gt;</pre> 元素。分别在：</p>
<ol>
<li>成功处理XHR请求后</li>
<li>在发送XHR请求前</li>
<li>XHR请求处理失败后</li>
</ol>
<p>触发。示例：</p>
<pre class="crayon-plain-tag">$$( 'form.ajax-submit' ).on( 'submitted', function( e ) {
    var xhr = e.detail.xhr; // 浏览器的XMLHttpRequest对象
    var data = e.detail.data; // Ajax响应的内容
} );</pre>
</td>
</tr>
<tr>
<td>⚡beforeSubmit</td>
</tr>
<tr>
<td>⚡submitError</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg"> 页签（Tabs）</span></div>
<p> 使用页签，可以方便的在不同内容直接切换。
<div class="blog_h2"><span class="graybg">页签的布局</span></div>
<pre class="crayon-plain-tag">&lt;!-- 页签包装元素 --&gt;
&lt;div class="tabs"&gt;
    &lt;!-- 页签，必须具有tab类，唯一的id，激活的（可见）页签添加active类 --&gt;
    &lt;div class="tab active" id="tab1"&gt;页签一的内容&lt;/div&gt;
    &lt;div class="tab" id="tab2"&gt;页签二的内容&lt;/div&gt;
    &lt;div class="tab" id="tab3"&gt;页签三的内容&lt;/div&gt;
&lt;/div&gt;</pre>
<div class="blog_h2"><span class="graybg">在页签之间切换</span></div>
<pre class="crayon-plain-tag">&lt;!-- 为链接添加tab-link类，则它可以用来打开页签  --&gt;
&lt;!-- href指定页签的id --&gt;
&lt;!-- active指定此链接的样式为激活的 --&gt;
&lt;a href="#tab1" class="tab-link active"&gt;页签一&lt;/a&gt;
&lt;a href="#tab2" class="tab-link"&gt;页签二&lt;/a&gt;
&lt;a href="#tab3" class="tab-link"&gt;页签三&lt;/a&gt;</pre>
<div class="blog_h3"><span class="graybg">按钮组+页签的典型示例</span></div>
<pre class="crayon-plain-tag">&lt;div class="page-content"&gt;
    &lt;div class="content-block"&gt;
        &lt;!-- 按钮组，用于控制页签 --&gt;
        &lt;div class="buttons-row"&gt;
            &lt;a href="#tab1" class="tab-link active button"&gt;Tab1&lt;/a&gt;
            &lt;a href="#tab2" class="tab-link button"&gt;Tab2&lt;/a&gt;
            &lt;a href="#tab3" class="tab-link button"&gt;Tab3&lt;/a&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;!-- 页签--&gt;
    &lt;div class="tabs"&gt;
        &lt;div id="tab1" class="tab active"&gt;
            &lt;div class="content-block"&gt;&lt;/div&gt;
        &lt;/div&gt;
        &lt;div id="tab2" class="tab"&gt;
            &lt;div class="content-block"&gt;&lt;/div&gt;
        &lt;/div&gt;
        &lt;div id="tab3" class="tab"&gt;
            &lt;div class="content-block"&gt;&lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;</pre>
<div class="blog_h2"><span class="graybg">一个链接打开多个页签</span></div>
<p>有时需要在点击链接时，打开（属于不同页签的）多个标签页，这时可以使用data-tab属性而不是href：</p>
<pre class="crayon-plain-tag">&lt;!-- 顶部页签 --&gt;
&lt;div class="tabs tabs-top"&gt;
    &lt;div class="tab tab1 active"&gt;...&lt;/div&gt;
    &lt;div class="tab tab2"&gt;...&lt;/div&gt;
&lt;/div&gt;
&lt;!-- 底部页签 --&gt;
&lt;div class="tabs tabs-bottom"&gt;
    &lt;div class="tab tab1 active"&gt;...&lt;/div&gt;
    &lt;div class="tab tab2"&gt;...&lt;/div&gt;
&lt;/div&gt;
&lt;div class="tab-links"&gt;
    &lt;!-- data-tab指定需要显示的标签页的选择器，.tab1导致顶部、底部页签的第一个标签页同时显示 --&gt;
    &lt;a href="#" class="tab-link" data-tab=".tab1"&gt;Tab 1&lt;/a&gt;
    &lt;a href="#" class="tab-link" data-tab=".tab2"&gt;Tab 2&lt;/a&gt;
&lt;/div&gt;</pre>
<div class="blog_h2"><span class="graybg">启用页签动画</span></div>
<p>需要额外的包装元素：</p>
<pre class="crayon-plain-tag">&lt;div class="tabs-animated-wrap"&gt;
    &lt;div class="tabs"&gt;&lt;/div&gt;
&lt;/div&gt;</pre>
<div class="blog_h2"><span class="graybg">通过滑动手势（Swipe）切换标签页 </span></div>
<pre class="crayon-plain-tag">&lt;div class="pages navbar-fixed"&gt;
    &lt;div data-page="home" class="page with-subnavbar"&gt;
        &lt;div class="navbar"&gt;
            &lt;div class="navbar-inner"&gt;
                &lt;div class="subnavbar"&gt;
                    &lt;!-- 该例子把按钮组放在子导航栏中 --&gt;
                    &lt;div class="buttons-row"&gt;
                        &lt;a href="#tab1" class="button active tab-link"&gt;Tab1&lt;/a&gt;
                        &lt;a href="#tab2" class="button tab-link"&gt;Tab2&lt;/a&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;!-- tabs-swipeable-wrap包装用于启用滑动手势 --&gt;
        &lt;div class="tabs-swipeable-wrap"&gt;
            &lt;div class="tabs"&gt;
                &lt;!-- 使用 page-content类，以便保持每个标签页的滚动位置 --&gt;
                &lt;div id="tab1" class="page-content tab active"&gt;
                    &lt;div class="content-block"&gt;&lt;/div&gt;
                &lt;/div&gt;
                &lt;div id="tab2" class="page-content tab"&gt;
                    &lt;div class="content-block"&gt;&lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;</pre>
<div class="blog_h2"><span class="graybg">页签相关方法和事件</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">方法/事件</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>app.showTab(tab)</td>
<td>激活一个标签页，tab为标签页的HTMLElement或者CSS选择器</td>
</tr>
<tr>
<td>⚡show</td>
<td>该事件的Target为<pre class="crayon-plain-tag">&lt;div class="tab"&gt;</pre> ，当页签变得可见时触发，示例：<br />
<pre class="crayon-plain-tag">$$('#tab1').on('show', function () {
    app.alert('页签一已激活');
});</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg"> Swiper Slider（幻灯片组件）</span></div>
<p>F7的幻灯片组件Swiper非常强大，支持灵活的配置。该组件已经发展为独立组件，可以<span style="background-color: #c0c0c0;">不依赖于F7</span>框架使用，并且用在<span style="background-color: #c0c0c0;">桌面浏览器</span>上也非常合适。</p>
<p>Swiper支持多种特性：</p>
<ol>
<li>分页：默认显示一组小圆点，类似于iOS主屏幕下部的bullet，指示当前是第几张幻灯片</li>
<li>水平或者垂直方向切换，分页也相应的显示在底部/右侧</li>
<li>嵌套的幻灯片</li>
<li>同时显示多列幻灯片</li>
<li>同时显示多行幻灯片</li>
<li>显示左右侧的导航按钮</li>
<li>循环显示幻灯片</li>
<li>各种切换效果：淡入淡出、3D立方体、3D封面流、3D翻转效果</li>
<li>显示幻灯缩略图</li>
<li>键盘导航、鼠标中键导航</li>
<li>动态增减幻灯片数量</li>
<li>视差效果</li>
<li>以分数（1/10）、进度条等方式显示分页信息</li>
<li>支持图片延迟加载</li>
</ol>
<div class="blog_h2"><span class="graybg">幻灯片组件的HTML布局</span></div>
<div class="blog_h3"><span class="graybg">简单幻灯片组件</span></div>
<pre class="crayon-plain-tag">&lt;!-- 主容器，包含幻灯片、分页 --&gt;
&lt;div class="swiper-container"&gt;
    &lt;!-- 幻灯片条目的额外包装元素 --&gt;
    &lt;div class="swiper-wrapper"&gt;
        &lt;!-- 幻灯片条目，可以包含任意HTML --&gt;
        &lt;div class="swiper-slide"&gt;Slide 1&lt;/div&gt;
        &lt;div class="swiper-slide"&gt;Slide 2&lt;/div&gt;
        &lt;div class="swiper-slide"&gt;Slide 3&lt;/div&gt;
    &lt;/div&gt;
    &lt;!-- 包含自动创建的分页小圆点，可选元素 --&gt;
    &lt;div class="swiper-pagination"&gt;&lt;/div&gt;
&lt;/div&gt;</pre>
<div class="blog_h3"><span class="graybg">哈希导航</span></div>
<pre class="crayon-plain-tag">&lt;div class="swiper-container"&gt;
    &lt;div class="swiper-wrapper"&gt;
        &lt;!-- 通过data-hash设置幻灯片的哈希值 --&gt;
        &lt;div class="swiper-slide" data-hash="slide1"&gt;Slide 1&lt;/div&gt;
        &lt;div class="swiper-slide" data-hash="slide2"&gt;Slide 2&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;script type="text/javascript"&gt;
var swiper = new Swiper('.swiper-container', {
    hashnav: true
})
&lt;/script&gt;</pre>
<div class="blog_h3"><span class="graybg">图片和延迟加载 </span></div>
<pre class="crayon-plain-tag">&lt;div class="swiper-container"&gt;
    &lt;div class="swiper-wrapper"&gt;
        &lt;!-- 延迟加载的图片 --&gt;
        &lt;div class="swiper-slide"&gt;
            &lt;img data-src="path/to/picture-1.jpg" class="swiper-lazy"&gt;
            &lt;div class="swiper-lazy-preloader"&gt;&lt;/div&gt;
        &lt;/div&gt;
        &lt;!-- 延迟加载的图片，可以使用data-srcset属性（视网膜屏，4x表示4个物理像素组成1个显示像素） --&gt;
        &lt;div class="swiper-slide"&gt;
            &lt;img data-src="path/to/logo-small.png" data-srcset="path/logo/logo-large.png 2x" class="swiper-lazy"&gt;
            &lt;div class="swiper-lazy-preloader"&gt;&lt;/div&gt;
        &lt;/div&gt;
        &lt;!-- 延迟加载的背景图片 --&gt;
        &lt;div class="swiper-slide"&gt;
            &lt;div data-background="path/to/picture-2.jpg" class="swiper-lazy"&gt;
                &lt;div class="swiper-lazy-preloader"&gt;&lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;!-- 延迟加载的背景图片，设置在幻灯片元素上 --&gt;
        &lt;div data-background="path/to/picture-3.jpg" class="swiper-slide swiper-lazy"&gt;
            &lt;div class="swiper-lazy-preloader"&gt;&lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;script type="text/javascript"&gt;
var swiper = new Swiper('.swiper-container', {
    preloadImages: false,
    lazyLoading: true
});  
&lt;/script&gt;</pre>
<div class="blog_h2"><span class="graybg">初始化幻灯片组件</span></div>
<div class="blog_h3"><span class="graybg">通过HTML</span></div>
<pre class="crayon-plain-tag">&lt;!-- 
    只需为容器添加swiper-init类，即可通过HTML初始化幻灯片
    data-***用于指定初始化参数，必须把驼峰式大小写改为短横线小写风格
    亦可通过data-swiper，传递一个JSON字符串，以指定所有初始化参数
--&gt;
&lt;div class="swiper-container swiper-init" data-speed="400" data-space-between="40" data-pagination=".swiper-pagination"&gt;
    &lt;div class="swiper-wrapper"&gt;&lt;/div&gt;
&lt;/div&gt;</pre>
<p>下面的代码获得通过HTML初始化的幻灯片组件实例：</p>
<pre class="crayon-plain-tag">var swiper = $$('.swiper-container')[0].swiper;
swiper.slideNext(); //调用方法</pre>
<div class="blog_h3"><span class="graybg">通过JavaScript</span></div>
<p>初始化幻灯片，需要容器元素的DOM已经存在，因此需要在pageInit事件/回调中调用初始化函数：</p>
<pre class="crayon-plain-tag">/**
 * @param swiperContainer swiper-container元素的HTMLElement或者CSS选择器
 * @param parameters 幻灯片初始化参数
 * @return 已经初始化的幻灯片对象实例
 */
app.swiper(swiperContainer, parameters);
//另外一种方式
var swiper = new Swiper(swiperContainer, parameters);
//应用举例：
var mySwiper = app.swiper('.swiper-container', {
    speed: 400,
    spaceBetween: 100
});</pre>
<div class="blog_h2"><span class="graybg">幻灯片组件的初始化参数</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">参数</td>
<td style="width: 28%; text-align: center;">类型和默认值</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>initialSlide</td>
<td>number = 0</td>
<td>最初显示的幻灯片的索引</td>
</tr>
<tr>
<td>direction</td>
<td>string = 'horizontal'</td>
<td>幻灯片的切换方向，还可以取值“vertical”<br />如果设置为vertical，则分页bullet显示在右侧而不是底部</td>
</tr>
<tr>
<td>speed</td>
<td>number = 300</td>
<td>幻灯切换动画持续的毫秒数</td>
</tr>
<tr>
<td>width</td>
<td>number</td>
<td rowspan="2">设置组件的宽高，这些选项会导致幻灯片组件失去响应式（Responsive）特性</td>
</tr>
<tr>
<td>height</td>
<td>number</td>
</tr>
<tr>
<td>autoHeight</td>
<td>boolean = false</td>
<td>如果设置为true，那么组件包装元素的高度将自动与当前幻灯片的高度一致</td>
</tr>
<tr>
<td>roundLengths</td>
<td>boolean = false</td>
<td>如果设置为true，则自动舍入幻灯片的宽高为整数，避免某些屏幕上文本显示模糊</td>
</tr>
<tr>
<td>nested</td>
<td>boolean = false</td>
<td>仅在嵌套的、且与父组件direction相同的幻灯组件上设置为true，以修正touch事件拦截</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>自动播放</em></td>
</tr>
<tr>
<td>autoplay</td>
<td>number</td>
<td>自动播放间隔ms，如果不设置该参数，则禁用自动播放</td>
</tr>
<tr>
<td>autoplayStopOnLast</td>
<td>boolean = false</td>
<td>是否在到达最后一张幻灯时停止播放，对循环模式无效</td>
</tr>
<tr>
<td>autoplayDisableOnInteraction</td>
<td>boolean = true</td>
<td>是否在用户与组件交互时，停止自动播放<br />在交互结束后，自动播放会重新开始</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>CSS特效</em></td>
</tr>
<tr>
<td>effect</td>
<td>string = 'slide'</td>
<td>幻灯片切换CSS特效，可以是：
<ol>
<li><span style="color: #333333; font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: 13px; line-height: 22px;">slide：</span><span style="color: #333333; font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: 13px; line-height: 22px;"> 滑动</span></li>
<li>fade： 淡入淡出</li>
<li>cube： 3D立方体</li>
<li>coverflow： 3D封面流</li>
<li>flip： 3D翻转</li>
</ol>
</td>
</tr>
<tr>
<td>fade</td>
<td>object = <br />
<pre class="crayon-plain-tag">{
    crossFade: false
}</pre>
</td>
<td>淡入淡出特效的参数</td>
</tr>
<tr>
<td>cube</td>
<td>object = <br />
<pre class="crayon-plain-tag">{
  slideShadows: true,
  shadow: true,
  shadowOffset: 20,
  shadowScale: 0.94
}</pre>
</td>
<td>
<p>3D立方体特效的参数，禁用阴影可以获得更好的性能</p>
</td>
</tr>
<tr>
<td>coverflow</td>
<td>object = <br />
<pre class="crayon-plain-tag">{
  rotate: 50,
  stretch: 0,
  depth: 100,
  modifier: 1,
  slideShadows : true
}</pre>
</td>
<td>3D封面流特效的参数，禁用阴影可以获得更好的性能</td>
</tr>
<tr>
<td>flip</td>
<td>object = <br />
<pre class="crayon-plain-tag">{
  slideShadows : true
  limitRotation: true
}</pre>
</td>
<td>3D翻转特效的参数，</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>视差效果</em></td>
</tr>
<tr>
<td>parallax</td>
<td>boolean = false</td>
<td>如果启用，则可以在幻灯元素内使用parallaxed元素</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>幻灯片网格</em></td>
</tr>
<tr>
<td>spaceBetween</td>
<td>number = 0</td>
<td>两张幻灯片之间的举例</td>
</tr>
<tr>
<td>slidesPerView</td>
<td>number = 1<br />string = 'auto'</td>
<td>每个视图包含的幻灯片数量，即同时能够显示的幻灯片数量<br />设置为auto，并且loop: true，则同时需要设置loopedSlides参数<br />设置为auto，则与多行模式（slidesPerColumn 大于1）不兼容</td>
</tr>
<tr>
<td>slidesPerColumn</td>
<td>number = 1</td>
<td>每一列显示幻灯片的数量，多行布局</td>
</tr>
<tr>
<td>slidesPerColumnFill</td>
<td>string = 'column'</td>
<td>可选值column/row，指定幻灯片如何填充行</td>
</tr>
<tr>
<td>slidesPerGroup</td>
<td>number = 1</td>
<td>指定几张幻灯片分为一组</td>
</tr>
<tr>
<td>centeredSlides</td>
<td>boolean = false</td>
<td>如果设置为true，则当前幻灯片被居中显示</td>
</tr>
<tr>
<td>slidesOffsetBefore</td>
<td>number = 0</td>
<td>在容器的开始处（所有幻灯片之前）添加额外的偏移量</td>
</tr>
<tr>
<td>slidesOffsetAfter</td>
<td>number = 0</td>
<td>在容器的结束处（所有幻灯片之后）添加额外的偏移量</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>鼠标指针</em></td>
</tr>
<tr>
<td>grabCursor</td>
<td>boolean = false</td>
<td>悬停在幻灯片上时，显示手状鼠标指针</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>触碰</em></td>
</tr>
<tr>
<td>touchEventsTarget</td>
<td>string = 'container'</td>
<td>在哪个元素上（Target）监听touch事件，可以是：
<ol>
<li><span style="color: #333333; font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: 13px; line-height: 22px;">container：在.swiper-container元素上</span></li>
<li>wrapper：在.swiper-wrapper元素上</li>
</ol>
</td>
</tr>
<tr>
<td>touchAngle</td>
<td>number = 45</td>
<td>可以触发移动操作的滑动操作的角度限制</td>
</tr>
<tr>
<td>simulateTouch</td>
<td>boolean = true</td>
<td>设置为true，则支持鼠标拖拽模拟触碰滑动操作</td>
</tr>
<tr>
<td>shortSwipes</td>
<td>boolean = true</td>
<td>是否启用短滑</td>
</tr>
<tr>
<td>longSwipes</td>
<td>boolean = true</td>
<td>是否启用长滑</td>
</tr>
<tr>
<td>followFinger</td>
<td>boolean = true</td>
<td>手指滑动时，幻灯片是否也跟着移动</td>
</tr>
<tr>
<td>onlyExternal</td>
<td>boolean = false</td>
<td>如果设置为true，则只能通过API调用来切换幻灯片</td>
</tr>
<tr>
<td>threshold</td>
<td>number = 0</td>
<td>触发幻灯片移动的最小滑动距离，单位px</td>
</tr>
<tr>
<td>touchMoveStopPropagation</td>
<td>boolean = true</td>
<td>是否禁止touchmove事件的传播（propagation）</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>点击</em></td>
</tr>
<tr>
<td>preventClicks</td>
<td>boolean = true</td>
<td>在滑动时，避免在链接上意外触发click事件</td>
</tr>
<tr>
<td>preventClicksPropagation</td>
<td>boolean = true</td>
<td>在滑动时，禁止点击事件的传播</td>
</tr>
<tr>
<td>slideToClickedSlide</td>
<td>boolean = false</td>
<td>如果设置为true，在某种幻灯片上点击，则切换至该幻灯片</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>滑动手势</em></td>
</tr>
<tr>
<td>allowSwipeToPrev</td>
<td>boolean = true</td>
<td>是否允许向前（左/上）滑动</td>
</tr>
<tr>
<td>allowSwipeToNext</td>
<td>boolean = true</td>
<td>是否允许向后（右/下）滑动</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>分页</em></td>
</tr>
<tr>
<td>pagination</td>
<td>string / HTMLElement</td>
<td>分页内容的容器元素或者其CSS选择器</td>
</tr>
<tr>
<td>paginationType</td>
<td>string = 'bullets'</td>
<td>
<p>分页显示方式：</p>
<ol>
<li>bullets 一排小圆点</li>
<li>fraction 类似于1/10这样的数字</li>
<li>progress 进度条</li>
<li>custom 自定义</li>
</ol>
</td>
</tr>
<tr>
<td>paginationHide</td>
<td>boolean = true</td>
<td>是否允许通过点击分页容器，来切换分页的可见性</td>
</tr>
<tr>
<td>paginationClickable</td>
<td>boolean = false</td>
<td>是否允许通过点击小圆点来切换到相应幻灯片，仅用于bullet</td>
</tr>
<tr>
<td>paginationElement</td>
<td>string = 'span'</td>
<td>单个小圆点在什么HTML标记内显示，仅用于bullet</td>
</tr>
<tr>
<td>paginationBulletRender</td>
<td>function(index, className)</td>
<td>bullet类型的分页的渲染函数，示例：<br />
<pre class="crayon-plain-tag">function( index, className ) {
    return '&lt;span class="' + className + '"&gt;' + 
               ( index + 1 ) + 
           '&lt;/span&gt;';
}</pre>
</td>
</tr>
<tr>
<td>paginationFractionRender</td>
<td>function(swiper, currentClassName, totalClassName)</td>
<td>fraction类型的分页的渲染函数，示例：<br />
<pre class="crayon-plain-tag">function ( swiper, currentClassName, totalClassName ) {
    return 
    '&lt;span class="' + currentClassName + '"&gt;&lt;/span&gt;' + 
    ' of ' + '&lt;span class="' + totalClassName + '"&gt;&lt;/span&gt;';
};</pre>
</td>
</tr>
<tr>
<td>paginationProgressRender</td>
<td>function(swiper, progressbarClass)</td>
<td>progress类型的分页的渲染函数，示例：<br />
<pre class="crayon-plain-tag">function (swiper, progressbarClass) {
    return '&lt;span class="' + progressbarClass + '"&gt;&lt;/span&gt;';
}</pre>
</td>
</tr>
<tr>
<td>paginationCustomRender</td>
<td>function (swiper, current, total)</td>
<td>custom类型的分页的渲染函数，示例：<br />
<pre class="crayon-plain-tag">function (swiper, current, total) {
    return current + ' of ' + total;
}</pre>
</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>滚动条</em></td>
</tr>
<tr>
<td>scrollbar</td>
<td>string / HTMLElement</td>
<td>滚动条的容器元素</td>
</tr>
<tr>
<td>scrollbarHide</td>
<td>boolean = true</td>
<td>在结束用户交互后，是否自动隐藏滚动条</td>
</tr>
<tr>
<td>scrollbarDraggable</td>
<td>boolean = false</td>
<td>是否允许拖拽滚动条</td>
</tr>
<tr>
<td>scrollbarSnapOnRelease</td>
<td>boolean = false</td>
<td>是否滚动条后，是否立即确定幻灯片位置</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>键盘/滚轮</em></td>
</tr>
<tr>
<td>keyboardControl</td>
<td>boolean = false</td>
<td>是否启用键盘导航，如果设置为true，那么可以用上下左右方向键进行幻灯片切换</td>
</tr>
<tr>
<td>mousewheelControl</td>
<td>boolean = false</td>
<td>是否启用滚轮导航</td>
</tr>
<tr>
<td>mousewheelReleaseOnEdges</td>
<td>boolean = false</td>
<td>如果设置为true，到达第一/最后一张幻灯片后，继续滚动滚轮时，允许页面滚动</td>
</tr>
<tr>
<td>mousewheelInvert</td>
<td>boolean = false</td>
<td>是否翻转鼠标滚轮方向</td>
</tr>
<tr>
<td>mousewheelSensitivity</td>
<td>number = 1</td>
<td>滚轮滚动速度的倍率</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>哈希导航</em></td>
</tr>
<tr>
<td>hashnav</td>
<td>boolean = false</td>
<td>设置为true，则允许对幻灯片组件进行哈希导航</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>图片</em></td>
</tr>
<tr>
<td>preloadImages</td>
<td>boolean = true</td>
<td>如果启用，那么组件会强制预加载所有图片</td>
</tr>
<tr>
<td>updateOnImagesReady</td>
<td>boolean = true</td>
<td>如果设置为true，那么所有图片预加载完毕后，组件会重新初始化，需要preloadImages:true</td>
</tr>
<tr>
<td>lazyLoading</td>
<td>boolean = false</td>
<td>设置为true则启用延迟加载，需要preloadImages:false</td>
</tr>
<tr>
<td>lazyLoadingInPrevNext</td>
<td>boolean = false</td>
<td rowspan="2">设置相邻图片的延迟加载规则<br />第二个参数指定多远的相邻图片会被预加载，不得小于slidesPerView</td>
</tr>
<tr>
<td>lazyLoadingInPrevNextAmount</td>
<td>number = 1</td>
</tr>
<tr>
<td>lazyLoadingOnTransitionStart</td>
<td>boolean = false</td>
<td>默认的，组件会在切换动画结束后开始延迟的加载，设置为true，则动画开始时就加载</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>循环模式</em></td>
</tr>
<tr>
<td>loop</td>
<td>boolean = false</td>
<td>设置为true，则循环播放幻灯片</td>
</tr>
<tr>
<td>loopAdditionalSlides</td>
<td>number = 0</td>
<td>启用循环模式后，多少幻灯片被克隆显示</td>
</tr>
<tr>
<td>loopedSlides</td>
<td>number = null</td>
<td>在循环模式中，如果同时指定了slidesPerView:'auto'，则需要设置该参数</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>回调函数</em></td>
</tr>
<tr>
<td>runCallbacksOnInit</td>
<td>boolean = true</td>
<td>是否在幻灯片组件初始化时执行onTransitionStart/End、onSlideChangeStart/End回调</td>
</tr>
<tr>
<td>onInit</td>
<td rowspan="9">
<p>function(swiper)</p>
<p>其中：</p>
<ol>
<li>swiper 幻灯片组件对象</li>
</ol>
</td>
<td>组件初始化后立即执行</td>
</tr>
<tr>
<td>onSlideChangeStart</td>
<td>切换到其它幻灯片的动画开始时执行</td>
</tr>
<tr>
<td>onSlideChangeEnd</td>
<td>切换到其它幻灯片的动画结束后执行</td>
</tr>
<tr>
<td>onSlideNextStart</td>
<td rowspan="2">与前面类似，但是仅适用于向前切换时</td>
</tr>
<tr>
<td>onSlideNextEnd</td>
</tr>
<tr>
<td>onSlidePrevStart</td>
<td rowspan="2">与前面类似，但是仅适用于向后切换时</td>
</tr>
<tr>
<td>onSlidePrevEnd</td>
</tr>
<tr>
<td>onTransitionStart</td>
<td>在转换开始时执行</td>
</tr>
<tr>
<td>onTransitionEnd</td>
<td>在转换结束后执行</td>
</tr>
<tr>
<td>onTouchStart</td>
<td rowspan="8">function(swiper, event)</td>
<td>用户触碰幻灯片时触发</td>
</tr>
<tr>
<td>onTouchMove</td>
<td>用户触碰幻灯片，并滑动手指时触发</td>
</tr>
<tr>
<td>onTouchMoveOpposite</td>
<td>用户触碰幻灯片，并以对立方向滑动手指时触发<br />所谓对立方向是指，如果组件direction是水平的，而手指滑动方向是垂直的</td>
</tr>
<tr>
<td>onSliderMove</td>
<td>用户触碰幻灯片，并滑动手指导致幻灯片移动时触发</td>
</tr>
<tr>
<td>onTouchEnd</td>
<td>用户放开手指后触发</td>
</tr>
<tr>
<td>onClick</td>
<td>用户点击幻灯片后触发，要求手指触碰屏幕至少300ms</td>
</tr>
<tr>
<td>onTap</td>
<td>用户轻点幻灯片后触发</td>
</tr>
<tr>
<td>onDoubleTap</td>
<td>用户连续两次轻点幻灯片后触发</td>
</tr>
<tr>
<td>onImagesReady</td>
<td>function(swiper)</td>
<td>所有图片都加载完毕后触发</td>
</tr>
<tr>
<td>onProgress</td>
<td>function(swiper, progress)</td>
<td>幻灯片显示进度变化时触发，progress是0-1之间的数字</td>
</tr>
<tr>
<td>onReachBeginning</td>
<td>function(swiper)</td>
<td>幻灯片切换到起始位置（initial position）时触发</td>
</tr>
<tr>
<td>onReachEnd</td>
<td>function(swiper)</td>
<td>幻灯片切换到起最后一张时触发</td>
</tr>
<tr>
<td>onDestroy</td>
<td>function(swiper)</td>
<td>组件被销毁时触发</td>
</tr>
<tr>
<td>onAutoplay</td>
<td>function(swiper)</td>
<td>类似于onSlideChangeStart，但是由自动播放触发</td>
</tr>
<tr>
<td>onAutoplayStart</td>
<td>function(swiper)</td>
<td>自动播放开始时触发</td>
</tr>
<tr>
<td>onAutoplayStop</td>
<td>function(swiper)</td>
<td>自动部分停止后触发</td>
</tr>
<tr>
<td>onLazyImageLoad</td>
<td>function (swiper, slide, image)</td>
<td>开始延迟加载图片时触发</td>
</tr>
<tr>
<td>onLazyImageReady</td>
<td>function (swiper, slide, image)</td>
<td>延迟加载的图片加载完毕后触发</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>CSS名字空间 </em></td>
</tr>
<tr>
<td>slideClass</td>
<td>string = 'swiper-slide'</td>
<td>幻灯片的样式类名</td>
</tr>
<tr>
<td>slideActiveClass</td>
<td>string = 'swiper-slide-active'</td>
<td>当前幻灯片的样式类名</td>
</tr>
<tr>
<td>slideVisibleClass</td>
<td>string = 'swiper-slide-visible'</td>
<td>可见的幻灯片的样式类名</td>
</tr>
<tr>
<td>slideDuplicateClass</td>
<td>string = 'swiper-slide-duplicate'</td>
<td>复制的幻灯片的样式类名</td>
</tr>
<tr>
<td>slideNextClass</td>
<td>string = 'swiper-slide-next'</td>
<td>下一个幻灯片的样式类名</td>
</tr>
<tr>
<td>slidePrevClass</td>
<td>string = 'swiper-slide-prev'</td>
<td>上一个幻灯片的样式类名</td>
</tr>
<tr>
<td>wrapperClass</td>
<td>string = 'swiper-wrapper'</td>
<td>组件包装元素的样式类名</td>
</tr>
<tr>
<td>bulletClass</td>
<td>string = 'swiper-pagination-bullet'</td>
<td>bullet分页小圆点的类名</td>
</tr>
<tr>
<td>bulletActiveClass</td>
<td>string = 'swiper-pagination-bullet-active'</td>
<td>bullet分页当前小圆点的类名</td>
</tr>
<tr>
<td>paginationHiddenClass</td>
<td>string = 'swiper-pagination-hidden'</td>
<td>隐藏分页区的类名</td>
</tr>
<tr>
<td>paginationCurrentClass</td>
<td>string = 'swiper-pagination-current'</td>
<td>当前分页数值的类名</td>
</tr>
<tr>
<td>paginationTotalClass</td>
<td>string = 'swiper-pagination-total'</td>
<td>总计分页数值的类名</td>
</tr>
<tr>
<td>paginationProgressbarClass</td>
<td>string = 'swiper-pagination-progressbar'</td>
<td>分页进度条的类名</td>
</tr>
<tr>
<td>buttonDisabledClass</td>
<td>string = 'swiper-button-disabled'</td>
<td>禁用按钮的类名</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">幻灯片组件的属性和方法</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 45%; text-align: center;">属性/方法</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>array params</td>
<td>组件初始化参数对象</td>
</tr>
<tr>
<td>array container</td>
<td>Dom7/jQuery元素集，container[0]为组件的容器元素</td>
</tr>
<tr>
<td>array wrapper</td>
<td>Dom7/jQuery元素集，wrapper[0]为组件的Wrapper元素</td>
</tr>
<tr>
<td>array slides</td>
<td>Dom7/jQuery元素集，所有幻灯片元素的集合</td>
</tr>
<tr>
<td>array bullets</td>
<td>Dom7/jQuery元素集，所有小圆点的集合</td>
</tr>
<tr>
<td>number width</td>
<td>容器的宽度</td>
</tr>
<tr>
<td>number height</td>
<td>容器的高度</td>
</tr>
<tr>
<td>translate</td>
<td>wrapper的当前translate值</td>
</tr>
<tr>
<td>number progress</td>
<td>当前播放进度，0-1之间</td>
</tr>
<tr>
<td>number activeIndex</td>
<td>当前活动幻灯片的索引</td>
</tr>
<tr>
<td>number previousIndex</td>
<td>前一个幻灯片的索引</td>
</tr>
<tr>
<td>boolean isBeginning</td>
<td>如果当前是第一张幻灯片，则为true</td>
</tr>
<tr>
<td>boolean  isEnd</td>
<td>如果当前是最后一张幻灯片，则为true</td>
</tr>
<tr>
<td>boolean autoplaying</td>
<td>如果当前处于自动播放模式，则为true</td>
</tr>
<tr>
<td>boolean animatingru</td>
<td>如果当前正在执行转换动画，则为true</td>
</tr>
<tr>
<td>object touches</td>
<td>包含触碰事件若干参数：startX、startY、currentX、currentY、diff</td>
</tr>
<tr>
<td>clickedIndex</td>
<td>最后一次被点击的幻灯片的索引</td>
</tr>
<tr>
<td>clickedSlide</td>
<td>最后一次被点击的幻灯片</td>
</tr>
<tr>
<td>slideNext(runCallbacks, speed)</td>
<td rowspan="3">
<p>执行转换动画，并切换到下一张、上一张、指定的幻灯片</p>
<ol>
<li>runCallbacks 是否执行回调</li>
<li>speed 转换动画执行时间，ms</li>
</ol>
</td>
</tr>
<tr>
<td>slidePrev(runCallbacks, speed)</td>
</tr>
<tr>
<td>slideTo(index, speed, runCallbacks)</td>
</tr>
<tr>
<td>update(updateTranslate)</td>
<td>
<p>更新布局和渲染，实际上是调用以下函数：</p>
<ol>
<li>updateContainerSize() 重新计算容器尺寸</li>
<li>updateSlidesSize() 重新计算幻灯片数量及其偏移量</li>
<li>updateProgress() 重新计算播放进度</li>
<li>updatePagination() 重新计算分页布局并重渲染小圆点</li>
<li>updateClasses() 更新幻灯片和小圆点的active/prev/next样式类</li>
</ol>
<p>在手工添加/删除幻灯片后、隐藏/显示组件后、对组件进行DOM操作后应该调用此方法</p>
</td>
</tr>
<tr>
<td>onResize()</td>
<td>改变浏览器大小后该方法被调用</td>
</tr>
<tr>
<td>detachEvents()</td>
<td>移除所有事件监听器</td>
</tr>
<tr>
<td>attachEvents()</td>
<td>重新添加所有监听器</td>
</tr>
<tr>
<td>startAutoplay()</td>
<td>开始自动播放</td>
</tr>
<tr>
<td>stopAutoplay()</td>
<td>停止自动播放</td>
</tr>
<tr>
<td>destroy(deleteInstance, cleanupStyles)</td>
<td>销毁组件实例，移除所有监听器<br />deleteInstance  是否删除组件实例，默认true<br />cleanupStyles  是否清除自定义样式，默认false</td>
</tr>
<tr>
<td>appendSlide(slides)</td>
<td rowspan="2">附加新的幻灯片到组件的尾部、头部。示例：<br />
<pre class="crayon-plain-tag">swiper.appendSlide('&lt;div class="swiper-slide"&gt;Slide 10"&lt;/div&gt;')
swiper.appendSlide([
   '&lt;div class="swiper-slide"&gt;Slide 10"&lt;/div&gt;', 
   '&lt;div class="swiper-slide"&gt;Slide 11"&lt;/div&gt;'
]);</pre>
</td>
</tr>
<tr>
<td>prependSlide(slides)</td>
</tr>
<tr>
<td>removeSlide(slideIndex)</td>
<td>移除指定索引对应的幻灯片</td>
</tr>
<tr>
<td>removeAllSlides()</td>
<td>移除全部幻灯片</td>
</tr>
<tr>
<td>setWrapperTranslate(translate)</td>
<td>为Wrapper元素设置CSS转换的translate值</td>
</tr>
<tr>
<td>getWrapperTranslate()</td>
<td>获得Wrapper元素的CSS转换的translate值</td>
</tr>
<tr>
<td>on(callback, handler)</td>
<td>添加回调函数/事件监听器。示例：<br />
<pre class="crayon-plain-tag">swiper.on('slideChangeStart', function () {
    console.log('slide change start');
});</pre>
</td>
</tr>
<tr>
<td>once(callback, handler)</td>
<td>添加一次性回调函数/事件监听器</td>
</tr>
<tr>
<td>off(callback)</td>
<td>移除指定回调/事件的全部处理函数</td>
</tr>
<tr>
<td>lockSwipeToNext()</td>
<td>禁止向前切换幻灯片</td>
</tr>
<tr>
<td>unlockSwipeToNext()</td>
<td>禁止向后切换幻灯片</td>
</tr>
<tr>
<td>lockSwipes()</td>
<td>禁止幻灯片切换</td>
</tr>
<tr>
<td>unlockSwipes()</td>
<td>允许幻灯片切换</td>
</tr>
<tr>
<td>disableMousewheelControl()</td>
<td>禁止滚轮控制</td>
</tr>
<tr>
<td>enableMousewheelControl()</td>
<td>启用滚轮控制</td>
</tr>
<tr>
<td>disableKeyboardControl()</td>
<td>禁止键盘控制</td>
</tr>
<tr>
<td>enableKeyboardControl()</td>
<td>启用键盘控制</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">照片浏览器（Photo Browser）</span></div>
<p>该组件模拟iOS系统照片浏览器的行为，用于展示图片/照片，支持<span style="background-color: #c0c0c0;">缩放和平移</span>操作。 </p>
<div class="blog_h2"><span class="graybg">创建照片浏览器实例</span></div>
<p> 仅支持通过JavaScript API创建：</p>
<pre class="crayon-plain-tag">/**
 * @param parameters 照片浏览器初始化参数
 * @reutrn 已初始化的照片浏览器实例
 */
app.photoBrowser( parameters );</pre>
<div class="blog_h2"><span class="graybg">照片浏览器初始化参数</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">参数</td>
<td style="width: 30%; text-align: center;">类型和默认值</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>photos</td>
<td>array = []</td>
<td>一个数组，元素可以是：
<ol>
<li><span style="color: #333333; font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: small;"><span style="line-height: 22px;">URL字符串</span></span></li>
<li>对象，具有url（或者html）、caption属性</li>
</ol>
</td>
</tr>
<tr>
<td>initialSlide</td>
<td>number = 0</td>
<td>以幻灯片显示照片（photo slide）时，第一张显示的照片的索引</td>
</tr>
<tr>
<td>spaceBetween</td>
<td>number = 20</td>
<td>幻灯片的间距</td>
</tr>
<tr>
<td>speed</td>
<td>number = 300</td>
<td>幻灯片切换动画的时间，ms</td>
</tr>
<tr>
<td>zoom</td>
<td>boolean = true</td>
<td>是否支持Zoom/Pan操作</td>
</tr>
<tr>
<td>maxZoom</td>
<td>number = 3</td>
<td>最大缩放倍数</td>
</tr>
<tr>
<td>minZoom</td>
<td>number = 1</td>
<td>最小缩放倍数</td>
</tr>
<tr>
<td>exposition</td>
<td>boolean = true</td>
<td>是否在点击照片浏览器后，启用展览模式（exposition mode）</td>
</tr>
<tr>
<td>expositionHideCaptions</td>
<td>boolean = false</td>
<td>在展览模式时是否隐藏标题</td>
</tr>
<tr>
<td>swipeToClose</td>
<td>boolean = true</td>
<td>如果启用，上下滑动手指可以关闭照片浏览器</td>
</tr>
<tr>
<td>view</td>
<td>View</td>
<td>如果使用page类型的照片浏览器，该参数指定页面所在的已初始化的视图</td>
</tr>
<tr>
<td>type</td>
<td>string = 'standalone'</td>
<td>
<p>定义照片浏览器的打开方式：</p>
<ol>
<li>standalone 打开为overlay，以特定的转换效果</li>
<li>popup 打开为popup</li>
<li>page 插入新页面到视图，并加载此页面，在其中打开浏览器</li>
</ol>
</td>
</tr>
<tr>
<td>loop</td>
<td>boolean = false</td>
<td>是否循环播放幻灯片</td>
</tr>
<tr>
<td>theme</td>
<td>string = 'light'</td>
<td>照片浏览器主题，可以设置为dark，深色</td>
</tr>
<tr>
<td>captionsTheme</td>
<td>string</td>
<td>标题的主题，可以设置为dark或light，默认和theme一致</td>
</tr>
<tr>
<td>navbar</td>
<td>boolean = true</td>
<td>是否显示照片浏览器的导航栏</td>
</tr>
<tr>
<td>toolbar</td>
<td>boolean = true</td>
<td>是否显示照片浏览器的工具栏</td>
</tr>
<tr>
<td>backLinkText</td>
<td>string = 'Close'</td>
<td>返回链接的文字</td>
</tr>
<tr>
<td>ofText</td>
<td>string = 'of'</td>
<td>可以设置of的替换文本</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>延迟加载</em></td>
</tr>
<tr>
<td>lazyLoading</td>
<td>boolean = false</td>
<td>是否启用照片延迟加载</td>
</tr>
<tr>
<td>lazyLoadingInPrevNext</td>
<td>boolean = false</td>
<td>设置为true，则启用临近（左/右）照片的延迟加载</td>
</tr>
<tr>
<td>lazyLoadingOnTransitionStart</td>
<td>boolean = false</td>
<td>是否在切换动画开始时即加载照片</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>模板</em></td>
</tr>
<tr>
<td>template</td>
<td>string</td>
<td>照片浏览器的Template7模板</td>
</tr>
<tr>
<td>navbarTemplate</td>
<td>string</td>
<td>导航栏的Template7模板</td>
</tr>
<tr>
<td>toolbarTemplate</td>
<td>string</td>
<td>工具栏的Template7模板</td>
</tr>
<tr>
<td>photoTemplate</td>
<td>string</td>
<td>单个照片元素的Template7模板</td>
</tr>
<tr>
<td>photoLazyTemplate</td>
<td>string</td>
<td>单个延迟加载照片的Template7模板</td>
</tr>
<tr>
<td>objectTemplate</td>
<td>string</td>
<td>单个object元素的Template7模板</td>
</tr>
<tr>
<td>captionTemplate</td>
<td>string</td>
<td>单个标题元素的Template7模板</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>回调</em></td>
</tr>
<tr>
<td>onOpen</td>
<td>function(photobrowser)</td>
<td>照片浏览器打开时执行</td>
</tr>
<tr>
<td>onClose</td>
<td>function(photobrowser)</td>
<td>照片浏览器关闭时执行</td>
</tr>
<tr>
<td>onSwipeToClose</td>
<td>function(photobrowser)</td>
<td>用户通过滑动手势关闭照片浏览器时执行</td>
</tr>
<tr>
<td>onSlideChangeStart</td>
<td>function(swiper)</td>
<td rowspan="2">在动画开始前/结束后执行</td>
</tr>
<tr>
<td>onSlideChangeEnd</td>
<td>function(swiper)</td>
</tr>
<tr>
<td>onTransitionStart</td>
<td>function(swiper)</td>
<td rowspan="2">在转换开始前/结束后执行</td>
</tr>
<tr>
<td>onTransitionEnd</td>
<td>function(swiper)</td>
</tr>
<tr>
<td>onClick</td>
<td>function(swiper, event)</td>
<td>点击/触碰照片超过300ms后触发</td>
</tr>
<tr>
<td>onTap</td>
<td>function(swiper, event)</td>
<td>轻触照片后触发</td>
</tr>
<tr>
<td>onDoubleTap</td>
<td>function(swiper, event)</td>
<td>连续两次轻触照片后触发</td>
</tr>
<tr>
<td>onLazyImageLoad</td>
<td>function(swiper, slide, image)</td>
<td>开始照片延迟加载前触发</td>
</tr>
<tr>
<td>onLazyImageReady</td>
<td>function(swiper, slide, image)</td>
<td>照片延迟加载完毕后触发</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">照片浏览器属性和方法 </span></div>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 30%; text-align: center;">属性/方法</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>swiper</td>
<td>包含已经初始化的幻灯片组件的（Swiper）实例</td>
</tr>
<tr>
<td>container</td>
<td>容器元素的Dom7元素</td>
</tr>
<tr>
<td>exposed</td>
<td>是否处于展览模式</td>
</tr>
<tr>
<td>activeSlideIndex</td>
<td>当前照片索引</td>
</tr>
<tr>
<td>params</td>
<td>所有初始化参数</td>
</tr>
<tr>
<td>open(index)</td>
<td>打开照片浏览器并切换到指定索引的照片，如果不指定index，则显示上一次打开的照片</td>
</tr>
<tr>
<td>close()</td>
<td>关闭照片浏览器</td>
</tr>
<tr>
<td>toggleZoom()</td>
<td>启用/禁用当前照片的缩放</td>
</tr>
<tr>
<td>toggleExposition()</td>
<td>启用/禁用展览模式</td>
</tr>
<tr>
<td>enableExposition()</td>
<td>启用展览模式</td>
</tr>
<tr>
<td>disableExposition()</td>
<td>禁用展览模式</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg" style="font-weight: bold; color: #007755;">照片浏览器模板</span></div>
<p>照片浏览器组件允许通过模板定制其UI</p>
<div class="blog_h3"><span class="graybg">主模板（template）</span></div>
<p>其它模板都会嵌入到该模板中。</p>
<pre class="crayon-plain-tag">&lt;div class="photo-browser photo-browser-{{theme}}"&gt;
    &lt;div class="view navbar-fixed toolbar-fixed"&gt;
        {{#unless material}}{{#if navbar}}
        navbarTemplate
        {{/if}}{{/unless}}
        &lt;div class="page no-toolbar {{#unless navbar}}no-navbar{{/unless}} toolbar-fixed navbar-fixed" data-page="photo-browser-slides"&gt;
            {{#if material}}{{#if navbar}}
            navbarTemplate
            {{/if}}{{/if}}
            {{#if toolbar}}
            toolbarTemplate
            {{/if}}
            &lt;div class="photo-browser-captions photo-browser-captions-{{js "this.captionsTheme || this.theme"}}"&gt;
                {{#each photos}}{{#if caption}}
                captionTemplate
                {{/if}}{{/each}}
            &lt;/div&gt;
            &lt;div class="photo-browser-swiper-container swiper-container"&gt;
                &lt;div class="photo-browser-swiper-wrapper swiper-wrapper"&gt;
                    {{#each photos}}
                    {{#js_compare "this.html || ((typeof this === \'string\' || this instanceof String) &amp;&amp; (this.indexOf(\'&lt;\') &gt;= 0 || this.indexOf(\'&gt;\') &gt;= 0))"}}
                        objectTemplate
                    {{else}}
                        {{#if @root.lazyLoading}}
                        photoLazyTemplate
                        {{else}}
                        photoTemplate
                        {{/if}}
                    {{/js_compare}}
                    {{/each}}
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;</pre>
<div class="blog_h3"><span class="graybg">导航栏模板（navbarTemplate）</span></div>
<pre class="crayon-plain-tag">&lt;div class="navbar"&gt;
    &lt;div class="navbar-inner"&gt;
        &lt;div class="left sliding"&gt;
            &lt;!-- photo-browser-close-link 点击后会关闭照片浏览器，并且解除事件监听器 --&gt;
            &lt;a href="#" class="link close-popup photo-browser-close-link {{#unless backLinkText}}icon-only{{/unless}} {{js "this.type === \'page\' ? \'back\' : \'\'"}}"&gt;
                &lt;i class="icon icon-back {{iconsColorClass}}"&gt;&lt;/i&gt;
                {{#if backLinkText}}&lt;span&gt;{{backLinkText}}&lt;/span&gt;{{/if}}
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="center sliding"&gt;
            &lt;!-- photo-browser-current 当前照片的索引 --&gt;
            &lt;span class="photo-browser-current"&gt;&lt;/span&gt; 
            &lt;span class="photo-browser-of"&gt;{{ofText}}&lt;/span&gt;   
            &lt;!-- photo-browser-current 照片总数 --&gt;
            &lt;span class="photo-browser-total"&gt;&lt;/span&gt;
        &lt;/div&gt;
        &lt;div class="right"&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;</pre>
<div class="blog_h3"><span class="graybg">工具栏模板（toolbarTemplate）</span></div>
<pre class="crayon-plain-tag">&lt;div class="toolbar tabbar"&gt;
    &lt;div class="toolbar-inner"&gt;
        &lt;!-- 切换到前一张照片的按钮 --&gt;
        &lt;a href="#" class="link photo-browser-prev"&gt;
            &lt;i class="icon icon-prev {{iconsColorClass}}"&gt;&lt;/i&gt;
        &lt;/a&gt;
        &lt;!-- 切换到后一张照片的按钮 --&gt;
        &lt;a href="#" class="link photo-browser-next"&gt;
            &lt;i class="icon icon-next {{iconsColorClass}}"&gt;&lt;/i&gt;
        &lt;/a&gt;
    &lt;/div&gt;
&lt;/div&gt;</pre>
<div class="blog_h3"><span class="graybg">照片模板（photoTemplate）</span></div>
<pre class="crayon-plain-tag">&lt;div class="photo-browser-slide swiper-slide"&gt;
    &lt;span class="photo-browser-zoom-container"&gt;
        &lt;img src="{{js "this.url || this"}}"&gt;
    &lt;/span&gt;
&lt;/div&gt;</pre>
<div class="blog_h3"><span class="graybg">延迟加载照片模板（photoLazyTemplate） </span></div>
<pre class="crayon-plain-tag">&lt;div class="photo-browser-slide photo-browser-slide-lazy swiper-slide"&gt;
    &lt;div class="preloader {{@root.preloaderColorClass}}"&gt;{{#if @root.material}}{{@root.materialPreloaderSvg}}{{/if}}&lt;/div&gt;
    &lt;span class="photo-browser-zoom-container"&gt;
        &lt;img data-src="{{js " this.url || this"}}" class="swiper-lazy"&gt;
    &lt;/span&gt;
&lt;/div&gt;</pre>
<div class="blog_h3"><span class="graybg">对象元素模板（objectTemplate）</span></div>
<pre class="crayon-plain-tag">&lt;div class="photo-browser-slide photo-browser-object-slide swiper-slide"&gt;{{js "this.html || this"}}&lt;/div&gt;</pre>
<div class="blog_h3"><span class="graybg">标题模板（captionTemplate） </span></div>
<pre class="crayon-plain-tag">&lt;div class="photo-browser-caption" data-caption-index="{{@index}}"&gt;
    {{caption}}
&lt;/div&gt;</pre>
<div class="blog_h1"><span class="graybg">自动完成（Autocomplete）</span></div>
<p> F7提供了一个移动设备友好、为触碰操作优化的自动完成组件。</p>
<div class="blog_h2"><span class="graybg">创建自动完成组件实例</span></div>
<p>只能通过JavaScript API创建：</p>
<pre class="crayon-plain-tag">/**
 * @param parameters 初始化参数
 * @return 已初始化的自动完成组件的实例
 */
app.autocomplete(parameters);</pre>
<div class="blog_h2"><span class="graybg">自动完成组件的初始化参数</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">参数</td>
<td style="width: 25%; text-align: center;">类型和默认值</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>openIn</td>
<td>string = 'page'</td>
<td>
<p>组件如何打开：</p>
<ol>
<li>page 页面</li>
<li>popup 弹窗</li>
<li>dropdown 下拉列表</li>
</ol>
</td>
</tr>
<tr>
<td>source</td>
<td>function<br /> (autocomplete, query, render)</td>
<td>
<p>该函数执行搜索：</p>
<ol>
<li>autocomplete 组件实例</li>
<li>query 查询文本</li>
<li>render 渲染回调，必须传递匹配条目的数组给此回调</li>
</ol>
<p>示例：</p>
<pre class="crayon-plain-tag">var allItems = [];
function source( autocomplete, query, render ) {
    var results = [];
    if ( query.length === 0 ) {
        // 无查询条件
        render( results );
        return;
    }
    //根据查询条件过滤数据
    for ( var i = 0; i &lt; fruits.length; i++ ) {
        if ( allItems.indexOf( query ) &gt;= 0 ) {
            results.push( fruits[i] );
        }
    }
    //调用渲染函数
    render( results );
}</pre>
</td>
</tr>
<tr>
<td>limit</td>
<td>number</td>
<td>自动完成显示条目的最大数量</td>
</tr>
<tr>
<td>preloader</td>
<td>boolean = false</td>
<td>设置为true，则在自动完成的布局中添加预加载指示器（Preloader）</td>
</tr>
<tr>
<td>preloaderColor</td>
<td>string</td>
<td>预加载指示器的颜色，可以是white/black</td>
</tr>
<tr>
<td>valueProperty</td>
<td>string = 'id'</td>
<td>匹配条目的什么属性作为值（Value）使用</td>
</tr>
<tr>
<td>textProperty</td>
<td>string = 'text'</td>
<td>匹配条目的什么属性作为显示值（Display value）使用</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>专用于Standalone模式（以page或popup打开组件）的参数</em></td>
</tr>
<tr>
<td>opener</td>
<td>string/HTMLElement</td>
<td>链接的HTMLElement或CSS选择器，点击此链接后打开自动完成页面或者Popup</td>
</tr>
<tr>
<td>popupCloseText</td>
<td>string = 'Close'</td>
<td>Popup关闭文本</td>
</tr>
<tr>
<td>backText</td>
<td>string = 'Back'</td>
<td>页面的后退文本</td>
</tr>
<tr>
<td>pageTitle</td>
<td>string</td>
<td>页面的标题</td>
</tr>
<tr>
<td>searchbarPlaceholderText</td>
<td>string = 'Search...'</td>
<td>搜索栏占位符文本</td>
</tr>
<tr>
<td>searchbarCancelText</td>
<td>string = 'Cancel'</td>
<td>搜索栏取消按钮文本</td>
</tr>
<tr>
<td>notFoundText</td>
<td>string = 'Nothing found'</td>
<td>无匹配条目条目时显示的文本</td>
</tr>
<tr>
<td>multiple</td>
<td>boolean = false</td>
<td>是否允许选择多个条目</td>
</tr>
<tr>
<td>backOnSelect</td>
<td>boolean = false</td>
<td>选中一个条目后立即关闭自动完成组件，仅用于单选模式</td>
</tr>
<tr>
<td>navbarTheme</td>
<td>string</td>
<td>导航栏主题颜色</td>
</tr>
<tr>
<td>formTheme</td>
<td>string</td>
<td>表单主题颜色</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>专用于下拉菜单模式的参数</em></td>
</tr>
<tr>
<td>input</td>
<td>string/HTMLElement</td>
<td>与此自动完成组件相关的文本输入框</td>
</tr>
<tr>
<td>dropdownPlaceholderText</td>
<td>string</td>
<td>下拉列表占位符文本</td>
</tr>
<tr>
<td>updateInputValueOnSelect</td>
<td>boolean = true</td>
<td>选中一个条目后，自动更新文本输入框</td>
</tr>
<tr>
<td>expandInput</td>
<td>boolean = false</td>
<td>如果设置为true，则列表视图中的item-input元素的宽度扩展为屏幕宽度</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>回调</em></td>
</tr>
<tr>
<td>onChange</td>
<td>function (autocomplete, value)</td>
<td>自动完成值改变后，执行此回调：
<ol>
<li><span style="color: #333333; font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: small;"><span style="line-height: 22px;">autocomplete 组件实例</span></span></li>
<li>value 选中的条目</li>
</ol>
</td>
</tr>
<tr>
<td>onOpen</td>
<td>function (autocomplete)</td>
<td>自动完成组件打开时执行此回调</td>
</tr>
<tr>
<td>onClose</td>
<td>function (autocomplete)</td>
<td>自动完成组件关闭时执行此回调</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>模板</em></td>
</tr>
<tr>
<td>navbarTemplate</td>
<td>string</td>
<td>Standalone模式导航栏模板</td>
</tr>
<tr>
<td>itemTemplate</td>
<td>string</td>
<td>Standalone模式表单条目（radio/checkbox）模板</td>
</tr>
<tr>
<td>dropdownTemplate</td>
<td>string</td>
<td>下拉列表模板</td>
</tr>
<tr>
<td>dropdownItemTemplate</td>
<td>string</td>
<td>下拉列表条目的模板</td>
</tr>
<tr>
<td>dropdownPlaceholderTemplate</td>
<td>string</td>
<td>下拉列表占位符的模板</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">自动完成组件的方法和属性</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">方法/属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>params</td>
<td>组件的初始化参数</td>
</tr>
<tr>
<td>value </td>
<td>选中条目的数组 </td>
</tr>
<tr>
<td>opened</td>
<td>组件是否处于打开状态 </td>
</tr>
<tr>
<td>dropdown</td>
<td>下拉列表的Dom7实例 </td>
</tr>
<tr>
<td>popup</td>
<td>Popup（如果打开）的Dom7实例 </td>
</tr>
<tr>
<td>open() </td>
<td>打开组件 </td>
</tr>
<tr>
<td>close()</td>
<td>关闭组件 </td>
</tr>
<tr>
<td>showPreloader()</td>
<td>显示预加载提示符 </td>
</tr>
<tr>
<td>hidePreloader()</td>
<td>隐藏预加载提示符 </td>
</tr>
<tr>
<td>destroy()</td>
<td>销毁组件，解除所有事件监听器</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg"><span style="font-weight: bold; color: #007755;">自动完成组件的默认模板</span> </span></div>
<p>本节列出F7为自动完成组件提供的缺省模板，定制这些模板可以改变组件的外观。
<div class="blog_h3"><span class="graybg">navbarTemplate</span></div>
<p>导航栏模板：该模板用于Standalone模式。</p>
<pre class="crayon-plain-tag">&lt;div class="navbar {{#if navbarTheme}}theme-{{navbarTheme}}{{/if}}"&gt;&lt;!-- 设置主题 --&gt;
    &lt;div class="navbar-inner"&gt;
        &lt;div class="left sliding"&gt;&lt;!-- 左侧，依据iOS/Material设置不同的按钮 --&gt;
            {{#if material}}
            &lt;a href="#" class="link {{#if inPopup}}close-popup{{else}}back{{/if}} icon-only"&gt;
              &lt;i class="icon icon-back"&gt;&lt;/i&gt;
            &lt;/a&gt;
            {{else}}
            &lt;a href="#" class="link {{#if inPopup}}close-popup{{else}}back{{/if}}"&gt;
                &lt;i class="icon icon-back"&gt;&lt;/i&gt;
                {{#if inPopup}}
                &lt;span&gt;{{popupCloseText}}&lt;/span&gt;
                {{else}}
                &lt;span&gt;{{backText}}&lt;/span&gt;
                {{/if}}
            &lt;/a&gt;
            {{/if}}
        &lt;/div&gt;
        &lt;div class="center sliding"&gt;{{pageTitle}}&lt;/div&gt;&lt;!-- 中间，显示页面标题 --&gt;
        {{#if preloader}}
        &lt;div class="right"&gt;&lt;!-- 右侧，显示预加载提示符 --&gt;
            &lt;div class="autocomplete-preloader preloader {{#if preloaderColor}}preloader-{{preloaderColor}}{{/if}}"&gt;&lt;/div&gt;
        &lt;/div&gt;
        {{/if}}
    &lt;/div&gt;
&lt;/div&gt;</pre>
<div class="blog_h3"><span class="graybg">itemTemplate</span></div>
<p>列表条目模板：每个备选条目（列表视图的元素）使用的模板。</p>
<pre class="crayon-plain-tag">&lt;li&gt;
    &lt;label class="label-{{inputType}} item-content"&gt;
        &lt;!-- 单/复选框，隐藏 --&gt;
        &lt;input type="{{inputType}}" name="{{inputName}}" value="{{value}}" {{#if selected}}checked{{/if}}&gt;
        &lt;!-- 图标和文本的布局，依主题而不同 --&gt;
        {{#if material}}
            &lt;div class="item-media"&gt;
                &lt;i class="icon icon-form-{{inputType}}"&gt;&lt;/i&gt;
            &lt;/div&gt;
            &lt;div class="item-inner"&gt;
                &lt;div class="item-title"&gt;{{text}}&lt;/div&gt;
            &lt;/div&gt;
        {{else}}
            {{#if checkbox}}
            &lt;div class="item-media"&gt;
                &lt;i class="icon icon-form-checkbox"&gt;&lt;/i&gt;
            &lt;/div&gt;
            {{/if}}
            &lt;div class="item-inner"&gt;
                &lt;div class="item-title"&gt;{{text}}&lt;/div&gt;
            &lt;/div&gt;
        {{/if}}
    &lt;/label&gt;
&lt;/li&gt;</pre>
<div class="blog_h3"><span class="graybg">dropdownTemplate</span></div>
<p>下拉菜单外框使用的模板。</p>
<pre class="crayon-plain-tag">&lt;div class="autocomplete-dropdown"&gt;
    &lt;div class="autocomplete-dropdown-inner"&gt;
        &lt;!-- 列表视图放在这里 --&gt;
        &lt;div class="list-block"&gt;
            &lt;ul&gt;&lt;/ul&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;!-- 预加载指示器 --&gt;
    {{#if preloader}}
    &lt;div class="autocomplete-preloader preloader {{#if preloaderColor}}preloader-{{preloaderColor}}{{/if}}"&gt;
      {{#if material}}{{materialPreloaderHtml}}{{/if}}
    &lt;/div&gt;
    {{/if}}
&lt;/div&gt;</pre>
<div class="blog_h3"><span class="graybg">dropdownItemTemplate</span></div>
<p>下拉菜单模式下，每个备选条目使用的模板。</p>
<pre class="crayon-plain-tag">&lt;li&gt;
    &lt;label class="{{#unless placeholder}}label-radio{{/unless}} item-content" data-value="{{value}}"&gt;
        &lt;div class="item-inner"&gt;
            &lt;div class="item-title"&gt;{{text}}&lt;/div&gt;
        &lt;/div&gt;
    &lt;/label&gt;
&lt;/li&gt;</pre>
<div class="blog_h3"><span class="graybg">dropdownPlaceholderTemplate</span></div>
<p>下拉菜单模式下，占位符文本使用的模板。</p>
<pre class="crayon-plain-tag">&lt;li class="autocomplete-dropdown-placeholder"&gt;
    &lt;div class="item-content"&gt;
        &lt;div class="item-inner"&gt;
            &lt;div class="item-title"&gt;{{text}}&lt;/div&gt;
        &lt;/div&gt;
    &lt;/label&gt;
&lt;/li&gt;</pre>
<div class="blog_h2"><span class="graybg">应用举例</span></div>
<div class="blog_h3"><span class="graybg">下拉菜单模式+Ajax</span></div>
<pre class="crayon-plain-tag">&lt;div class="list-block"&gt;
    &lt;ul&gt;
        &lt;li class="item-content"&gt;
            &lt;div class="item-title label"&gt;显示语言&lt;/div&gt;
            &lt;div class="item-input"&gt;
                &lt;input type="text" placeholder="Language" id="autocomplete-dropdown-ajax"&gt;
            &lt;/div&gt;
        &lt;/li&gt;
    &lt;/ul&gt;
&lt;/div&gt;
&lt;script type="text/javascript"&gt;
var autocompleteDropdownAjax = app.autocomplete( {
    input : '#autocomplete-dropdown-ajax',
    openIn : 'dropdown',
    preloader : true, // 异步获取数据，一般都显示预加载指示器
    valueProperty : 'id', // 对象的值属性
    textProperty : 'name', // 对象的显示值属性
    limit : 20, // 最多显示20条数据于下拉列表
    dropdownPlaceholderText : '请输入语言名称',
    expandInput : true, // 扩展输入框为100%宽度，Label隐藏 
    source : function( autocomplete, query, render ) {
        var results = [];
        if ( query.length === 0 ) {
            render( results );
            return;
        }
        // 加载数据前，显示指示器
        autocomplete.showPreloader();
        // 执行异步请求
        $$.ajax( {
            url : 'autocomplete-languages.json',
            method : 'GET',
            dataType : 'json',
            data : {
                query : query
            },
            //获取服务器数据后的回调 
            success : function( data ) {
                for ( var i = 0; i &lt; data.length; i++ ) {
                    if ( data[i].name.toLowerCase().indexOf( query.toLowerCase() ) &gt;= 0 ) results.push( data[i] );
                }
                // 隐藏指示器
                autocomplete.hidePreloader();
                // 异步的执行渲染函数 
                render( results );
            }
        } );
    }
} );
&lt;/script&gt;</pre>
<div class="blog_h1"><span class="graybg">选取器（Picker）</span></div>
<p>Picker提供了一些基础功能，利用这些功能，你可以很方便的定制UI风格与iOS类似的选取器。</p>
<p>Picker可以用作内联组件（ inline component），也可以用作Overlay。在平板电脑上，Overlay的选取器自动被转换为<span style="background-color: #c0c0c0;">Popover</span>。</p>
<div class="blog_h2"><span class="graybg">创建和访问选取器实例 </span></div>
<p>只能通过JavaScript API创建：</p>
<pre class="crayon-plain-tag">/**
 * @param parameters 选取器初始化参数
 * @return 已实例化的选取器实例
 */
app.picker(parameters);</pre>
<div class="blog_h3"><span class="graybg">获得内联选取器实例</span></div>
<p>对于通过内联（inline）方式创建的选取器，可以通过其HTML容器的属性获得选取器实例：</p>
<pre class="crayon-plain-tag">var picker = $$('.picker-inline')[0].f7Picker;</pre>
<div class="blog_h2"><span class="graybg">选取器（列）初始化参数</span></div>
<div class="blog_h3"><span class="graybg">选取器初始化参数</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">参数</td>
<td style="text-align: center;">类型和默认值</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>container</td>
<td>HTMLElement/string</td>
<td>仅用于内联（inline）选取器，存放生成的选取器对象的容器的HTMLElement或者CSS选择器</td>
</tr>
<tr>
<td>input</td>
<td>HTMLElement/string</td>
<td>与此选取器相关的input元素的HTMLElement或者CSS选择器</td>
</tr>
<tr>
<td>scrollToInput</td>
<td>boolean = true</td>
<td>当打开选取器时，滚动视口（Viewport，即page-content）到input元素</td>
</tr>
<tr>
<td>inputReadOnly</td>
<td>boolean = true</td>
<td>如果设置为true，则input元素被设置readonly属性</td>
</tr>
<tr>
<td>convertToPopover</td>
<td>boolean = true</td>
<td>在大屏幕上（Pad）把模态选取器转换为Popover</td>
</tr>
<tr>
<td>onlyOnPopover</td>
<td>boolean = false</td>
<td>如果设置为true，则选取器总是在Popover中打开</td>
</tr>
<tr>
<td>cssClass</td>
<td>string</td>
<td>在模态选取器上设置的额外CSS类</td>
</tr>
<tr>
<td>closeByOutsideClick</td>
<td>boolean = true</td>
<td>是否在点击外部区域后，自动关闭选取器</td>
</tr>
<tr>
<td>toolbar</td>
<td>boolean = true</td>
<td>启用模态选取器工具栏</td>
</tr>
<tr>
<td>toolbarCloseText</td>
<td>string = 'Done'</td>
<td>选取器工具栏上关闭按钮的文本</td>
</tr>
<tr>
<td>toolbarTemplate</td>
<td>string = <br />
<pre class="crayon-plain-tag">&lt;div class="toolbar"&gt;
  &lt;div class="toolbar-inner"&gt;
    &lt;div class="left"&gt;&lt;/div&gt;
    &lt;div class="right"&gt;
      &lt;a href="#" class="link close-picker"&gt;
        {{closeText}}
      &lt;/a&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;</pre>
</td>
<td>工具栏的Template7模板</td>
</tr>
<tr>
<td>rotateEffect</td>
<td>boolean = false</td>
<td>是否启用3D滚动效果</td>
</tr>
<tr>
<td>momentumRatio</td>
<td>boolean = false</td>
<td>对选取器进行触碰并滑动操作后，更大的值是否产生更多的momentum</td>
</tr>
<tr>
<td>updateValuesOnMomentum</td>
<td>boolean = false</td>
<td>在momentum期间是否更新选取器和input的值</td>
</tr>
<tr>
<td>updateValuesOnTouchmove</td>
<td>boolean = true</td>
<td>在触碰并滑动时，是否更新选取器和input的值</td>
</tr>
<tr>
<td>value</td>
<td>array</td>
<td>初始值的数组，每个元素对应了选取器单个列的值</td>
</tr>
<tr>
<td>formatValue</td>
<td>function (p, values, displayValues)</td>
<td>用于格式化input的值的函数，values/displayValues为两个数组，其索引为idx的元素，分别为当前选取器值第idx列的值/显示值</td>
</tr>
<tr>
<td>cols</td>
<td>array</td>
<td>选取器的数据定义，为一个数组，其每个元素定义了选取器的单个列，元素格式为PO，符合“列参数”规定</td>
</tr>
<tr>
<td>onChange</td>
<td>function (p, values, displayValues)</td>
<td>当选取器的值改变时该函数被调用。values/displayValues为两个数组，其索引为idx的元素，分别为当前选取器值第idx列的值/显示值</td>
</tr>
<tr>
<td>onOpen</td>
<td>function (p)</td>
<td>选取器打开时，执行该回调</td>
</tr>
<tr>
<td>onClose</td>
<td>function (p)</td>
<td>选取器关闭时，执行该回调</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">选取器列初始化参数</span></div>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 20%; text-align: center;">参数</td>
<td style="text-align: center;">类型和默认值</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>values</td>
<td>array</td>
<td>该列的值数组</td>
</tr>
<tr>
<td>displayValues</td>
<td>array</td>
<td>该列的显示值数组</td>
</tr>
<tr>
<td>cssClass</td>
<td>string</td>
<td>在列HTML容器上设置的额外CSS样式类</td>
</tr>
<tr>
<td>textAlign</td>
<td>string</td>
<td>列文本的对齐方式，可以是left/center/right</td>
</tr>
<tr>
<td>width</td>
<td>number</td>
<td>列宽度，单位px</td>
</tr>
<tr>
<td>divider</td>
<td>boolean = false</td>
<td>作为分隔字符的列，不需要values/displays，例如用来隔开时间的冒号</td>
</tr>
<tr>
<td>content</td>
<td>string</td>
<td>分隔字符（串）的内容</td>
</tr>
<tr>
<td>onChange</td>
<td>function (p, value, displayValue)</td>
<td>列的值发生变化时调用此回调</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">选取器（列）属性和方法 </span></div>
<div class="blog_h3"><span class="graybg">选取器属性和方法</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 35%; text-align: center;">属性/方法</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>params</td>
<td>初始化参数</td>
</tr>
<tr>
<td>value</td>
<td>数组，选取器当前每列的值</td>
</tr>
<tr>
<td>displayValue</td>
<td>数组，选取器当前每列的显示值</td>
</tr>
<tr>
<td>opened</td>
<td>此选取器当前是否处于打开状态</td>
</tr>
<tr>
<td>inline</td>
<td>此选取器是否为内联的</td>
</tr>
<tr>
<td>cols</td>
<td>列对象的数组，每个元素都有自己的属性和方法（参见下表）。下面示例如何获得第一个列对象：<br />
<pre class="crayon-plain-tag">// 获取第一个列对象
var col = picker.cols[0];</pre>
</td>
</tr>
<tr>
<td>container</td>
<td>选取器的HML容器的Dom7对象</td>
</tr>
<tr>
<td>setValue(values, duration)</td>
<td>
<p>设置选取器的值：</p>
<ol>
<li>values：数组，对应每个列的值</li>
<li>duration：动画持续时间</li>
</ol>
</td>
</tr>
<tr>
<td>open()</td>
<td>打开选取器</td>
</tr>
<tr>
<td>close()</td>
<td>关闭选取器</td>
</tr>
<tr>
<td>destroy()</td>
<td>销毁选取器实例，移除所有监听器</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">选取器列属性和方法 </span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 35%; text-align: center;">属性/方法</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>container</td>
<td>列HTML元素的Dom7对象</td>
</tr>
<tr>
<td>items</td>
<td>列条目HTML元素的Dom7对象</td>
</tr>
<tr>
<td>value</td>
<td>列的当前值</td>
</tr>
<tr>
<td>displayValue</td>
<td>列的当前显示值</td>
</tr>
<tr>
<td>activeIndex</td>
<td>列当前值在值列表中的索引</td>
</tr>
<tr>
<td>setValue(value, duration)</td>
<td>设置此列的当前值</td>
</tr>
<tr>
<td>replaceValues(values, displayValues)</td>
<td>替换此列的值/显示值列表</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">应用举例</span></div>
<div class="blog_h3"><span class="graybg">双列联动选取器 </span></div>
<p>所谓联动，是指第一列的值改变后，第二列的值列表自动被替换： </p>
<pre class="crayon-plain-tag">&lt;div class="content-block-title"&gt;联动示例&lt;/div&gt;
&lt;div class="list-block"&gt;
    &lt;ul&gt;
        &lt;li&gt;
            &lt;div class="item-content"&gt;
                &lt;div class="item-inner"&gt;
                    &lt;div class="item-input"&gt;
                        &lt;input type="text" placeholder="你的车型" readonly id="picker-dependent"&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/li&gt;
    &lt;/ul&gt;
&lt;/div&gt;
&lt;script type="text/javascript"&gt;
    var carVendors = {
        '日产' : [ '本田', '雷克萨斯', '，马自达', '尼桑', '丰田' ],
        '德产' : [ '奥迪', '宝马', '梅赛德斯', '沃尔沃' ],
        '美产' : [ '林肯', '富特', '别克', '凯迪拉克' ]
    };
    var pickerDependent = app.picker( {
        input : '#picker-dependent', // 关联的输入元素
        rotateEffect : true, // 3D转轮效果
        formatValue : function( picker, values ) {
            return values[1]; // 第二列的值，即汽车品牌为输出到input的值
        },
        cols : [ {
            // 第一列，汽车按生产国分类
            textAlign : 'left',
            values : [ '日产', '德产', '美产' ],
            // 在第一列值改变时，自动设置第二列的值列表，这是联动的关键
            onChange : function( picker, country ) {
                if ( picker.cols[1].replaceValues ) {
                    // 替换值列表
                    picker.cols[1].replaceValues( carVendors[country] );
                }
            }
        }, {
            values : carVendors['日产'],
            width : 160,
        }, ]
    } );
&lt;/script&gt;</pre>
<div class="blog_h3"><span class="graybg">时间选取器</span></div>
<p>这是一个多列、内联的选取器实例：</p>
<pre class="crayon-plain-tag">&lt;div class="content-block-title"&gt;内联选取器：日期-时间&lt;/div&gt;
&lt;div class="content-block"&gt;
 &lt;div style="padding:0; margin-right:-15px; width:auto" class="content-block-inner"&gt;
   &lt;div style="margin:0" class="list-block"&gt;
     &lt;ul style="border-top:none"&gt;
       &lt;li&gt;
         &lt;div class="item-content"&gt;
           &lt;div class="item-inner"&gt;
             &lt;div class="item-input"&gt;
               &lt;input type="text" placeholder="选取日期/时间" readonly id="picker-date"&gt;
             &lt;/div&gt;
           &lt;/div&gt;
         &lt;/div&gt;
       &lt;/li&gt;
     &lt;/ul&gt;
   &lt;/div&gt;
   &lt;div id="picker-date-container"&gt;&lt;/div&gt;
 &lt;/div&gt;
&lt;/div&gt;
&lt;script type="text/javascript"&gt;
    var today = new Date();
    var pickerInline = app.picker( {
        input : '#picker-date', //管理的input元素
        container : '#picker-date-container', //内联选取器的容器元素
        toolbar : false,
        rotateEffect : true,
        //默认值设置为今天
        value : [ today.getMonth(), today.getDate(), today.getFullYear(), today.getHours(), ( today.getMinutes() &lt; 10 ? '0' + today.getMinutes() : today.getMinutes() ) ],
        onChange : function( picker, values, displayValues ) {
            //第二列，日期，需要依据月份的不同设置值列表
            var daysInMonth = new Date( picker.value[2], picker.value[0] * 1 + 1, 0 ).getDate();
            if ( values[1] &gt; daysInMonth ) {
                picker.cols[1].setValue( daysInMonth );
            }
        },
        //格式化
        formatValue : function( p, values, displayValues ) {
            return displayValues[0] + ' ' + values[1] + ', ' + values[2] + ' ' + values[3] + ':' + values[4];
        },
    
        cols : [
        // 月份
        {
            values : ( '0 1 2 3 4 5 6 7 8 9 10 11' ).split( ' ' ),
            displayValues : ( 'January February March April May June July August September October November December' ).split( ' ' ),
            textAlign : 'left'
        },
        // 日期
        { values : [ 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31 ] },
        // 年度
        {
            values : ( function() {
                var arr = [];
                for ( var i = 1950; i &lt;= 2030; i++ ) { arr.push( i ); }
                return arr;
            } )(),
        },
        // 日期 时间的分隔符列，空格
        { divider : true,  content : '  ' },
        // 小时
        {
            values : ( function() {
                var arr = [];
                for ( var i = 0; i &lt;= 23; i++ ) { arr.push( i ); }
                return arr;
            } )(),
        },
        // 小时:分钟的分隔符列，冒号
        { divider : true, content : ':' },
        // 分钟
        {
            values : ( function() {
                var arr = [];
                for ( var i = 0; i &lt;= 59; i++ ) { arr.push( i &lt; 10 ? '0' + i : i ); }
                return arr;
            } )(),
        } ]
    } );
&lt;/script&gt;</pre>
<div class="blog_h1"><span class="graybg">日历（日期选取器）</span></div>
<p>F7提供的日历（Calendar）组件为触碰操作优化，可以方便的处理日期字段。与一般的选取类似，日历支持内联、overlay展示方式。</p>
<div class="blog_h2"><span class="graybg">创建和访问日历实例</span></div>
<p>只能通过JavaScript API创建日历实例：</p>
<pre class="crayon-plain-tag">/**
 * @param parameters 日历初始化参数
 * @return 已初始化的日历对象实例
 */
app.calendar(parameters);

// 访问内联日历组件的实例
var calendar = $$('.calenadr-inline')[0].f7Calendar;</pre>
<div class="blog_h2"><span class="graybg">日历初始化参数</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 22%; text-align: center;">参数</td>
<td style="text-align: center;">类型和默认值</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>container</td>
<td>HTMLElement/string</td>
<td rowspan="11">同普通选取器组件</td>
</tr>
<tr>
<td>input</td>
<td>HTMLElement/string</td>
</tr>
<tr>
<td>scrollToInput</td>
<td>boolean = true</td>
</tr>
<tr>
<td>inputReadOnly</td>
<td>boolean = true</td>
</tr>
<tr>
<td>convertToPopover</td>
<td>boolean = true</td>
</tr>
<tr>
<td>onlyOnPopover</td>
<td>boolean = false</td>
</tr>
<tr>
<td>cssClass</td>
<td>string</td>
</tr>
<tr>
<td>closeByOutsideClick</td>
<td>boolean = true</td>
</tr>
<tr>
<td>toolbar</td>
<td>boolean = true</td>
</tr>
<tr>
<td>toolbarCloseText</td>
<td>string = 'Done'</td>
</tr>
<tr>
<td>toolbarTemplate</td>
<td>string = <br />
<pre class="crayon-plain-tag">&lt;div class="toolbar"&gt;
  &lt;div class="toolbar-inner"&gt;
    {{monthPicker}}
    {{yearPicker}}
  &lt;/div&gt;
&lt;/div&gt;</pre>
</td>
</tr>
<tr>
<td>value</td>
<td>array</td>
<td>初始被选中的日期的数组，每个元素为选中的日期（Date）对象</td>
</tr>
<tr>
<td>disabled</td>
<td rowspan="2">DateRange，用于表示日期的范围，可以用数组、函数、对象三种形式表示：<br />
<pre class="crayon-plain-tag">//数组方式，指定起止日期
var range = [ 
    new Date(2015, 10, 10), 
    new Date(2015, 10, 11)
];

//函数方式，对任意日期返回true/false
//true则包含在Range内
function range( date ) {
    if ( date.getFullYear() === 2015 
         &amp;&amp; date.getMonth() === 10 ) {
        return true;
    }
    else {
        return false;
    }
}

//对象方式
range = {
    from: new Date(2015, 9, 1),
    to: new Date(2015, 11, 31)
}</pre>
</td>
<td>额外的被禁用的日期范围</td>
</tr>
<tr>
<td>events</td>
<td>具有事件的日期范围，这些日期数字的下面具有额外的小圆点标记</td>
</tr>
<tr>
<td>rangesClasses</td>
<td>array</td>
<td>用于为日期范围添加额外的样式。为一数组，每个元素均为：<br />
<pre class="crayon-plain-tag">{cssClass:string, range: DateRange}

//举例：
var calendar = app.calendar( {
    rangesClasses : [ {
        cssClass : 'day-holiday',
        range : {
            from : new Date( 2016, 0, 1 ),
            to : new Date( 2016, 0, 10 )
        }
    } ]
} );</pre>
</td>
</tr>
<tr>
<td>formatValue</td>
<td>function (p, values)</td>
<td>格式化日期，输出到input元素。函数的返回值必须是一个string，values为Date对象的数组</td>
</tr>
<tr>
<td>monthNames</td>
<td>array = <br />['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August' , 'September' , 'October', 'November', 'December']</td>
<td>所有月份的名称</td>
</tr>
<tr>
<td>monthNamesShort</td>
<td>array = <br />['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']</td>
<td>所有月份的简称</td>
</tr>
<tr>
<td>dayNames</td>
<td>array = <br />['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday']</td>
<td>所有DOW的名称</td>
</tr>
<tr>
<td>dayNamesShort</td>
<td>array = <br />['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']</td>
<td>所有DOW的检查</td>
</tr>
<tr>
<td>updateValuesOnTouchmove</td>
<td>boolean  = true</td>
<td>在触碰被移动后，更新选取器和input的值</td>
</tr>
<tr>
<td>firstDay</td>
<td>number = 1</td>
<td>一周的第一天，默认1即星期一</td>
</tr>
<tr>
<td>weekendDays</td>
<td>array = [0, 6]</td>
<td>周末是那几天，默认周六、日</td>
</tr>
<tr>
<td>dateFormat</td>
<td>string = 'yyyy-mm-dd'</td>
<td>日期格式：
<ol>
<li><span style="color: #333333; font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: small;"><span style="line-height: 22px;">yyyy 四位年份</span></span></li>
<li>yy 二位年份</li>
<li>mm 二位月份</li>
<li>m 月份</li>
<li>MM 完整月份名称</li>
<li>M 月份短名称</li>
<li>dd 二位日期</li>
<li>d 日期</li>
<li>DD 完整DOW名称</li>
<li>D  DOW简称</li>
</ol>
</td>
</tr>
<tr>
<td>multiple</td>
<td>boolean = false</td>
<td>允许选择多个日期值</td>
</tr>
<tr>
<td>rangePicker</td>
<td>boolean = false</td>
<td>允许范围选择，与multiple不兼容</td>
</tr>
<tr>
<td>direction</td>
<td>string = 'horizontal'</td>
<td>月份布局方向，可以是vertical/horizontal</td>
</tr>
<tr>
<td>minDate</td>
<td>Date = null</td>
<td>允许的最小日期</td>
</tr>
<tr>
<td>maxDate</td>
<td>Date = null</td>
<td>允许的最大日期</td>
</tr>
<tr>
<td>touchmove</td>
<td>boolean = true</td>
<td>触碰并滑动时是否切换日历月份</td>
</tr>
<tr>
<td>animate</td>
<td>boolean = true</td>
<td>启用动画效果</td>
</tr>
<tr>
<td>closeOnSelect</td>
<td>boolean = false</td>
<td>选择后是否立即关闭日历</td>
</tr>
<tr>
<td>weekHeader</td>
<td>boolean = true</td>
<td>是否显示DOW头，以DOW简称</td>
</tr>
<tr>
<td>monthPicker</td>
<td>boolean = true</td>
<td>是否在工具栏显示月份选择器</td>
</tr>
<tr>
<td>monthPickerTemplate</td>
<td>string = <br />
<pre class="crayon-plain-tag">&lt;div class="picker-calendar-month-picker"&gt;
    &lt;a href="#" class="link icon-only picker-calendar-prev-month"&gt;
        &lt;i class="icon icon-prev"&gt;&lt;/i&gt;
    &lt;/a&gt;
    &lt;span class="current-month-value"&gt;&lt;/span&gt;
    &lt;a href="#" class="link icon-only picker-calendar-next-month"&gt;
        &lt;i class="icon icon-next"&gt;&lt;/i&gt;
    &lt;/a&gt;
&lt;/div&gt;</pre>
</td>
<td>月份选择器的Template7模板</td>
</tr>
<tr>
<td>yearPicker</td>
<td>boolean = true</td>
<td>是否在工具栏显示年度选择器</td>
</tr>
<tr>
<td>yearPickerTemplate</td>
<td>string = <br />
<pre class="crayon-plain-tag">&lt;div class="picker-calendar-year-picker"&gt;
    &lt;a href="#" class="link icon-only picker-calendar-prev-year"&gt;
        &lt;i class="icon icon-prev"&gt;&lt;/i&gt;
    &lt;/a&gt;
    &lt;span class="current-year-value"&gt;&lt;/span&gt;
    &lt;a href="#" class="link icon-only picker-calendar-next-year"&gt;
        &lt;i class="icon icon-next"&gt;&lt;/i&gt;
    &lt;/a&gt;
&lt;/div&gt;</pre>
</td>
<td>年度选择器的Template7模板</td>
</tr>
<tr>
<td>onChange</td>
<td>function (p, values, displayValues)</td>
<td>选取器的值改变后，执行该回调</td>
</tr>
<tr>
<td>onMonthAdd</td>
<td>function (p, monthContainer)</td>
<td>新的月份对应的HTML加入到DOM后执行</td>
</tr>
<tr>
<td>onDayClick</td>
<td>function (p, dayContainer, year, month, day)</td>
<td>用户点击/选择任意日期后执行</td>
</tr>
<tr>
<td>onMonthYearChangeStart</td>
<td>function (p, year, month)</td>
<td>在动画切换月份开始时执行</td>
</tr>
<tr>
<td>onMonthYearChangeEnd</td>
<td>function (p, year, month)</td>
<td>在动画切换月份结束时执行</td>
</tr>
<tr>
<td>onOpen</td>
<td>function (p)</td>
<td>在打开日历时执行</td>
</tr>
<tr>
<td>onClose</td>
<td>function (p)</td>
<td>在关闭日历时执行</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">日历属性与方法</span></div>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 40%; text-align: center;">属性/方法</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>params</td>
<td>日历的初始化参数</td>
</tr>
<tr>
<td>value</td>
<td>数组，包含当前选中的日期对象</td>
</tr>
<tr>
<td>opened</td>
<td>如果日历当前处于打开状态，则为true</td>
</tr>
<tr>
<td>inline</td>
<td>如果当前日历是内联的，则为true</td>
</tr>
<tr>
<td>container</td>
<td>日历组件的HTML容器，Dom7对象</td>
</tr>
<tr>
<td>setValue(values)</td>
<td>设置新的，被选择的日期，支持入参数组</td>
</tr>
<tr>
<td>nextMonth(duration)</td>
<td>在duration毫秒后，动画至下一月</td>
</tr>
<tr>
<td>prevMonth(duration)</td>
<td>在duration毫秒后，动画至上一月</td>
</tr>
<tr>
<td>nextYear()</td>
<td>动画至下一年</td>
</tr>
<tr>
<td>prevYear()</td>
<td>动画至上一年</td>
</tr>
<tr>
<td>setYearMonth(year, month, duration)</td>
<td>在duration毫秒后，动画至year年month月</td>
</tr>
<tr>
<td>open()</td>
<td>打开日历</td>
</tr>
<tr>
<td>close()</td>
<td>关闭日历</td>
</tr>
<tr>
<td>destroy()</td>
<td>销毁日历实例并移除事件监听器</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">下拉以刷新</span></div>
<p>下拉刷新是很多移动应用使用的组件， 用于触发页面内容更新。</p>
<div class="blog_h2"><span class="graybg">下拉刷新布局</span></div>
<p>可以集成下拉刷新功能到页面中：</p>
<pre class="crayon-plain-tag">&lt;div class="page"&gt;
    &lt;!-- page-content元素需要添加额外的pull-to-refresh-content类 --&gt;
    &lt;!-- data-ptr-distance为触发刷新动作的下拉距离，单位像素，默认44 --&gt;
    &lt;div class="page-content pull-to-refresh-content" data-ptr-distance="55"&gt;
        &lt;!-- 隐藏的下拉刷新层，包含一个预加载提示符和一个箭头图标 --&gt;
        &lt;div class="pull-to-refresh-layer"&gt;
            &lt;div class="preloader"&gt;&lt;/div&gt;
            &lt;div class="pull-to-refresh-arrow"&gt;&lt;/div&gt;
        &lt;/div&gt;
        &lt;!-- 常规页面内容 --&gt;
        &lt;div class="list-block"&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;</pre>
<div class="blog_h2"><span class="graybg">下拉刷新组件的事件序列</span></div>
<ol>
<li>当用户下拉pull-to-refresh-content元素时，该元素获得额外样式类：pull-down</li>
<li>当用户下拉pull-to-refresh-content元素超过data-ptr-distance后，该元素获得额外样式类：pull-up。默认的，箭头图标的方向将反转，提示用户可以释放并刷新</li>
<li>当处于pull-up状态时，用户释放下拉手势，pull-to-refresh-layer元素获得额外样式类：refreshing。默认的箭头图标隐藏，预加载提示符显示</li>
</ol>
<div class="blog_h2"><span class="graybg">下拉刷新相关事件</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">事件</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>⚡pullstart</td>
<td rowspan="5">
<p>这些事件的Target均为<pre class="crayon-plain-tag">&lt;div class="pull-to-refresh-content"&gt;</pre> 元素，触发时机分别为：</p>
<ol>
<li>开始下拉pull-to-refresh-content元素时</li>
<li>在移动pull-to-refresh-content元素的过程中</li>
<li>释放pull-to-refresh-content元素时</li>
<li>组件进入刷新（refreshing）状态时</li>
<li>刷新完毕后，组件返回初始状态时，该事件在<pre class="crayon-plain-tag">pullToRefreshDone()</pre> 被调用后触发</li>
</ol>
</td>
</tr>
<tr>
<td>⚡pullmove</td>
</tr>
<tr>
<td>⚡pullend</td>
</tr>
<tr>
<td>⚡refresh</td>
</tr>
<tr>
<td>⚡refreshdone</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">下拉刷新相关方法</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 35%; text-align: center;">方法</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>app.pullToRefreshDone(ptrContent)</td>
<td>
<p>ptrContent为pull-to-refresh-content元素对应的HTMLElement或者CSS选择器，下同</p>
<p>当刷新动作完成后，调用此方法可以重置组件，用户可以再次下拉刷新</p>
</td>
</tr>
<tr>
<td>app.pullToRefreshTrigger(ptrContent) </td>
<td>手工触发下拉刷新操作</td>
</tr>
<tr>
<td>app.destroyPullToRefresh(ptrContent)</td>
<td>禁用页面上的下拉刷新组件</td>
</tr>
<tr>
<td>app.initPullToRefresh(ptrContent)</td>
<td>启用页面上的下拉刷新组件</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">无限滚动</span></div>
<p> 当页面滚动到接近底部时，利用无限滚动组件可以加载额外的内容或者触发特定的动作。</p>
<div class="blog_h2"><span class="graybg">无限滚动布局</span></div>
<p>可以在任何可滚动容器上添加额外样式类，来启用无限滚动组件。例如：</p>
<pre class="crayon-plain-tag">&lt;div class="page"&gt;
    &lt;!-- 
        infinite-scroll 启用无限滚动功能的样式类
        data-distance 离底部多远时，触发无限滚动事件，单位像素，默认50px
    --&gt;
    &lt;div class="page-content infinite-scroll" data-distance="100"&gt;&lt;/div&gt;
&lt;/div&gt;</pre>
<div class="blog_h3"><span class="graybg">在顶部进行无限滚动</span></div>
<p>默认的，页面滚动到底部时触发无限滚动，如果要在滚动到顶部时触发无限滚动，可以：</p>
<pre class="crayon-plain-tag">&lt;div class="page"&gt;
    &lt;!-- 添加额外的样式类infinite-scroll-top --&gt;
    &lt;div class="page-content infinite-scroll infinite-scroll-top"&gt;&lt;/div&gt;
&lt;/div&gt;</pre>
<div class="blog_h2"><span class="graybg">无限滚动相关方法和事件</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%; text-align: center;">方法/事件</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>⚡infinite</td>
<td>该事件的Target为<pre class="crayon-plain-tag">&lt;div class="page-content infinite-scroll"&gt;</pre> 元素。当页面滚动到里底部还有data-distance时触发</td>
</tr>
<tr>
<td>app.attachInfiniteScroll(container)</td>
<td rowspan="2">添加/移除无限滚动事件监听器到infinite-scroll元素对应的HTMLElement或者CSS选择器</td>
</tr>
<tr>
<td>app.detachInfiniteScroll(container)</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg"> 通知（Notifications）</span></div>
<p>使用通知组件，可以实现类似于iOS消息推送（Push）的效果。</p>
<div class="blog_h2"><span class="graybg">通知的布局</span></div>
<p>通知仅仅是媒体列表视图的特例，通常不需要手工处理其布局（JavaScript API会自动创建）。当你创建了通知组件后，F7会自动添加notifications元素到body中：</p>
<pre class="crayon-plain-tag">&lt;body&gt;
    &lt;!-- 随通知组件自动添加的新元素，为一个媒体列表视图 --&gt;
    &lt;div class="notifications list-block media-list"&gt;
        &lt;ul&gt;
            &lt;!-- 每一行对应一个通知条目 --&gt;
            &lt;li class="notification-item"&gt;
                &lt;div class="item-content"&gt;
                    &lt;div class="item-media"&gt;
                        &lt;!-- 通知媒体内容 --&gt;
                    &lt;/div&gt;
                    &lt;div class="item-inner"&gt;
                        &lt;div class="item-title-row"&gt;
                            &lt;div class="item-title"&gt;
                                &lt;!-- 通知标题 --&gt;
                            &lt;/div&gt;
                            &lt;div class="item-after"&gt;
                                &lt;!-- 通知关闭图标 --&gt;
                                &lt;a href="#" class="close-notification"&gt;&lt;/a&gt;
                            &lt;/div&gt;
                        &lt;/div&gt;
                        &lt;div class="item-subtitle"&gt;
                            &lt;!-- 通知子标题 --&gt;
                        &lt;/div&gt;
                        &lt;div class="item-text"&gt;
                            &lt;!-- 通知消息文本 --&gt;
                        &lt;/div&gt;
                    &lt;/div&gt;
                &lt;/div&gt;
            &lt;/li&gt;

        &lt;/ul&gt;
    &lt;/div&gt;
&lt;/body&gt;</pre>
<div class="blog_h2"><span class="graybg">通知相关方法 </span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 35%; text-align: center;">方法</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>app.addNotification(parameters)</td>
<td>以初始化参数parameters添加/显示一个通知</td>
</tr>
<tr>
<td>app.closeNotification(notificationElement) </td>
<td>关闭一个通知，notificationElement为对应的HTMLElement或者CSS选择器</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">通知初始化参数 </span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 15%; text-align: center;">参数</td>
<td style="width: 15%; text-align: center;">类型和默认值</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>title</td>
<td>string</td>
<td>仅iOS主题。通知的标题，默认值为应用初始化参数notificationTitle</td>
</tr>
<tr>
<td>subtitle</td>
<td>string</td>
<td>仅iOS主题。通知子标题，默认值为应用初始化参数notificationSubtitle</td>
</tr>
<tr>
<td>media</td>
<td>string</td>
<td>仅iOS主题。媒体元素，指定图标/图片的HTML字符串，默认值为应用初始化参数notificationMedia</td>
</tr>
<tr>
<td>hold</td>
<td>number</td>
<td>在多少ms后自动关闭通知，默认值为应用初始化参数notificationHold</td>
</tr>
<tr>
<td>closeIcon</td>
<td>boolean = true</td>
<td>仅iOS主题。是否显示关闭图标</td>
</tr>
<tr>
<td>button</td>
<td>object</td>
<td>
<p>Material通知按钮，支持三个属性：</p>
<ol>
<li>text 按钮文本</li>
<li>color 按钮颜色名称</li>
<li>close 点击后是否关闭通知</li>
</ol>
</td>
</tr>
<tr>
<td>closeOnClick</td>
<td>boolean = false</td>
<td>是否在点击后自动关闭通知</td>
</tr>
<tr>
<td>additionalClass</td>
<td>string</td>
<td>在通知元素上添加的额外样式类，用于外观定制</td>
</tr>
<tr>
<td>custom</td>
<td>string</td>
<td>用于定制通知的HTML布局</td>
</tr>
<tr>
<td>onClick</td>
<td>function</td>
<td>通知被点击后执行的回调</td>
</tr>
<tr>
<td>onClose</td>
<td>function</td>
<td>通知被关闭后执行的回调</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg"> 图片延迟加载（Lazy Load）</span></div>
<p>该组件用于推迟页面中图片的加载，仅当页面滚动到图片所在位置时，才触发加载动作。该功能可以提升页面加载速度、滚动性能，并降低流量消耗。</p>
<p>要延迟加载的图片必须位于可滚动的页面（<pre class="crayon-plain-tag">&lt;div class="page-content"&gt;</pre> ）内。</p>
<div class="blog_h2"><span class="graybg">用法</span></div>
<div class="blog_h3"><span class="graybg">普通图片</span></div>
<pre class="crayon-plain-tag">&lt;div class="page-content"&gt;
    &lt;!-- 使用data-src代替src，并且添加lazy样式类 --&gt;
    &lt;img data-src="path/to/image.jpg" class="lazy"&gt;
&lt;/div&gt;</pre>
<div class="blog_h3"><span class="graybg">背景图片</span></div>
<pre class="crayon-plain-tag">&lt;div class="page-content"&gt;
    &lt;!-- data-background使用指定背景图片 --&gt;
    &lt;div data-background="path/to/image.jpg" class="lazy"&gt;&lt;/div&gt;
&lt;/div&gt;</pre>
<div class="blog_h3"><span class="graybg">淡入效果</span></div>
<p> 当延迟加载的图片成功加载后，可以淡入的显示：</p>
<pre class="crayon-plain-tag">&lt;div class="page-content"&gt;
    &lt;img data-src="path/to/image.jpg" class="lazy lazy-fadein"&gt;
    &lt;div data-background="path/to/image.jpg" class="lazy lazy-fadein"&gt;&lt;/div&gt;
&lt;/div&gt;</pre>
<div class="blog_h2"><span class="graybg">手工触发加载 </span></div>
<p>手工触发lazy事件，即可立即加载目标图片：</p>
<pre class="crayon-plain-tag">$$('img.lazy').trigger('lazy');
$$('div.lazy').trigger('lazy');</pre>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/framework7-study-note-2">Framework7学习笔记（二）：组件</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/framework7-study-note-2/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>基于AngularJS开发Web应用</title>
		<link>https://blog.gmem.cc/webapp-dev-with-angularjs</link>
		<comments>https://blog.gmem.cc/webapp-dev-with-angularjs#comments</comments>
		<pubDate>Thu, 31 Dec 2015 02:21:43 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[HTML]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Mobile]]></category>
		<category><![CDATA[AngularJS]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=10435</guid>
		<description><![CDATA[<p>AngularJS简介 AngularJS是用于创建动态Web应用的框架，它为克服HTML语言的缺点而生。 HTML能够很好的声明静态文档，灵活的进行页面布局。但是，它缺乏声明动态视图的能力。为了解决这一问题，前些年的Web开发，通常采用以下的技术手段： 基于服务器端脚本来生成HTML页面：这种方式的缺点是，需要大量的页面导航，并且增加了服务器CPU的负担。服务器端脚本有两种开发风格： 以脚本语言为中心：使用JSP、PHP、ASP等脚本语言完成开发工作，这很容易导致UI代码和应用逻辑的耦合，难以维护 以模板或标签库中心：使用Velocity、Structs标签库、EL表达式等框架，来生成HTML页面。这种风格可以较好的解耦UI代码和应用逻辑 以JavaScript为中心完成HTML页面绘制：例如ExtJS框架。这种方式可以非常灵活的生成并操控HTML页面，可以通过XHR避免页面导航。其缺点是复杂性高，完全丢弃了HTML语言本身的优势 AngularJS某种程度上结合了这两种方式的优点： 类似于模板或标签库。AngularJS扩展了HTML的语法，采用简洁易读的方式声明页面组件，支持数据绑定和依赖注入。重要的是，所有操作都在浏览器中执行，与服务器无关 类似于JavaScript框架。Angular实现了一个MVC框架，支持通过JavaScript对页面进行操控、与服务器通信（XHR）。可以实现单页面应用。单页面开发的支持对于移动WebApp非常重要，可以避免不必要的流量和性能（电量）损耗 AngularJS适合以CRUD为主要功能的应用，游戏、UI编辑器等需要复杂DOM操控的应用则不太适合，可以使用支持更低级抽象的JavaScript框架（例如jQuery）代替之。 AngularJS的理念 把DOM操控从应用逻辑中解耦是个非常好的主意，可以极大的改善可测试性 可测试性和代码编写速度一样重要 把客户端和服务器端完全解耦，以便并行开发、分别支持两端的重用 让日常任务简单，让复杂任务可能 AngularJS解除以下痛苦 避免注册回调（监听器）：到处出现的回调注册代码，让代码变得杂乱。AngularJS减少了你需要编写的大量样板式代码 避免编程式的操控DOM：AngularJS支持声明式的指出视图如何随着应用状态的变化而变化。避免了编程式的操控DOM 避免手工在UI和模型之间传递数据：AngularJS支持表单验证、数据绑定、自动UI更新 AngularJS重要概念 特殊标记（Markup）：Angular在HTML中引入的新的记号，标记分为： <a class="read-more" href="https://blog.gmem.cc/webapp-dev-with-angularjs">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/webapp-dev-with-angularjs">基于AngularJS开发Web应用</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">AngularJS简介</span></div>
<p>AngularJS是用于<span style="background-color: #c0c0c0;">创建动态Web应用</span>的框架，它为克服HTML语言的缺点而生。</p>
<p>HTML能够很好的声明静态文档，灵活的进行页面布局。但是，它缺乏声明<span style="background-color: #c0c0c0;">动态视图</span>的能力。为了解决这一问题，前些年的Web开发，通常采用以下的技术手段：</p>
<ol>
<li>基于服务器端脚本来生成HTML页面：这种方式的缺点是，需要<span style="background-color: #c0c0c0;">大量的页面导航</span>，并且<span style="background-color: #c0c0c0;">增加了服务器CPU的负担</span>。服务器端脚本有两种开发风格：
<ol>
<li>以脚本语言为中心：使用JSP、PHP、ASP等脚本语言完成开发工作，这很容易导致UI代码和应用逻辑的耦合，难以维护</li>
<li>以模板或标签库中心：使用Velocity、Structs标签库、EL表达式等框架，来生成HTML页面。这种风格可以较好的解耦UI代码和应用逻辑</li>
</ol>
</li>
<li>以JavaScript为中心完成HTML页面绘制：例如ExtJS框架。这种方式可以非常灵活的生成并操控HTML页面，可以通过XHR避免页面导航。其缺点是<span style="background-color: #c0c0c0;">复杂性高</span>，完全<span style="background-color: #c0c0c0;">丢弃了HTML</span>语言本身的优势</li>
</ol>
<p>AngularJS某种程度上结合了这两种方式的优点：</p>
<ol>
<li>类似于模板或标签库。AngularJS扩展了HTML的语法，采用简洁易读的方式声明页面组件，支持<span style="background-color: #c0c0c0;">数据绑定和依赖注入</span>。重要的是，所有操作都在浏览器中执行，与服务器无关</li>
<li>类似于JavaScript框架。Angular实现了一个MVC框架，支持通过JavaScript对页面进行操控、与服务器通信（XHR）。可以实现单页面应用。单页面开发的支持<span style="background-color: #c0c0c0;">对于移动WebApp非常重要</span>，可以避免不必要的流量和性能（电量）损耗</li>
</ol>
<p>AngularJS适合以CRUD为主要功能的应用，游戏、UI编辑器等需要复杂DOM操控的应用则不太适合，可以使用支持更低级抽象的JavaScript框架（例如jQuery）代替之。</p>
<div class="blog_h3"><span class="graybg">AngularJS的理念</span></div>
<ol>
<li>把<span style="background-color: #c0c0c0;">DOM操控从应用逻辑中解耦</span>是个非常好的主意，可以极大的改善可测试性</li>
<li>可测试性和代码编写速度一样重要</li>
<li>把客户端和服务器端完全解耦，以便并行开发、分别支持两端的重用</li>
<li>让日常任务简单，让复杂任务可能</li>
</ol>
<div class="blog_h3"><span class="graybg">AngularJS解除以下痛苦</span></div>
<ol>
<li>避免注册回调（监听器）：到处出现的回调注册代码，让代码变得杂乱。AngularJS减少了你需要编写的大量样板式代码</li>
<li>避免编程式的操控DOM：AngularJS支持<span style="background-color: #c0c0c0;">声明式</span>的指出视图如何随着应用状态的变化而变化。避免了编程式的操控DOM</li>
<li>避免手工在UI和模型之间传递数据：AngularJS支持表单验证、数据绑定、自动UI更新</li>
</ol>
<div class="blog_h3"><span class="graybg">AngularJS重要概念</span></div>
<ol>
<li>特殊标记（Markup）：Angular在HTML中引入的新的记号，标记分为：
<ol>
<li>指令（Directive）：以HTML属性或元素的形式出现。指令是AngularJS访问DOM元素唯一建议方式，遵循这一建议，可以增强可测试性。可以自己实现指令，以扩展Angular的功能</li>
<li>用于数据绑定的双花括号（Double curly braces）：形式为<pre class="crayon-plain-tag">{{ expression | filter }}</pre> ，Angular把括号中的内容解析为文本并显示。expression是JavaScript风格的表达式；filter则负责格式化expression，让其更加可读</li>
</ol>
</li>
<li>指令：用于为DOM添加特殊行为、或者转换DOM子树的Angular标记</li>
<li>模板（Template）：带有特殊标记的HTML文件被称为模板，AngularJS会使用编译器解析、处理此模板，并转换、渲染HTML</li>
<li>数据绑定（Data binding）：在UI和模型之间同步数据，AngularJS支持双向绑定（two-way data binding）：当UI变化后，数据自动同步到对应的模型字段上；当模型字段变化后，UI自动更新</li>
<li>依赖注入（DI）：AngularJS中所有组件，包括指令（directives）、过滤器（filters）、控制器（controllers）、服务（services）通过DI容器（Injector）创建和注入。依赖注入让所有组件能够自动获取其依赖对象的引用 </li>
<li>模块（Modules）：用于管理一组相互协作的组件</li>
<li>控制器（Controller）：在AngularJS中，控制器是一个构造函数，通常会附带一组行为。控制器自动创建一个Scope，并负责初始化之</li>
<li>作用域（Scope）：AngularJS中的Scope是模型的容器，其属性就是模型的属性。Scope还作为某个DOM分支下所有Markup的上下文对象（this）。正如DOM结构一样，Scope也可能形成树状</li>
</ol>
<div class="blog_h3"><span class="graybg">下载AngularJS</span></div>
<p>Angular把核心模块（angular.js）和可选模块（angular-***.js）的代码分离在不同的JavaScript文件中，可以到<a href="https://code.angularjs.org/1.4.8/">官网代码库</a>下载。核心模块和每个可选模块都有对应的压缩版本。</p>
<div class="blog_h1"><span class="graybg">Angular引入的CSS样式类</span></div>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 30%; text-align: center;">CSS样式类</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>ng-scope</td>
<td>当Angular定义新的Scope后，对应HTML元素上被添加该样式类</td>
</tr>
<tr>
<td>ng-isolate-scope</td>
<td>当Angular定义新的隔离Scope后，对应HTML元素上被添加该样式类</td>
</tr>
<tr>
<td>ng-binding</td>
<td>当一个元素通过<pre class="crayon-plain-tag">ng-bind</pre> 、<pre class="crayon-plain-tag">{{}}</pre> 等方式进行了数据绑定后，Angular为该HTML元素添加该样式类</td>
</tr>
<tr>
<td>ng-invalid, ng-valid</td>
<td>当一个表单控件没有通过/通过验证时，分别应用ng-invalid/ng-valid类</td>
</tr>
<tr>
<td>ng-pristine, ng-dirty</td>
<td>当用户和一个表单控件交互过后，应用ng-dirty，否则应用ng-pristine</td>
</tr>
<tr>
<td>ng-touched, ng-untouched</td>
<td>当表单控件获取/失去焦点时，应用对应的样式类</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">Angular内置过滤器</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">过滤器</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>currency</td>
<td>用于格式化为货币格式，可以设置货币符号</td>
</tr>
<tr>
<td>date</td>
<td>用于格式化日期、时间：<br />
<pre class="crayon-plain-tag">{{ today | date:'medium' }} &lt;!-- Aug 09, 2013 12:09:02 PM --&gt;
{{ today | date:'short' }} &lt;!-- 8/9/1312:09PM --&gt;
{{ today | date:'fullDate' }} &lt;!-- Thursday, August 09, 2013 --&gt;
{{ today | date:'longDate' }} &lt;!-- August 09, 2013 --&gt;
{{ today | date:'mediumDate' }}&lt;!-- Aug 09, 2013 --&gt;
{{ today | date:'shortDate' }} &lt;!-- 8/9/13 --&gt;
{{ today | date:'mediumTime' }}&lt;!-- 12:09:02 PM --&gt;
{{ today | date:'shortTime' }} &lt;!-- 12:09 PM --&gt;

四位年份：{{ today | date:'yyyy' }} &lt;!-- 2013 --&gt;
两位年份：{{ today | date:'yy' }} &lt;!-- 13 --&gt;
一位年份：{{ today | date:'y' }} &lt;!-- 2013 --&gt;

英文月份：{{ today | date:'MMMM' }} &lt;!-- August --&gt;
英文月份简写：{{ today | date:'MMM' }} &lt;!-- Aug --&gt;
数字月份：{{ today |date:'MM' }} &lt;!-- 08 --&gt;
一年中的第几个月份：{{ today |date:'M' }} &lt;!-- 8 --&gt;

数字日期：{{ today|date:'dd' }} &lt;!-- 09 --&gt;
一个月中的第几天：{{ today | date:'d' }} &lt;!-- 9 --&gt;
英文星期：{{ today | date:'EEEE' }} &lt;!-- Thursday --&gt;
英文星期简写：{{ today | date:'EEE' }} &lt;!-- Thu --&gt;

24小时制数字小时：{{today|date:'HH'}} &lt;!--00--&gt;
一天中的第几个小时：{{today|date:'H'}} &lt;!--0--&gt;
12小时制数字小时：{{today|date:'hh'}} &lt;!--12--&gt;
上午或下午的第几个小时：{{today|date:'h'}} &lt;!--12--&gt;

数字分钟数：{{ today | date:'mm' }} &lt;!-- 09 --&gt;
一个小时中的第几分钟：{{ today | date:'m' }} &lt;!-- 9 --&gt;

数字秒数：{{ today | date:'ss' }} &lt;!-- 02 --&gt;
一分钟内的第几秒：{{ today | date:'s' }} &lt;!-- 2 --&gt;
毫秒数：{{ today | date:'.sss' }} &lt;!-- .995 --&gt;

上下午标识：{{ today | date:'a' }} &lt;!-- AM --&gt;
四位时区标识：{{ today | date:'Z' }} &lt;!--- 0700 --&gt;</pre>
</td>
</tr>
<tr>
<td>filter</td>
<td>
<p>从给定数组中选择一个子集，并将其生成一个新数组返回。通常用于过滤展示数据，支持三种类型的过滤器参数：</p>
<ol>
<li>字符串：返回所有包含此字符串的元素，在过滤器参数前加<pre class="crayon-plain-tag">!</pre> 则反选。举例：<br />
<pre class="crayon-plain-tag">{{ ['Alex','Meng'] | filter 'e' }}</pre>
</li>
<li>对象：对待过滤元素的属性与此参数的同名属性进行比较，如果属性值是字符串则判断是否包含该字符串。<pre class="crayon-plain-tag">$</pre> 表示比较全部属性。举例：<br />
<pre class="crayon-plain-tag">{{ [{ name : 'Alex'}, { name : 'Meng'} ] |  filter:{name:'e'} }}</pre>
</li>
<li>函数：对每个元素执行此函数，返回true则包含在结果子集</li>
</ol>
</td>
</tr>
<tr>
<td>json</td>
<td>可以把JavaScript对象转换为JSON字符串，有利于调试</td>
</tr>
<tr>
<td>limitTo</td>
<td>对输入数字、字符串进行截断：<br />
<pre class="crayon-plain-tag">&lt;!-- 截取字符串前三个字符 --&gt;
{{ str | limitTo:3 }}
&lt;!-- 截取字符串最后五个字符 --&gt;
{{ str | limitTo:-5 }}</pre>
</td>
</tr>
<tr>
<td>lowercase</td>
<td>转换为小写</td>
</tr>
<tr>
<td>number</td>
<td>格式化数字，如果输入非数字，生成空串：<br />
<pre class="crayon-plain-tag">{{ 123456789 | number }}
&lt;!-- 1,234,567,890 --&gt;
{{ 1.234567 | number:2 }}
&lt;!-- 1.23 --&gt;</pre>
</td>
</tr>
<tr>
<td>orderBy</td>
<td>
<p>对数组进行排序。该过滤器可以接受两个参数，第二个参数设置为true则反转排序：</p>
<p><pre class="crayon-plain-tag">{{[user1,user2] | orderBy:'name':true }}</pre>
</td>
</tr>
<tr>
<td>uppercase</td>
<td>转换为大写</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">Angular内置指令</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">指令</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>ng-disabled</td>
<td rowspan="4">
<p>值为布尔类型的HTML属性，只有当属性不存在时，才能达到布尔值false的效果，也就是说，根据HTML规范，<pre class="crayon-plain-tag">selected="false"</pre> 和<pre class="crayon-plain-tag">selected="true"</pre> 的效果是一样的。</p>
<p>为此，Angular推出这些HTML属性的变体，规避HTML布尔属性反直觉特性，简化模板编写</p>
</td>
</tr>
<tr>
<td>ng-checked</td>
</tr>
<tr>
<td>ng-readonly</td>
</tr>
<tr>
<td>ng-selected</td>
</tr>
<tr>
<td>ng-href</td>
<td>当使用当前作用域中的属性动态创建URL时，使用该指令。当URL中的插值尚未生效时，Angular不会执行点击动作</td>
</tr>
<tr>
<td>ng-src</td>
<td>当此指令中表达式尚未生效时，不显示图像</td>
</tr>
<tr>
<td>ng-app</td>
<td rowspan="2">
<p>这两个指令分别用于声明Angular应用、控制器，它们的共同点是：会创建新的作用域：</p>
<ol>
<li>ng-app：创建根作用域$rootScope</li>
<li>ng-controller：以根作用域，或者另外一个作用域为<span style="background-color: #c0c0c0;">原型</span>，创建子作用域</li>
</ol>
</td>
</tr>
<tr>
<td>ng-controller</td>
</tr>
<tr>
<td>ng-include</td>
<td>
<p>加载、编译并包含外部HTML模板片段到当前应用中。</p>
<p>模板的URL被同源策略限制——只有相同HTTP协议、域名下的URL才允许。同源策略可以通过<a href="/http-faq#cors">CORS</a>突破。</p>
<p>注意，ng-include会自动创建一个新的子作用域，如果要使用既有的作用域，必须指定ng-controller。</p>
<p>示例：</p>
<pre class="crayon-plain-tag">&lt;div ng-include="/partial.html" 
     ng-controller="指定既有控制器的名称" 
     onload="可以在模板加载完毕后执行一个表达式"
&gt;&lt;/div&gt;</pre>
</td>
</tr>
<tr>
<td>ng-switch</td>
<td>
<p>可以根据属性值的不同，来切换渲染视图：
<pre class="crayon-plain-tag">&lt;input type="text" ng-model="person.name" /&gt;
&lt;!-- 依据person.name进行切换 --&gt;
&lt;div ng-switch on="person.name"&gt;
    KO!!! Winner is 
    &lt;!-- ng-switch-default不匹配任何ng-switch-when时显示 --&gt; 
    &lt;span ng-switch-default&gt;null&lt;/span&gt;
    &lt;!--  ng-switch-when仅当属性值匹配时显示 --&gt;
    &lt;span ng-switch-when="Alex"&gt;{{ person.name }}&lt;/span&gt;
&lt;/div&gt;</pre>
</td>
</tr>
<tr>
<td>ng-view</td>
<td>用于容纳路由使用的子模板</td>
</tr>
<tr>
<td>ng-if</td>
<td>如果表达式为true，则对应元素插入到DOM中；否则从DOM中移除。它与ng-show/ng-hide的不同之处是，后两者仅仅会隐藏DOM节点</td>
</tr>
<tr>
<td>ng-repeat</td>
<td>遍历一个集合或为集合中的每个元素生成一个模板实例，集合中<span style="background-color: #c0c0c0;">每个元素都被赋予新的子Scope</span>。并且新的Scope暴露以下属性：
<ol>
<li>$index 当前遍历的进度（0开始）</li>
<li>$first 如果当前是第一个元素</li>
<li>$middle 如果当前不是第一个、也不是最后一个元素</li>
<li>$last 如果当前是最后一个元素</li>
<li>$even 如果index为偶数</li>
<li>$odd 如果index为奇数</li>
</ol>
</td>
</tr>
<tr>
<td>ng-init</td>
<td>设置当前作用域的内部初始状态：<br />
<pre class="crayon-plain-tag">&lt;div ng-init="greeting='greetngs';person='Alex'"&gt;
    {{greeting}} {{person}}
&lt;/div&gt;</pre>
</td>
</tr>
<tr>
<td>{{ }}</td>
<td>双括号操作符本质上是指令，它是ng-bind的简写形式，不需要显式的创建HTML元素，可以嵌在HTML文本节点中。<br />在屏幕可视的区域内使用{{ }}会导致页面加载时未渲染的元素发生闪烁，用ng-bind可以规避该问题。</td>
</tr>
<tr>
<td>ng-bind</td>
<td>Angular加载含有{{ }}的元素后并不会立刻渲染它们，导致未渲染内容闪烁。使用ng-bind可以避免闪烁：<br />
<pre class="crayon-plain-tag">&lt;body ng-init="greeting='Hello'"&gt;
    &lt;p ng-bind="greeting"&gt;&lt;/p&gt;
&lt;/body&gt;</pre>
</td>
</tr>
<tr>
<td>ng-cloak</td>
<td>在{{}}所在元素上使用该指令，可以避免页面闪烁——先显示未解析的{{}}，然后闪烁为解析后的正确HTML<br />该指令会在解析完成之前隐藏目标元素，因而避免闪烁。该指令可以<span style="background-color: #c0c0c0;">放在祖先元素上</span>，从而对多个{{}}生效</td>
</tr>
<tr>
<td>ng-bind-template</td>
<td>类似于ng-bind，可以绑定多个表达式：<br />
<pre class="crayon-plain-tag">&lt;div ng-bind-template="{{message}}{{name}}"&gt;&lt;/div&gt;</pre>
</td>
</tr>
<tr>
<td>ng-model</td>
<td>
<p>来将input、select、text area或自定义表单控件同<span style="background-color: #c0c0c0;">包含它们的作用域中的属性（如果属性尚不存在，会隐式的创建）</span>进行绑定。该指令负责处理表单验证、在元素上设置ng-valid/ng-invalid等样式类、在父表单中注册控件：
<pre class="crayon-plain-tag">&lt;input type="text" ng-model="user.name" /&gt;</pre>
</td>
</tr>
<tr>
<td>ng-show<br />ng-hide</td>
<td>
<p>根据表达式的值显示或者隐藏HTML元素
</td>
</tr>
<tr>
<td>ng-change</td>
<td>
<p>当表单控件的值发生变化时，执行指定的表达式</p>
</td>
</tr>
<tr>
<td>ng-form</td>
<td>
<p>用来在一个表单内部嵌套另一个表单，只有内部所有的子表单都合法时，外部的表单才会合法，在通过ng-repeat动态创建表单时，该指令很有用</p>
<p>Angular不会通过POST提交表单，除非指定了action属性。要指定提交表单时执行的JavaScript函数，可以：</p>
<ol>
<li>ng-submit，在表单元素上标注该指令</li>
<li>ng-click，在第一个submit类型的input子元素上标注该指令</li>
</ol>
</td>
</tr>
<tr>
<td>ng-click</td>
<td>
<p>指定元素被点击时执行指定的表达式</p>
</td>
</tr>
<tr>
<td>ng-select</td>
<td>
<pre class="crayon-plain-tag">&lt;script type="text/javascript"&gt;
    angular.module( 'app', [] ).controller( 'Ctrler', function( $scope ) {
        $scope.cities = [ 
            { id : 1, name : 'Peking', country : 'China'}, 
            { id : 2, name : 'Nanking', country : 'China'}, 
            { id : 3, name : 'Canton', country : 'China' }, 
            { id : 4, name : 'Chunking', country : 'China' } , 
            { id : 4, name : 'Kyoto', country : 'Japan' }];
    } );
&lt;/script&gt;
&lt;body ng-app="app" ng-controller="Ctrler"&gt;
&lt;!--
如果数据集（下面的cities）为数组，ng-options的语法可以是：
label for value in array
select as label for value in array
label group by group for value in array
select as label group by group for value in array track by trackexpr
（select表示下拉项值，label表示标签）
--&gt;
&lt;select ng-model="city" ng-options=
    "city.id as city.name group by city.country for city in cities"&gt;
    &lt;option value=""&gt;Choose City&lt;/option&gt;&lt;!-- 未选择时显示的元素 --&gt;
&lt;/select&gt;
&lt;/body&gt;</pre>
</td>
</tr>
<tr>
<td>ng-submit</td>
<td>当表单提交时，执行指定的表达式</td>
</tr>
<tr>
<td>ng-class</td>
<td>动态设置元素的CSS样式类，该表达式的值是一个对象：<br />
<pre class="crayon-plain-tag">ng-class="{cssClassName: when1,cssClassName1:when2}"</pre></p>
<p>对象的属性名是CSS样式类名，属性值为布尔值，如果为true，则该样式类添加到元素上，否则从元素上移除</p>
</td>
</tr>
<tr>
<td>ng-attr-(suffix)</td>
<td>指定一个HTML属性，并绕过某些浏览器检查</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">Angular内置事件</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 12%; text-align: center;">类型</td>
<td style="width: 25%; text-align: center;">名称</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center;" rowspan="3">emit</td>
<td>$includeContentLoaded</td>
<td>当ngInclude的内容被重新加载时，从ngInclude指令上触发</td>
</tr>
<tr>
<td>$includeContentRequested</td>
<td>
<p>当ngInclude的内容被请求时，从ngInclude指令上触发</p>
</td>
</tr>
<tr>
<td>$viewContentLoaded</td>
<td>当ngView的内容被重新加载时，从当前ngView作用域上发送</td>
</tr>
<tr>
<td rowspan="7">broadcast</td>
<td>$locationChangeStart</td>
<td>通过$location服务对浏览器URL作更新时，触发该事件</td>
</tr>
<tr>
<td>$locationChangeSuccess</td>
<td>当浏览器的地址成功变更，又没有阻止$locationChangeStart事件时，该事件从$rootScope触发</td>
</tr>
<tr>
<td>$routeChangeStart</td>
<td>在路由变更发生之前（解析路由变更所需的所有依赖项时），$routeChangeStart事件从$rootScope触发</td>
</tr>
<tr>
<td>$routeChangeSuccess</td>
<td>在所有路由依赖项跟着$routeChangeStart被解析之后， $routeChangeSuccess事件从<br />$rootScope上触发</td>
</tr>
<tr>
<td>$routeChangeError</td>
<td>如果路由对象上任意的resolve属性被拒绝（Reject）了，$routeChangeError事件触发</td>
</tr>
<tr>
<td>$routeUpdate</td>
<td>如果$routeProvider上的reloadOnSearch属性被设置成false，并且使用了控制器的同一个<br />实例，$routeUpdate事件从$rootScope触发 </td>
</tr>
<tr>
<td>$destroy</td>
<td>在作用域被销毁之前，$destroy事件会在作用域上广播。子作用域可以利用此机会执行自身的清理</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">jqLite</span></div>
<p>函数<pre class="crayon-plain-tag">angular.element()</pre> 可以把一个DOM元素或者HTML字符串包装为一个类似jQuery元素的对象。如果当前应用使用jQuery（其JS文件必须在Angular之前引入），那么该函数的返回值就是一个jQuery元素；否则，它是一个jqLite对象——Angular内置的jQuery的子集。jqLite更加轻量，和jQuery元素的API兼容。jqLite提供以下jQuery兼容方法：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 20%; text-align: center;">方法</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>addClass()</td>
<td>添加指定的样式类到元素上：<pre class="crayon-plain-tag">addClass( "myClass yourClass" );</pre></td>
</tr>
<tr>
<td>after()</td>
<td>插入内容到元素后面（作为兄弟元素）：<pre class="crayon-plain-tag">after( "&lt;p&gt;Test&lt;/p&gt;" )</pre> ，支持入参HTML、Element、Array</td>
</tr>
<tr>
<td>append()</td>
<td>作为最后一个子元素插入，支持入参HTML、Element、Array</td>
</tr>
<tr>
<td>attr()</td>
<td>不支持以函数作为入参。获取或者设置HTML属性，从jQuery1.6开始，对于尚未设置的HTML属性，总是返回undefined。如果要使用checked, selected, disabled等属性，应当使用prop()方法：<br />
<pre class="crayon-plain-tag">//对于元素：&lt;input type="checkbox" checked="checked" /&gt;
htmlElement.checked == e.prop( "checked" ) == true
htmlElement.getAttribute( "checked" ) == e.attr( "checked" ) == "checked"
e.attr( "checked" ) == "checked" //1.6+
e.attr( "checked" ) == true   //1.6-</pre>
</td>
</tr>
<tr>
<td>bind()</td>
<td>签名：<pre class="crayon-plain-tag">bind( eventType [, eventData ], handler )</pre> <br />不支持namespaces, selectors,eventData。给选中元素的一个或者多个事件添加监听器</td>
</tr>
<tr>
<td>children()</td>
<td>不支持selectors。获取元素的子元素</td>
</tr>
<tr>
<td>clone()</td>
<td>创建一个元素的深复制</td>
</tr>
<tr>
<td>contents()</td>
<td> 获取每个元素的子节点，返回的集合中包含文本和注释节点</td>
</tr>
<tr>
<td>css()</td>
<td>仅检索inline样式，不调用getComputedStyle()。获取或设置元素的style属性值</td>
</tr>
<tr>
<td>data()</td>
<td>存储或返回与元素关联的指定数据值</td>
</tr>
<tr>
<td>detach()</td>
<td>从DOM中移除匹配的元素</td>
</tr>
<tr>
<td>empty()</td>
<td>清除所有子节点</td>
</tr>
<tr>
<td>eq()</td>
<td>获取指定索引位置的元素</td>
</tr>
<tr>
<td>find()</td>
<td>限制通过标签名查找。过滤元素的子节点</td>
</tr>
<tr>
<td>hasClass()</td>
<td>确定元素本身是否分配了给定的CSS类</td>
</tr>
<tr>
<td>html()</td>
<td>获取或者设置元素的HTML内容</td>
</tr>
<tr>
<td>next()</td>
<td>不支持selectors。获取紧跟元素的兄弟元素</td>
</tr>
<tr>
<td>on()</td>
<td>不支持namespaces, selectors,eventData。类似于bind()</td>
</tr>
<tr>
<td>off()</td>
<td>不支持namespaces, selectors,eventData。类似于unbind()</td>
</tr>
<tr>
<td>one()</td>
<td>签名：<pre class="crayon-plain-tag">one( events [, data ], handler )</pre> <br />不支持namespaces, selectors。仅执行一次的事件监听器</td>
</tr>
<tr>
<td>parent()</td>
<td>不支持selectors。获取元素的父元素</td>
</tr>
<tr>
<td>prepend()</td>
<td> 将内容作为子元素插入到元素的开头</td>
</tr>
<tr>
<td>prop()</td>
<td> 获取或设置元素的属性值</td>
</tr>
<tr>
<td>ready()</td>
<td>指定一个DOM加载完成时执行的函数</td>
</tr>
<tr>
<td>remove()</td>
<td>从DOM中移除元素</td>
</tr>
<tr>
<td>removeAttr()</td>
<td>从元素中移除一个属性</td>
</tr>
<tr>
<td>removeClass()</td>
<td>从元素中移除一个、多个或者所有CSS类</td>
</tr>
<tr>
<td>removeData()</td>
<td>从元素中移除先前存储的数据</td>
</tr>
<tr>
<td>replaceWith()</td>
<td>使用提供的新内容替换元素</td>
</tr>
<tr>
<td>text()</td>
<td>获取或者设置元素中合并的文本内容</td>
</tr>
<tr>
<td>toggleClass()</td>
<td>从元素中添加或者移除一个或者多个CSS类</td>
</tr>
<tr>
<td>triggerHandler()</td>
<td>传递假的事件对象给handler。执行附加给元素的某个事件的所有事件处理程序</td>
</tr>
<tr>
<td>unbind()</td>
<td>不支持namespaces,eventData。通过名称移除一个事件处理程序</td>
</tr>
<tr>
<td>val()</td>
<td>获取或设置元素的当前值</td>
</tr>
<tr>
<td>wrap()</td>
<td>使用指定的HTML结构包裹元素</td>
</tr>
</tbody>
</table>
<p>jqLite提供额外的方法：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">方法</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>controller(name)</td>
<td>返回当前元素或者其祖先元素关联的控制器，默认获取关联到ngController指令的控制器，如果提供name参数，则name指定的控制器被返回</td>
</tr>
<tr>
<td>injector()</td>
<td>返回当前元素或者其祖先元素的injector</td>
</tr>
<tr>
<td>scope()</td>
<td>返回当前元素或者其祖先元素关联的作用域对象</td>
</tr>
<tr>
<td>isolateScope()</td>
<td>判断当前元素是否直接关联了一个隔离作用域</td>
</tr>
<tr>
<td>inheritedData()</td>
<td>类似data()，但是会沿着DOM树上溯寻找，知道根元素</td>
</tr>
</tbody>
</table>
<p>此外jqLite还提供了<pre class="crayon-plain-tag">$destroy</pre> 事件，在DOM节点被移除时，该事件触发。</p>
<div class="blog_h1"><span class="graybg">Angular组件和特性</span></div>
<div class="blog_h2"><span class="graybg">Hello World</span></div>
<p>本节使用简单的付款应用的例子，介绍AngularJS的基本概念。</p>
<div class="blog_h3"><span class="graybg">数据绑定示例</span></div>
<p>下面是付款应用最初的版本，运行后，可以观察到表单字段和总计金额的自动联动关系。</p>
<pre class="crayon-plain-tag">&lt;body&gt;
    &lt;!-- ng-app指令用于初始化Angular应用 --&gt;
    &lt;!-- ng-init指令用于执行初始化动作 --&gt;
    &lt;div ng-app ng-init="qty=1;cost=2"&gt;
        &lt;b&gt;发货单:&lt;/b&gt;
        &lt;div&gt;
            &lt;!-- ng-model绑定表单元素到模型的字段，可以认为模型是一个单独的名字空间 --&gt;
            数量: &lt;input type="number" min="0" ng-model="qty"&gt;
        &lt;/div&gt;
        &lt;div&gt;
            金额: &lt;input type="number" min="0" ng-model="cost"&gt;
        &lt;/div&gt;
        &lt;div&gt;
            &lt;!-- 双括号标记，表达式 | 过滤器形式。可以访问模型字段并进行JavaScript运算 --&gt;
            &lt;b&gt;总计:&lt;/b&gt; {{qty * cost | currency}}
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/body&gt;</pre>
<div class="blog_h3"><span class="graybg">控制器示例 </span></div>
<p>下面我们为付款应用添加一个AngularJS模块，并注册一个控制器类：</p>
<pre class="crayon-plain-tag">//声明一个AngularJS模块，声明后的模块可以被ng-app引用
var app = angular.module( 'invoiceModule', [] );
//注册一个控制器类
app.controller( 'InvoiceController', function() { //构造函数
    //成员变量
    this.qty = 1;
    this.cost = 2;
    this.inCurr = '欧元';
    this.currencies = [ '美元', '欧元', '人民币' ];
    this.usdToForeignRates = {
        '美元' : 1,
        '欧元' : 0.74,
        '人民币' : 6.09
    };
    //成员函数
    this.total = function total( outCurr ) {
        return this.convertCurrency( this.qty * this.cost, this.inCurr, outCurr );
    };
    this.convertCurrency = function convertCurrency( amount, inCurr, outCurr ) {
        return amount * this.usdToForeignRates[outCurr] / this.usdToForeignRates[inCurr];
    };
    this.pay = function pay() {
        window.alert( "谢谢！" );
    };
} );</pre>
<p> 修改HTML文件，使用控制器：</p>
<pre class="crayon-plain-tag">&lt;html&gt;
&lt;head&gt;
&lt;meta http-equiv="Content-Type" content="text/html; charset=utf-8" /&gt;
&lt;script type="text/javascript" src="lib/angular.js"&gt;&lt;/script&gt;
&lt;script type="text/javascript" src="invoice-module.js"&gt;&lt;/script&gt;
&lt;/head&gt;
&lt;body&gt;
    &lt;!-- 可以指定ng-app使用的主模块（Main module），AngularJS启动时，会使用初始化该模块，以及它依赖的所有其它模块--&gt;
    &lt;!-- ng-controller 指定该ng-app对应的控制器，该控制器对所有子元素负责 --&gt;
    &lt;!-- InvoiceController as invoice 相当于在当前Scope定义var invoice = new InvoiceController(); --&gt;
    &lt;div ng-app="invoiceModule" ng-controller="InvoiceController as invoice"&gt;
        &lt;b&gt;发货单:&lt;/b&gt;
        &lt;div&gt;
            &lt;!-- ng-model可以绑定控制器的成员变量 --&gt;
            数量: &lt;input type="number" min="0" ng-model="invoice.qty" required&gt;
        &lt;/div&gt;
        &lt;div&gt;
            金额: &lt;input type="number" min="0" ng-model="invoice.cost" required&gt;
        &lt;/div&gt;
        &lt;div&gt;
            币种: 
            &lt;select ng-model="invoice.inCurr"&gt;
                &lt;!-- ng-repeat用于重复指令所在元素，c作为当前迭代元素 --&gt;
                &lt;option ng-repeat="c in invoice.currencies"&gt;{{c}}&lt;/option&gt;
            &lt;/select&gt;
        &lt;/div&gt;
        &lt;div&gt;
            &lt;b&gt;总计:&lt;/b&gt; &lt;span ng-repeat="c in invoice.currencies"&gt; {{invoice.total(c) | currency:c}} &lt;/span&gt;
            &lt;!--  ng-click，当按钮被点击时，执行的处理函数 --&gt;
            &lt;button class="btn" ng-click="invoice.pay()"&gt;付款&lt;/button&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;</pre>
<div class="blog_h3"><span class="graybg">引入服务层</span></div>
<p>付款应用第二版的例子中，所有的逻辑都在控制器中。当应用程序变得复杂时，最好把视图无关的（View-independent）业务逻辑代码移动到服务层中，实现表现层与服务层的分离。</p>
<pre class="crayon-plain-tag">angular.module( 'finance', [/*不依赖其它模块*/] ) 
//下面声明了一个名为currencyConverter的对象工厂，调用工厂函数将返回一个currencyConverter服务
.factory( 'currencyConverter', function() { //这是工厂函数，不是构造器
    var currencies = [ '美元', '欧元', '人民币' ];
    var usdToForeignRates = {
        '美元' : 1,
        '欧元' : 0.74,
        '人民币' : 6.09
    };
    var convert = function( amount, inCurr, outCurr ) {
        return amount * usdToForeignRates[outCurr] / usdToForeignRates[inCurr];
    };
    //这个对象才是currencyConverter服务，它包含一个属性、一个方法：
    return {
        currencies : currencies,
        convert : convert
    };
} );</pre><br />
<pre class="crayon-plain-tag">angular.module( 
        'invoiceModule',     //模块名
        [ 'finance' ]  //所依赖的模块的列表
)
.controller( 'InvoiceController', [ 
    //数组，前面是需要注入的组件的声明，最后是当前组件的构造函数
    //这里会发生依赖注入，AngularJS发现currencyConverter是一个工厂，会调用它并生成服务对象，然后注入到InvoiceController的构造函数
    'currencyConverter',   
    //最后一个是控制器的构造函数，前面的这是依次需要注入的组件
    function( currencyConverter ) {
        this.qty = 1;
        this.cost = 2;
        this.inCurr = '欧元';
        this.currencies = currencyConverter.currencies;
    
        this.total = function total( outCurr ) {
            //改为调用服务提供的方法
            return currencyConverter.convert( this.qty * this.cost, this.inCurr, outCurr );
        };
        this.pay = function pay() {
            window.alert( "谢谢！" );
        };
    } 
] );</pre>
<div class="blog_h3"><span class="graybg">与后端服务器通信 </span></div>
<p>通常，Web应用都需要与后端HTTP服务进行通信，我们进一步改造付款应用。在这一版本中，我们让Service基于Ajax与后端通信：</p>
<pre class="crayon-plain-tag">angular.module( 'finance', [] ).factory( 'currencyConverter', [ 
    '$http', // 服务工厂需要注入此依赖
    function( $http ) {
        var currencies = [ '美元', '欧元', '人民币' ];
        var usdToForeignRates = {}; // 我们将从后端服务获得汇率列表
    
        var convert = function( amount, inCurr, outCurr ) {
            return amount * usdToForeignRates[outCurr] / usdToForeignRates[inCurr];
        };
        //获取汇率
        var refresh = function() {
            //通过jsonp执行跨域请求
            return $http.jsonp( BACKEND_URL ).then( function( response ) {
                //回调函数
                usdToForeignRates = response.data.query.results.rate;
            } );
        };
    
        refresh(); 
    
        return {
            currencies : currencies,
            convert : convert
        };
    } 
] );</pre>
<div class="blog_h2"><span class="graybg">模块</span></div>
<p>可以认为模块是多种应用程序组件——控制器、服务、过滤器、指令，等等——的容器。</p>
<p>传统的应用程序具有Main函数作为入口点，通过该入口点，可以实例化应用程序的其他组件。Angular没有这的入口点，它只是通过模块机制，<span style="background-color: #c0c0c0;">声明式的指定</span>应用程序该如何启动。这种方式具有以下优点：</p>
<ol>
<li>声明性的过程简洁，易于理解</li>
<li>可以保持全局命名空间的清洁</li>
<li>可以通过模块<span style="background-color: #c0c0c0;">组织可重用组件</span></li>
<li>单元测试中可以仅加载需要的代码（模块）</li>
</ol>
<p>对于大规模的应用程序，你应该分割出多个模块：</p>
<ol>
<li>每个特性（Feature）设置一个模块</li>
<li>每个可重用组件一个模块，特别是指令和过滤器</li>
<li>应用程序主模块，作为应用入口，依赖于上述模块，包含初始化代码</li>
</ol>
<div class="blog_h3"><span class="graybg">主模块</span></div>
<p>每个HTML页面可以对应一个AngularJS应用，通过指令<pre class="crayon-plain-tag">ng-app</pre> 可以指定该应用使用的Angular模块，此模块即主模块。</p>
<p>ng-app指令可以定义在任何HTML节点上，这意味着你可以让<span style="background-color: #c0c0c0;">页面的一部分使用Angular实现</span>，其它部分使用别的JavaScript框架。</p>
<div class="blog_h3"><span class="graybg">模块加载过程</span></div>
<p>在模块启动期间，Angular可以执行一系列配置、运行块：</p>
<ol>
<li>Config块：在Provider注册和配置阶段被执行。只有Provider、Constant可以被注入到Configuration块，这可以避免在服务尚未配置好之前意外的初始化它们。配置块调用方式为：<br /><pre class="crayon-plain-tag">module.config(function(injectables){});</pre> </li>
<li>Run块：在Injector创建好之后执行，用来kickstart应用程序。只有Instance、Constant可以被注入到Run块，这可以避免在应用程序运行期间再进行系统配置。运行快的调用方式为：<br /><pre class="crayon-plain-tag">module.run(function(injectables){});</pre> </li>
</ol>
<p>Angular提供了一系列的快捷方法，调用它们和执行Config块是等价的：</p>
<pre class="crayon-plain-tag">angular.module('app', []).
value('a', 123). 
factory('a', function() { return 123; }).
directive('directiveName', ...).
filter('filterName', ...);

//和下面的代码等价 

angular.module('myModule', []).
config(function($provide, $compileProvider, $filterProvider) {
  $provide.value('a', 123);
  $provide.factory('a', function() { return 123; });
  $compileProvider.directive('directiveName', ...);
  $filterProvider.register('filterName', ...);
});</pre>
<p>上述代码向Angular（的Injector）注册若干组件（值、服务、指令、过滤器），确切的说，是注册组件的Provider。Provider告知了Angular如何创建这些组件，你可以简单的把Provider理解为工厂，本文<a href="#provider">后续章节</a>会详细介绍Provider。</p>
<p>Run块是和Main函数最相似的组件，它在所有服务均已配置、Injector已经创建后执行。在Angular中，Run块通常存放一些难于单元测试的代码。 </p>
<div class="blog_h3"><span class="graybg">模块的依赖</span></div>
<p>模块可以依赖于（require）其他模块，被依赖模块的Config块，总是<span style="background-color: #c0c0c0;">先于依赖它的模块的Config块执行</span>。此规则同样适合Run块。</p>
<p>每个<span style="background-color: #c0c0c0;">模块只能被加载一次</span>，即使很多模块都依赖于它。</p>
<div class="blog_h3"><span class="graybg">找回模块的引用</span></div>
<p>语句：<pre class="crayon-plain-tag">angular.module('myModule', [])</pre> 会重新定义myModule模块，被覆盖模块管理的所有组件均被销毁。</p>
<p>语句：<pre class="crayon-plain-tag">angular.module('myModule')</pre> 则会找回已经存在的myModule模块的引用，如果myModule模块尚未定义，则抛出异常。</p>
<div class="blog_h2"><span class="graybg">模板与视图</span></div>
<p>所谓模板就是<span style="background-color: #c0c0c0;">添加了Angular特殊标记的HTML</span>。这些特殊标记包括：指令、双括号标记、过滤器、表单控件（用于验证用户输入）。 </p>
<p>在复杂的应用中，Angular支持在单个主页面下，使用多个<span style="background-color: #c0c0c0;">模板片断（partial templates）</span>来显示多个应用视图。所谓模板片断，就是<span style="background-color: #c0c0c0;">分散在不同HTML文件中的模板片段</span>。你可以使用<pre class="crayon-plain-tag">ngView</pre> 指令，基于传递给<pre class="crayon-plain-tag">$route</pre> 服务的配置来加载模板片断。</p>
<div class="blog_h3"><span class="graybg">视图</span></div>
<p>在Angular中，视图就是<span style="background-color: #c0c0c0;">模型在模板上的投影</span>。就好像你的影子会随着你走动一样，模型的任何变化也会体现到视图上。</p>
<div class="blog_h2"><span class="graybg">控制器</span></div>
<p>在Angular中，控制器是一类特殊的服务，其增强了视图的功能：</p>
<ol>
<li>控制器可以提供行为，供视图中事件监听指令调用</li>
<li>控制器可以方便的扩展$scope，添加模型属性</li>
</ol>
<p><pre class="crayon-plain-tag">ng-controller</pre> 指令会导致一个新创建的控制器实例关联到对应的DOM元素。Angular会创建一个新的Child Scope，该Scope隐式的关联到控制器对象。<pre class="crayon-plain-tag">$scope</pre> 代表此Scope，可以作为控制器的构造函数参数注入。 控制器实例的变量名（as varName）也作为$scope的属性。</p>
<p>应该避免使用控制器来：</p>
<ol>
<li>操控DOM。控制器应当仅仅包含业务逻辑，如果其中包含任何展现层逻辑，会严重的影响可测试性。使用数据绑定、指令可以封装大部分DOM操作</li>
<li>格式化输入。可以使用Angular表单控件来完成</li>
<li>过滤输出。可以使用Angular过滤器来完成</li>
<li>在控制器之间共享代码或状态。可以使用Angular Service完成</li>
<li>管理其它Angular组件的生命周期，例如创建Service实例</li>
</ol>
<div class="blog_h3"><span class="graybg">初始化$scope对象</span></div>
<p>通过为$scope添加必要的属性来初始化之，这些属性全部可以在HTML子元素的标记中访问：</p>
<pre class="crayon-plain-tag">angular.module('myApp').controller('GreetingController', ['$scope', function($scope) {
    $scope.greeting = 'Hola!';  //为$scope添加状态
    $scope.double = function(value) { return value * 2; }; //为$scope添加行为
}]);</pre><br />
<pre class="crayon-plain-tag">&lt;!-- 子元素都可以访问$scope，可以直接访问属性、方法 --&gt;
&lt;div ng-controller="GreetingController"&gt;
    {{ greeting }}
    &lt;input ng-model="num"&gt; equals {{ double(num) }}
&lt;/div&gt;</pre>
<div class="blog_h3"><span class="graybg">嵌套的控制器与Scope</span></div>
<p>控制器关联的DOM元素可以具有嵌套关系，相应的，这些控制器对应的Scope也具有嵌套关系，这类似于块级作用域： </p>
<pre class="crayon-plain-tag">&lt;div ng-controller="MainController"&gt;
    &lt;p&gt;Hello, {{name}}!&lt;/p&gt;

    &lt;div ng-controller="ChildController"&gt;
        &lt;p&gt;Hello,{{name}}!&lt;/p&gt;
        &lt;div ng-controller="GrandChildController"&gt;
            &lt;!-- GrandChildController的$如果定义了$scope.name，则使用，否则到外层scope中搜索 --&gt;
            &lt;p&gt;Hello, {{name}}!&lt;/p&gt; 
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;</pre>
<div class="blog_h2"><span class="graybg">服务</span></div>
<p>Angular中的服务是通过DI组织在一起的一群可替换对象，用于在应用程序中组织和共享代码。服务具有以下特性：</p>
<ol>
<li> 延迟初始化：仅当某个组件依赖于它，服务才被创建</li>
<li>单例：所有依赖者都获得同一个服务实例的引用</li>
</ol>
<p>Angular内置了若干重要的服务，例如$http。</p>
<div class="blog_h3"><span class="graybg">定义服务</span></div>
<p>开发人员可以定义自己的服务，并且通过一个<span style="background-color: #c0c0c0;">服务名称、服务工厂函数</span>，将其关联到某个模块。服务工厂函数可以返回一个对象/函数，此返回值（而非工厂函数本身）将被注入到依赖者中。</p>
<p>与所有Angular组件一样，服务也可以声明自己的依赖项。</p>
<div class="blog_h3"><span class="graybg">通过$provide注册服务</span></div>
<p>可以在模块的config函数中，通过$provide服务注册新的服务：</p>
<pre class="crayon-plain-tag">angular.module( 'myModule', [] ).config( [ '$provide', function( $provide ) {
    $provide.factory( 'serviceId', function() {
        return new Object();
    } );
} ] );</pre>
<p>这种方式常用在单元测试中，仿冒被测试组件的依赖项。</p>
<div class="blog_h2"><span class="graybg">作用域（Scope）</span></div>
<p>Scope是<span style="background-color: #c0c0c0;">应用程序数据模型（Data Model，简称Model）的容器</span>，某些时候你可以认为它和数据模型是同义词。同时Scope也是<span style="background-color: #c0c0c0;">表达式的执行上下文</span>。Scope形成嵌套的、层次性结构，就像DOM结构本身一样。Scope能够用于监控表达式、传播事件。</p>
<p>Scope充当模板（视图）、模型、控制器之间的粘合剂，它让模型和视图解耦，却又能保持同步。</p>
<div class="blog_h3"><span class="graybg">Scope的特性</span></div>
<ol>
<li>Scope提供了<pre class="crayon-plain-tag">$watch</pre> 函数，用于观察模型的变化。利用此API你可以把模型变更通知给整个Angular应用甚至Angular领域之外</li>
<li>Scope提供了<pre class="crayon-plain-tag">$apply</pre>  函数，应用来自Angular领域之外（控制器、服务、Angular事件处理器）的模型变化到视图组件</li>
</ol>
<p>通过嵌套，可以限制Scope对应用组件属性的访问，同时允许Scope对共享模型属性的访问。内嵌的Scope分为两类：</p>
<ol>
<li>子作用域（Child scopes）：利用JavaScript<span style="background-color: #c0c0c0;">原型机制</span>，从父Scope继承属性</li>
<li>隔离作用域（Isolate scopes）：不从父Scope继承属性</li>
</ol>
<div class="blog_h3"><span class="graybg">作为数据模型的Scope</span></div>
<p>Scope的（自定义）属性，就是数据模型的属性。</p>
<p>Scope是控制器和视图之间的桥梁。在模板链接期间，指令在Scope上创建$watch表达式。$watch允许指令在属性变化时获得通知，指令因而能够更新DOM的渲染。</p>
<p>尽管控制器和视图都引用Scope，但是它们绝不互相引用。这确保了可测试性。视图应当仅仅和Scope相关，从中获得渲染所需数据。</p>
<div class="blog_h3"><span class="graybg">Scope的层次</span></div>
<p>每个Angular应用程序都包含唯一的<span style="background-color: #c0c0c0;">根Scope</span>，此根Scope下可以有多个Child Scope。你可以注入<pre class="crayon-plain-tag">$rootScope</pre> 以使用根Scope。某些指令会自动创建新的Child Scope，新创建的Scope会自动加入到<span style="background-color: #c0c0c0;">Scope树</span>中。Scope树与其节点关联的DOM树，呈并行对应关系。</p>
<p>当Angular去估算一个表达式<pre class="crayon-plain-tag">{{varName}}</pre> 时，它会从表达式所在DOM元素<span style="background-color: #c0c0c0;">关联的Scope上</span>寻找varName的定义，如果找不到，则<span style="background-color: #c0c0c0;">到父Scope中寻找</span>，直到根Scope。当一个DOM元素关联到某个Scope时，Angular自动为其添加样式类<pre class="crayon-plain-tag">ng-scope</pre> 。</p>
<p>下面的例子会创建两层、三个Scope：</p>
<pre class="crayon-plain-tag">&lt;body ng-app ng-init="names = ['Alex','Wong']"&gt; &lt;!-- 根Scope --&gt;
&lt;span ng-repeat="name in names"&gt;{{name}}&lt;/span&gt; &lt;!-- 重复指令，每次循环创建一个Scope --&gt;</pre>
<p>上述模板生成的DOM结构如下： </p>
<pre class="crayon-plain-tag">&lt;html ng-app="" ng-init="names = ['Alex','Wong']" class="ng-scope"&gt;
    &lt;body&gt;
        &lt;!-- ngRepeat: name in names --&gt;
        &lt;span ng-repeat="name in names" class="ng-binding ng-scope"&gt;Alex&lt;/span&gt;
        &lt;!-- end ngRepeat: name in names --&gt;
        &lt;span ng-repeat="name in names" class="ng-binding ng-scope"&gt;Wong&lt;/span&gt;
        &lt;!-- end ngRepeat: name in names --&gt;
    &lt;/body&gt;
&lt;/html&gt;</pre>
<p>你可以通过<pre class="crayon-plain-tag">angular.element(aDomElement).scope()</pre> 获得某个DOM元素关联的Scope。</p>
<div class="blog_h3"><span class="graybg">Scope事件传播</span></div>
<p>Angular的事件系统是以Scope为中心的，Scope负责向外发出事件。尽管不同Scope可以通过共享变量进行通信，但是你应当更依赖事件机制，以便解耦不同应用组件。</p>
<p>Scope的事件传播方式与DOM事件类似：</p>
<ol>
<li>向下广播（<pre class="crayon-plain-tag">$broadcast(eventName,eventArgs)</pre> ）：沿Scope链，向下传播到所有子代作用域。如果从$rootScope上调用，相当于全局广播</li>
<li>向上冒泡（<pre class="crayon-plain-tag">$emit(eventName,eventArgs)</pre> ）：沿作用域链，向上传播到所有祖先作用域</li>
</ol>
<p>在可以传播事件的同时，Scope还支持注册事件监听器：<pre class="crayon-plain-tag">$on(eventName,function listener(event, eventArgs));</pre> ，该函数的返回值是一个“反注册函数”，<span style="background-color: #c0c0c0;">调用它即可取消事件监听器</span>：。从监听器里面抛出的异常，会全部传递到<pre class="crayon-plain-tag">$exceptionHandler</pre> 服务处理。监听器的<pre class="crayon-plain-tag">event</pre> 入参即事件对象，具有以下属性/方法：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">属性/方法</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>targetScope</td>
<td>发出该事件的原始Scope对象</td>
</tr>
<tr>
<td>currentScope</td>
<td>当前监听器所在的Scope对象</td>
</tr>
<tr>
<td>name</td>
<td>事件的名称</td>
</tr>
<tr>
<td>stopPropagation()</td>
<td>禁止冒泡事件进一步向上传播</td>
</tr>
<tr>
<td>preventDefault()</td>
<td>设置<pre class="crayon-plain-tag">defaultPrevented</pre> 标记为true，这样作用域链上后续的监听器可以依据此标记来决定需要执行的动作</td>
</tr>
<tr>
<td>defaultPrevented</td>
<td>一个标记位</td>
</tr>
</tbody>
</table>
<p>下面是Scope事件传播的一个例子：</p>
<pre class="crayon-plain-tag">&lt;script type="text/javascript"&gt;
    angular.module( 'eventModule', [] ).controller( 'EventController', [ '$scope', function( $scope ) {
        $scope.count = 0;
        //注册事件监听器
        $scope.$on( 'HelloEvent', function() {
            $scope.count++;
        } );
    } ] );
&lt;/script&gt;
&lt;/head&gt;
&lt;body ng-app="eventModule"&gt;
    &lt;div ng-controller="EventController"&gt;&lt;!-- 每个ng-controller都会创建自己的实例 --&gt;
        &lt;!-- Root Scope --&gt;
        Root scope &lt;tt&gt;HelloEvent&lt;/tt&gt; count: {{count}}
        &lt;ul&gt;
            &lt;li ng-repeat="i in [1]" ng-controller="EventController"&gt;
                &lt;button ng-click="$emit('MyEvent')"&gt;Emit&lt;/button&gt; &lt;!-- 触发事件，向上传播 --&gt;
                &lt;button ng-click="$broadcast('MyEvent')"&gt;Broadcast&lt;/button&gt; &lt;br&gt; &lt;!-- 触发事件，向下传播 --&gt;
                Middle scope &lt;tt&gt;HelloEvent&lt;/tt&gt; count: {{count}} &lt;!-- Current Scope --&gt;
                &lt;ul&gt;
                    &lt;!-- Child Scope --&gt;
                    &lt;li ng-repeat="item in [1, 2]" ng-controller="EventController"&gt;Leaf scope &lt;tt&gt;HelloEvent&lt;/tt&gt; count: {{count}} &lt;/li&gt;
                &lt;/ul&gt;
            &lt;/li&gt;
        &lt;/ul&gt;
    &lt;/div&gt;
&lt;/body&gt;</pre>
<div class="blog_h3"><span class="graybg">Scope的生命周期 </span></div>
<ol>
<li>创建：$rootScope是在应用启动时，由$Injector自动创建的；在模板链接阶段，<span style="background-color: #c0c0c0;">某些指令</span>（ng-controller、ng-repeat等）会创建Child scope</li>
<li>观察者注册：在模板链接阶段，指令会通过<pre class="crayon-plain-tag">$watch(watchExpression, listener)</pre> 注册观察者（Watcher）。watchExpression可以是Scope的一个属性、一个表达式的文本、或者一个函数（函数的返回值作为需要监控的值），listener只会在watchExpression当前值与先前值不相等时执行。很多指令会<span style="background-color: #c0c0c0;">隐式的注册</span>观察者，例如ngModel、{{}}、ngBind等</li>
<li>修改模型：要保证模型的修改被正确的监控（observed）到，应当使用<pre class="crayon-plain-tag">scope.$apply()</pre> 来执行修改。Angular的API会自动调用$apply，因此在控制器中<span style="background-color: #c0c0c0;">同步的修改模型</span>或者<span style="background-color: #c0c0c0;">通过$http、$timeout、$interval 等服务异步修改模型</span>时，不需要显式调用该方法。该方法的规格如下：<br />
<pre class="crayon-plain-tag">/**
 * @param expr 字符串表达式或者回调函数，回调函数唯一入参是当前$scope
 * @returns 表达式或者回调函数的执行结果
 */
function $apply(expr){}</pre>
</li>
<li>监控模型的修改：在$apply<span style="background-color: #c0c0c0;">调用的结尾</span>，Angular会在$rootScope上直接执行一个 <pre class="crayon-plain-tag">$digest()</pre> 循环，此操作会传播到所有Child scope上。所有被观察的表达式（watchExpression）被重新估算，以确认修改是否发生（脏值检查）。如果发生，则执行listener指定的逻辑</li>
<li>销毁 ：当Child scope不再需要，Angular会调用<pre class="crayon-plain-tag">scope.$destroy()</pre> 以销毁之。销毁后，$digest操作不会在传播到当前Child scope，并且Child scope模型所占用的内存空间可以被回收</li>
</ol>
<div class="blog_h3"><span class="graybg">Scope与指令</span></div>
<p>依据其与Scope交互的方式，可以把指令分为两类：</p>
<ol>
<li>负责观察的指令（Observing directives）：例如{{双括号表达式}}，它们通过$watch注册一个监听函数，每当表达式的值发生变化，这类指令都会收到通知，以便更新视图</li>
<li>负责监听（用户操作）的指令：例如ng-click，它们注册DOM事件的监听函数，当DOM事件发生时，它们使用$apply()方法执行关联的表达式并更新视图</li>
</ol>
<p>再次强调：使用不属于Angular领域的方式操控模型，必须通过$apply()方法完成，否则监听器可能无法正常的更新视图。</p>
<div class="blog_h3"><span class="graybg">Scope与控制器的交互</span></div>
<p>Scope与控制器的交互主要有以下几种情况：</p>
<ol>
<li>控制器利用Scope向模板暴露方法</li>
<li>控制器定义可以改变模型（Scope的属性）的方法</li>
<li>控制器可以注册模型的观察者（Watcher）</li>
</ol>
<div class="blog_h3"><span class="graybg" style="font-weight: bold;">$watch的非功能考虑</span></div>
<p>在Scope上进行脏检查是很频繁的操作，因此$watch指定的脏检查表达式应当是高效的，<span style="background-color: #c0c0c0;">不能包含针对DOM元素的操作</span>，因为DOM操作相对于JavaScript属性访问，速度有数量级的差距。</p>
<p>此外，不要在控制器中显式调用作用域的$watch方法， 这导致可测试性降低。</p>
<div class="blog_h3"><span class="graybg">$watch的“深度”</span></div>
<p>脏检查可以依据三种策略完成：</p>
<ol>
<li>基于引用的检查，通过<pre class="crayon-plain-tag">scope.$watch (exp, listener, false)</pre> 指定此策略。仅当表达式的返回值变为一个新的对象时，才认为数据已脏。也就是说，集合内部增减元素，不认为集合变脏</li>
<li>基于集合内容的检查，通过调用<pre class="crayon-plain-tag">scope.$watchCollection(exp,listener)</pre> 指定此策略。如果表达式返回值是一集合，当添加元素、删除元素、改变元素顺序时，也认为集合变脏</li>
<li>基于值的检查，通过调用<pre class="crayon-plain-tag">scope.$watch(exp,listener,true)</pre> 指定此策略。表达式内任何嵌套的对象发生变化时，都能检测为脏数据。这种方式最强大也最昂贵，在每次digest时，被观察表达式的整个对象结构需要被遍历</li>
</ol>
<div class="blog_h3"><span class="graybg">Angular如何与浏览器的事件循环集成</span></div>
<p>如下图，左侧显示的浏览器事件循环，右侧显示了AngularJS对浏览器事件循环的扩展：<br /><img class="aligncenter size-full wp-image-10489" src="https://blog.gmem.cc/wp-content/uploads/2015/12/concepts-runtime.png" alt="concepts-runtime" width="364" height="279" /></p>
<p>浏览器事件循环：</p>
<ol>
<li>首先需要明确一点，浏览器中的JavaScript脚本执行是“单线程”的，这意味着事件需要一件件处理，事件监听函数需要一个个执行</li>
<li>事件循环（Event-loop）等待一个事件的到达，这些事件可以是<span style="background-color: #c0c0c0;">用户交互、定时器事件、网络事件</span>（来自服务器的响应）</li>
<li>事件的回调函数被执行。此时进入<span style="background-color: #c0c0c0;">JavaScript上下文</span>。回调函数可能修改DOM的结构</li>
<li>回调执行完毕后，浏览器退出JavaScript上下文，并依据修改后的DOM结构重新渲染视图</li>
</ol>
<p>Angular修改了上述第二步，把JavaScript上下文分割为：经典的JavaScript上下文、Angular执行上下文两个部分。只有在Angular上下文中执行的操作才能受益于Angular数据绑定、异常处理、属性监控。通过$apply可以进入Angular上下文。 </p>
<p>Angular执行上下文的工作流流程如下：</p>
<ol>
<li>调用scope.$apply(fn)进入Angular执行上下文。fn是你期望在Angular执行上下文中完成的操作</li>
<li>Angular执行fn，通常会改变应用程序状态</li>
<li>Angular进入Digest循环。该循环由两个更小的循环组成，分别处理<span style="background-color: #c0c0c0;">$evalAsync队列</span>、<span style="background-color: #c0c0c0;">$watch队列</span>。Digest循环会<span style="background-color: #c0c0c0;">迭代执行</span>，直到模型变得<span style="background-color: #c0c0c0;">“稳定”</span>——$evalAsync队列为空、$watch不包含任何脏数据。Digest循环之所以需要反复执行，是因为某个表达式的估算可能存在副作用——<span style="background-color: #c0c0c0;">导致其它表达式的值发生改变</span></li>
<li>$evalAsync 队列用于调度那些需要在<span style="background-color: #c0c0c0;">当执行上下文外部</span>完成的工作。例如：指令、控制器直接调用<pre class="crayon-plain-tag">$evalAsync(expr)</pre> 后，expr会在Angular操控DOM之后，浏览器渲染之前被执行</li>
<li>$watch列表由前文介绍的<pre class="crayon-plain-tag">scope.$watch()</pre> 注册的表达式组成，这些表达式在上一次迭代后可能发生变化。如果检测到变化，则对应的监听函数被执行，典型操作是更新DOM</li>
<li>一旦Digest循环迭代完毕，退出Angular执行上下文、JavaScript上下文。随后浏览器渲染视图，反映出DOM更新</li>
</ol>
<div class="blog_h2"><span class="graybg">依赖注入</span></div>
<p>Angular的Injector子系统负责组件管理：创建组件、解析依赖、注入依赖。</p>
<p>依赖注入在Angular中广泛应用，你可以在定义一个组件时，以及调用模块的<pre class="crayon-plain-tag">run</pre> 、<pre class="crayon-plain-tag">config</pre> 方法时执行依赖注入：</p>
<ol>
<li>通过工厂函数或者构造函数定义的服务、指令、过滤器、动画等组件，可以注入service、value类型的组件作为依赖<br />
<pre class="crayon-plain-tag">//通过工厂函数定义组件：
angular.module('myModule', [])
    //第二个参数如果为数组，那么除了最后一个，都是需要注入的依赖
    .factory('serviceId', ['depService', function(depService) {
      //服务工厂函数
    }])
    .directive('directiveName', ['depService', function(depService) {
      //指令工厂函数
    }])
    .filter('filterName', ['depService', function(depService) {
      //过滤器工厂函数
    }]
);</pre>
</li>
<li>通过构造函数定义的控制器，可以注入service、value类型的组件作为依赖。并且可以注入其它特殊的依赖，例如$scope：<br />
<pre class="crayon-plain-tag">someModule.controller('MyCtrl', ['$scope', 'dep1', 'dep2', function($scope, dep1, dep2) {
  $scope.aMethod = function() {
  }
}]);</pre>
</li>
<li>run方法支持一个回调函数作为入参，在回调中，你可以注入service、value、constant类型的组件</li>
<li>config方法支持一个回调函数作为入参，在回调中，你可以注入provider、constant类型的组件。run、config方法的例子如下：<br />
<pre class="crayon-plain-tag">angular.module( 'myModule', [] )
//指定在模块配置阶段执行的函数
.config( [ 'depProvider', function( depProvider ) {
    
} ] )
//指定在模块运行期间执行的函数
.run( [ 'depService', function( depService ) {
    
} ] );</pre>
</li>
</ol>
<div class="blog_h3"><span class="graybg">依赖注入的方式</span></div>
<p>有三种方式可以告知Injector，组件需要哪些依赖：</p>
<ol>
<li>推荐的方式：内联数组记法，为模块的相应方法（controller、factory等）第二个参数传入数组，其最后一个元素是工厂/构造函数，其余元素都是<span style="background-color: #c0c0c0;"><span style="background-color: #c0c0c0;">字符串形式</span></span>的依赖声明：<br />
<pre class="crayon-plain-tag">module.controller( 'HelloCtrler', [ '$scope', 'greeter', function( $scope, greeter ) {
} ] );</pre>
</li>
<li>使用$inject属性执行依赖注入：<br />
<pre class="crayon-plain-tag">var HelloCtrler = function( $scope, greeter ) {};
HelloCtrler.$inject = [ '$scope', 'greeter' ];
someModule.controller( 'HelloCtrler', HelloCtrler );</pre>
</li>
<li>隐式注入。这种方式最简单，但是不支持压缩JavaScript：<br />
<pre class="crayon-plain-tag">module.controller('HelloCtrler', function($scope, greeter) {
    //依据构造函数入参名称，自动寻找对应的依赖，不需要字符串显式说明依赖项的名称
});</pre></p>
<p> 使用<pre class="crayon-plain-tag">ng-strict-di</pre> 指令可以禁用隐式注入。手工启动Angular时，可以通过如下方式禁用：</p>
<p><pre class="crayon-plain-tag">angular.bootstrap(document, ['app'], {
  strictDi: true
});</pre>
</li>
</ol>
<div class="blog_h2"><span class="graybg">表达式</span></div>
<p>Angular表达式类似于JavaScript表达式。表达式主要用于<span style="background-color: #c0c0c0;">插值绑定</span>，例如<br /><pre class="crayon-plain-tag">&lt;span title="attr-{{ attrBinding }}"&gt;{{ textBinding }}&lt;/span&gt;</pre> ，以及<span style="background-color: #c0c0c0;">指令属性</span>，例如<br /><pre class="crayon-plain-tag">ng-click="functionExpression()"</pre> </p>
<div class="blog_h3"><span class="graybg">与JavaScript表达式的不同</span></div>
<ol>
<li>上下文：JavaScript表达式的上下文对象是Window；Angular表达式的上下文对象是一个scope对象</li>
<li>null/undefined安全性：Angular对点号导航中出现null/undefined的表达式，解析为null/undefined；JavaScript则抛出ReferenceError/TypeError。在Angular中，表达式<pre class="crayon-plain-tag">{{a.b.c}}</pre> 和<pre class="crayon-plain-tag">{{((a||{}).b||{}).c}}</pre> 是等价的</li>
<li>控制流：Angular表达式不支持流程控制语句，例如条件分支、循环、异常处理。三元操作符<pre class="crayon-plain-tag">a ? b : c</pre> 是支持的</li>
<li>函数声明：Angular表达式不支持函数声明</li>
<li>正则式直接量：Angular表达式不支持声明正则式直接量</li>
<li>创建对象：Angular表达式不支持通过new创建新对象</li>
<li>其它：Angular不支持逗号操作符、void操作符</li>
<li>过滤器：Angular支持类似于Linux Shell管道的语法，在表达式中指定过滤器，以格式化输出</li>
</ol>
<p>如果要手工估算一个Angular表达式的值，可以调用<pre class="crayon-plain-tag">$eval()</pre> 函数。</p>
<div class="blog_h3"><span class="graybg">表达式上下文</span></div>
<p>Angular在内部使用<pre class="crayon-plain-tag">$parse</pre> 服务，而不是eval()来估算表达式的值。Angular有意的禁止对window、location、document等全局对象的访问，防止引入缺陷或者降低可测试性。</p>
<p>你可以使用<pre class="crayon-plain-tag">$window</pre> 、<pre class="crayon-plain-tag">$location</pre> 等服务代替那些全局对象，这些服务支持仿冒（Mock），利于单元测试：</p>
<pre class="crayon-plain-tag">&lt;script type="text/javascript"&gt;
    angular.module( 'app', [] ).controller( 'HelloCtrler', function( $window, $scope ) {
        $scope.name = 'World';
        $scope.greet = function() {
            $window.alert( 'Hello ' + $scope.name );
        };
    } );
&lt;/script&gt;
&lt;/head&gt;
&lt;body ng-app="app" ng-controller="HelloCtrler"&gt;
    &lt;button ng-click="greet()"&gt;Greet&lt;/button&gt;
&lt;/body&gt;</pre>
<div class="blog_h3"><span class="graybg">$event</span></div>
<p>ngClick、ngFocus等指令在其值表达式的当前上下文中暴露了一个<pre class="crayon-plain-tag">$event</pre> 对象。如果你同时使用jQuery那么该对象是一个<a href="http://api.jquery.com/category/events/event-object/">jQuery事件对象</a>；否则，它是一个jqLite对象。</p>
<div class="blog_h3"><span class="graybg">一次性绑定</span></div>
<p>所谓一次性绑定是指，表达式仅仅在第一次digest时估算一次，如果其值是<span style="background-color: #c0c0c0;">非undefined</span>，则以后不再估算。使用语法<pre class="crayon-plain-tag">{{::expression}}</pre> 声明一次性绑定。</p>
<p>一次性绑定可以用于<span style="background-color: #c0c0c0;">减少资源消耗</span>，因为它不会在后续的digest循环中被重新估算。</p>
<p>注意，如果绑定表达式中包含对象直接量，那么只有<span style="background-color: #c0c0c0;">对象直接量的所有属性均非undefined</span>，才停止后续估算。</p>
<div class="blog_h2"><span class="graybg">插值绑定</span></div>
<p>插值绑定用于为HTML属性、文本提供值，或者部分值。</p>
<p>在编译阶段，Angular编译器利用<pre class="crayon-plain-tag">$interpolate</pre> 服务检查文本节点、元素属性是否包含插值标记（即双括号标记）。如果包含，则<span style="background-color: #c0c0c0;">为节点添加interpolate指令</span>，并注册watcher。</p>
<div class="blog_h3"><span class="graybg">绑定布尔值</span></div>
<p>对于值类型为布尔的HTML属性，例如disabled，浏览器可能仅仅检查属性的存在性，而不看它的值，这导致<pre class="crayon-plain-tag">disabled="false"</pre> 也会导致禁用。因此，你不能使用<pre class="crayon-plain-tag">disabled="{{isDisabled}}"</pre> 方式进行绑定。</p>
<p>Angular提供了若干特殊指令，例如<pre class="crayon-plain-tag">ng-disabled</pre> 、<pre class="crayon-plain-tag">ng-readonly</pre> 、<pre class="crayon-plain-tag">ng-selected</pre> 、<pre class="crayon-plain-tag">ng-checked</pre> 专门用于解决布尔值绑定问题。</p>
<div class="blog_h3"><span class="graybg">绑定任意属性</span></div>
<p>某些浏览器会对属性值有效性进行验证，因此使用插值绑定可能导致错误，例如：<pre class="crayon-plain-tag">&lt;svg&gt;&lt;circle cx="{{cx}}"&gt;&lt;/circle&gt;&lt;/svg&gt;</pre> 。要解决此问题，可以使用<pre class="crayon-plain-tag">ng-attr-</pre> 前缀：<pre class="crayon-plain-tag">&lt;circle ng-attr-cx="{{cx}}"&gt;&lt;/circle&gt;</pre> 。</p>
<p>对于驼峰式大小写属性，使用此前缀语法时，需要在大写字母前面加下划线，并把大写字母改为小写。例如绑定<pre class="crayon-plain-tag">viewBox</pre> 属性，需使用<pre class="crayon-plain-tag">ng-attr-view_box</pre> </p>
<div class="blog_h3"><span class="graybg">避免在其它表达式中嵌入插值绑定</span></div>
<p>应当避免这样的用法：<pre class="crayon-plain-tag">&lt;div ng-show="form{{$index}}.$invalid"&gt;&lt;/div&gt;</pre> 。这增加了标记的复杂性，不一定对所有指令生效，而且影响性能。</p>
<div class="blog_h3"><span class="graybg">$interpolate和$interpolateProvider</span></div>
<p>可以通过依赖注入使用$interpolate，它是一个函数：</p>
<pre class="crayon-plain-tag">/**
 * @param text 包含{{}}标记的字符串
 * @param mustHaveExpression 如果为true，则text不包含表达式时返回null
 * @param trustedContext 用于安全性控制
 */
function $interpolate(text, mustHaveExpression, trustedContext){}</pre>
<p>通过$interpolateProvider可以改变插值表达式相关的配置，例如：</p>
<pre class="crayon-plain-tag">module.config( function( $interpolateProvider ) {
    //修改插值表达式的起始、终止符号
    $interpolateProvider.startSymbol( '__' );
    $interpolateProvider.endSymbol( '__' );
} );</pre>
<div class="blog_h2"><span class="graybg">过滤器</span></div>
<p>在Angular中，过滤器用于格式化表达式，并显示给用户。你可以在模板、控制器、服务中使用过滤器。过滤器可能很方便的自定义，其对应的API是定义在ng模块的provider：<pre class="crayon-plain-tag">$filterProvider</pre> 。</p>
<div class="blog_h3"><span class="graybg">在模板中使用过滤器</span></div>
<p>语法格式示例：</p>
<pre class="crayon-plain-tag">{{ expression | filter }}

&lt;!-- 过滤器的结果可以传递给其它过滤器 --&gt;
{{ expression | filter1 | filter2 | ... }}

&lt;!-- 过滤器支持入参 --&gt;
{{ expression | filter:argument1:argument2:... }}
&lt;!-- 格式化数字，保留2位小数，结果为1,234.00--&gt;
{{ 1234 | number:2 }}</pre>
<div class="blog_h3"><span class="graybg">在控制器、服务和指令中使用过滤器</span></div>
<p>要在这些组件中使用过滤器，只需要注入<pre class="crayon-plain-tag">&lt;filterName&gt;Filter</pre> 即可。例如要使用number过滤器，则注入numberFilter。注入的对象是一个函数，调用时，第一个参数是被格式化的值，后续参数是过滤器入参。</p>
<div class="blog_h3"><span class="graybg">创建新的过滤器</span></div>
<p>定制过滤器很简单，只需要调用模块的<pre class="crayon-plain-tag">filter(filterName, filterFunction)</pre> 方法注册即可。filterFunction必须是一个函数，其第一个入参是被格式化的值，后续入参是过滤器参数。</p>
<p>通常filterFunction应当是“纯函数”——无状态且幂等。Angular依赖这一特征，仅在过滤器输入变化的时候，才执行过滤器。</p>
<div class="blog_h3"><span class="graybg">有状态过滤器</span></div>
<p>尽管Angular建议把过滤器函数实现为无状态的，以便Angular执行性能优化，但是你还是可以使用下面的方式把过滤器声明为有状态的：</p>
<pre class="crayon-plain-tag">angular.module( 'app', [] ).filter( 'decorate', [ 'decoration', function( decoration ) {

    function decorateFilter( input ) {...}
    decorateFilter.$stateful = true; //声明为有状态过滤器
    return decorateFilter;
} ] );</pre>
<p>有状态过滤器在每轮Digest循环中都被调用。 </p>
<div class="blog_h2"><span class="graybg">表单和表单控件</span></div>
<p>Angular包含了若干指令、CSS样式类，用于双向数据绑定和HTML表单的处理。</p>
<p>双向数据绑定的关键指令是ngModel。该指令不但提供了视图、模型之间的双向同步机制，而且提供了额外的API以增强功能。</p>
<p>下面是Angular表单的简单例子：</p>
<pre class="crayon-plain-tag">&lt;script type="text/javascript"&gt;
    angular.module( 'app', [] ).controller( 'Ctrler', [ '$scope', function( $scope ) {
        $scope.master = {};

        $scope.update = function( user ) {
            $scope.master = angular.copy( user );
        };

        $scope.reset = function() {
            $scope.user = angular.copy( $scope.master );
        };

        $scope.reset();
    } ] );
&lt;/script&gt;
&lt;/head&gt;
&lt;body ng-app="app"&gt;
    &lt;div ng-controller="Ctrler"&gt;
        &lt;form novalidate&gt; &lt;!-- 禁用浏览器原生的表单验证功能 --&gt;
            姓名：&lt;input type="text" ng-model="user.name" /&gt;&lt;br /&gt;
            &lt;!-- Angular为HTML5的表单控件类型提供了基本的验证规则，
                 如果不通过验证，表单元素将不会同步到模型上，输入错误的电子邮件可以观察到这一点 
            --&gt;
            邮件：&lt;input type="email" ng-model="user.email" /&gt;&lt;br /&gt;
            
            性别：&lt;input type="radio" ng-model="user.gender" value="male" /&gt;男
            &lt;input type="radio" ng-model="user.gender" value="female" /&gt;女&lt;br /&gt;
            
            &lt;input type="button" ng-click="reset()" value="重置" /&gt;
            &lt;input type="submit" ng-click="update(user)" value="保存" /&gt;
        &lt;/form&gt;
        &lt;pre&gt;user = {{user | json}}&lt;/pre&gt;
        &lt;pre&gt;master = {{master | json}}&lt;/pre&gt;
    &lt;/div&gt;
&lt;/body&gt;</pre>
<div class="blog_h3"><span class="graybg">表单控件相关样式类</span></div>
<p>ngModel指令添加了若干CSS样式类，用以修饰处于不同状态的表单控件：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 25%; text-align: center;">CSS样式类</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>ng-valid</td>
<td>对应模型字段有效时的样式</td>
</tr>
<tr>
<td>ng-invalid</td>
<td>对应模型字段无效时的样式</td>
</tr>
<tr>
<td>ng-valid-[key]</td>
<td>对应模型字段针对特定的验证器有效时的样式，通过$setValidity调用添加</td>
</tr>
<tr>
<td>ng-invalid-[key]</td>
<td>对应模型字段针对特定的验证器无效时的样式，通过$setValidity调用添加</td>
</tr>
<tr>
<td>ng-pristine</td>
<td>用户从未与之交互过（例如点击它）的控件的样式</td>
</tr>
<tr>
<td>ng-dirty</td>
<td>用于已经交互过的控件的样式</td>
</tr>
<tr>
<td>ng-touched</td>
<td>控件获得焦点时的样式</td>
</tr>
<tr>
<td>ng-untouched</td>
<td>控件失去焦点时的样式</td>
</tr>
<tr>
<td>ng-pending</td>
<td>当存在$asyncValidators尚未执行时的样式</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">绑定表单和控件实例的状态</span></div>
<p>在Angular中，表单对应了一个<a href="https://docs.angularjs.org/api/ng/type/form.FormController">FormController</a>的实例，而使用ngModel指令标注的表单控件则对应<a href="https://docs.angularjs.org/api/ng/type/ngModel.NgModelController">NgModelController</a>的实例。表单、表单控件的HTML属性name的值，自动作为当前scope下的属性名——此属性就是FormController/NgModelController实例的引用。</p>
<p>上述特性意味着，可以使用Angular标准的绑定原语来访问表单、控件的内部状态。我们可以利用此特性来显示验证错误信息：</p>
<ol>
<li>当用户和控件交互后（$touched状态位被设置），显示验证错误信息</li>
<li>当前用户提交表单时（$submitted状态位被设置），显示验证错误信息，即使用户没有和表单控件交互</li>
</ol>
<p>下面的代码完善了上一个示例，添加了验证错误信息：</p>
<pre class="crayon-plain-tag">&lt;script type="text/javascript"&gt;
angular.module('app', [])
.controller('Ctrler', ['$scope', function($scope) {
  $scope.master = {};

  $scope.update = function(user) {
    $scope.master = angular.copy(user);
  };

  $scope.reset = function(form) {
    if (form) {
      form.$setPristine(); //设置为从未交互状态
      form.$setUntouched(); //设置为失焦点状态
    }
    $scope.user = angular.copy($scope.master);
  };

  $scope.reset();
}]);
&lt;/script&gt;
&lt;body ng-app="app"&gt;
&lt;div ng-controller="Ctrler" &gt;
&lt;form name="form" novalidate&gt;
    姓名:
    &lt;input type="text" ng-model="user.name" name="uName" required="" /&gt;
    &lt;!-- ng-show指令用于在表达式估算结果为true的时候显示标签。如果结果为false，Angular会给元素添加.ng-hide样式 --&gt;
    &lt;span ng-show="form.$submitted || form.uName.$touched"&gt; &lt;!-- 如果表单已提交，或uName控件已经被点击过 --&gt;
      &lt;span ng-show="form.uName.$error.required"&gt;请输入你的姓名&lt;/span&gt;&lt;!-- 如果required验证器失败 --&gt;
    &lt;/span&gt;
    &lt;br/&gt;
    邮件:
    &lt;input type="email" ng-model="user.email" name="uEmail" required="" /&gt;
    &lt;span ng-show="form.$submitted || form.uEmail.$touched"&gt;
      &lt;span ng-show="form.uEmail.$error.required"&gt;请输入你的邮件地址&lt;/span&gt;&lt;!-- 如果required验证器失败 --&gt;
      &lt;span ng-show="form.uEmail.$error.email"&gt;电子邮件格式错误&lt;/span&gt;&lt;!-- 如果email验证器失败 --&gt;
    &lt;/span&gt;
    &lt;br /&gt;

    &lt;input type="button" ng-click="reset(form)" value="重置" /&gt;
    &lt;input type="submit" ng-click="update(user)" value="保存" /&gt;&lt;!-- 提交时，验证机制自动触发--&gt;
&lt;/form&gt;
  &lt;pre&gt;user = {{user | json}}&lt;/pre&gt;
  &lt;pre&gt;master = {{master | json}}&lt;/pre&gt;
&lt;/div&gt;
&lt;/body&gt;</pre>
<div class="blog_h3"><span class="graybg">模型更新/验证的触发事件</span></div>
<p>默认的，表单元素值的任何改变都会立即触发模型更新、表单验证，你可以使用ngModelOptions指令覆盖此行为。例如：<br /><pre class="crayon-plain-tag">ng-model-options="{ updateOn: 'blur' }"</pre>  <br />仅在控件失去焦点时才验证并更新。如果需要在多个事件发生时触发验证，用空格分隔事件名，例如<pre class="crayon-plain-tag">updateOn: 'default blur'</pre> 。</p>
<div class="blog_h3"><span class="graybg">模型更新/验证延迟（去抖动）</span></div>
<p>你还可以让更新/验证<span style="background-color: #c0c0c0;">延迟触发</span>，例如<pre class="crayon-plain-tag">ng-model-options="{ debounce: 500 }"</pre> 会导致500ms后触发更新/验证。这个延迟会同时应用到parsers、validators、以及$dirty、$pristine等模型标记位。</p>
<p>延迟可以和触发事件联用：</p>
<pre class="crayon-plain-tag">&lt;!-- 分别指定default、blur事件时的触发延迟 --&gt;
ng-model-options="{ updateOn: 'default blur', debounce: { default: 500, blur: 0 } }"</pre>
<div class="blog_h3"><span class="graybg">验证机制的细节</span></div>
<p>Angular为HTML5的input类型：text, number, url, email, date, radio, checkbox提供了基本的验证实现。并且提供了required, pattern, minlength, maxlength, min, max等专用于验证的指令。</p>
<p>具体的验证工作由一个个单独的验证函数（验证器）负责完成。验证函数存放在<pre class="crayon-plain-tag">ngModelController.$validators</pre> 对象中。</p>
<p>验证函数必须满足如下规则：</p>
<ol>
<li>接收modelValue、viewValue作为入参，这两个参数分别代表模型的值、用户输入的值</li>
<li>返回布尔值，表示验证是否通过</li>
</ol>
<p>验证函数在以下时机调用：</p>
<ol>
<li>当输入控件的值变化时，即<pre class="crayon-plain-tag">$setViewValue</pre> 被调用时</li>
<li>当模型发生变化时</li>
</ol>
<p>注意：验证机制在成功运行<pre class="crayon-plain-tag">$parsers</pre> 和<pre class="crayon-plain-tag">$formatters</pre> 之后才会触发。</p>
<p>Angular依据验证函数的返回值，在内部调用<pre class="crayon-plain-tag">$setValidity</pre> 设置控件的有效性。失败的验证器，以其名字作为key，存放在<pre class="crayon-plain-tag">ngModelController.$error</pre> 对象中。</p>
<p>&nbsp;</p>
<p>Angular还支持<span style="background-color: #c0c0c0;">异步验证</span>，<pre class="crayon-plain-tag">ngModelController.$asyncValidators</pre> 包含了所有异步验证器。异步验证器主要用在需要通过$http进行后台验证的场景中。异步验证函数必须返回一个<pre class="crayon-plain-tag">promise</pre> 对象，并在验证成功时resolved，失败时rejected。正在进行的异步验证，以其名字作为key，存放在<pre class="crayon-plain-tag">ngModelController.$pending</pre> 对象中。</p>
<div class="blog_h3"><span class="graybg">开发自己的验证器</span></div>
<p>下面的例子中，定义了两个指令，分别在控件的控制器上注册同步、异步的验证器：</p>
<pre class="crayon-plain-tag">&lt;script type="text/javascript"&gt;
var app = angular.module( 'app', [] );
var INTEGER_REGEXP = /^\-?\d+$/;
//定义新的指令
app.directive( 'integer', function() {
    return {
        require : 'ngModel',
        link : function( scope, elm, attrs, ctrl ) {
            //添加到验证器的集合中
            ctrl.$validators.integer = function( modelValue, viewValue ) {
                if ( ctrl.$isEmpty( modelValue ) ) {
                    return true;
                }
                if ( INTEGER_REGEXP.test( viewValue ) ) {
                    return true;
                }
                return false;
            };
        }
    };
} );

app.directive( 'username', function( $q, $timeout ) {
    return {
        require : 'ngModel',
        link : function( scope, elm, attrs, ctrl ) {
            var usernames = [ 'Jim', 'John', 'Jill', 'Jackie' ];
            //添加到异步验证器的集合中
            ctrl.$asyncValidators.username = function( modelValue, viewValue ) {
                if ( ctrl.$isEmpty( modelValue ) ) {
                    //$q是ng模块中定义的一个服务，用于辅助你异步的调用函数
                    return $q.when(); //直接返回
                }

                var def = $q.defer(); //创建一个Deferred对象，代表未来会完成的任务
                //延迟两秒执行
                $timeout( function() {
                    // 仿冒一个延迟到达的后台响应
                    if ( usernames.indexOf( modelValue ) === -1 ) {
                        def.resolve(); //解决期望的promise为指定的值
                    }
                    else {
                        def.reject(); //回绝期望的promise为指定的原因
                    }

                }, 2000 );

                return def.promise; //返回Deferred关联的Promise对象
            };
        }
    };
} );
&lt;/script&gt;
&lt;body ng-app="app"&gt;
&lt;form name="form" class="css-form" novalidate&gt;
  &lt;div&gt;
    尺寸:
    &lt;input type="number" ng-model="size" name="size" min="0" max="10" integer /&gt;{{size}}
    &lt;span ng-show="form.size.$error.integer"&gt;尺寸必须是一个整数&lt;/span&gt;
    &lt;span ng-show="form.size.$error.min || form.size.$error.max"&gt;尺寸的范围必须在0到10之间！&lt;/span&gt;
  &lt;/div&gt;
  &lt;div&gt;
    用户名:
    &lt;input type="text" ng-model="name" name="name" username /&gt;{{name}}
    &lt;span ng-show="form.name.$pending.username"&gt;正在检查用户名的可用性&lt;/span&gt;
    &lt;span ng-show="form.name.$error.username"&gt;用户名已经被占用！&lt;/span&gt;
  &lt;/div&gt;
&lt;/form&gt;</pre>
<div class="blog_h3"><span class="graybg">修改内置验证器 </span></div>
<p>由于验证器只是<pre class="crayon-plain-tag">ngModelController.$validators</pre> 中的属性，因此可以很容易的替换它们，内置验证器也不例外：</p>
<pre class="crayon-plain-tag">app.directive( 'overwriteEmail', function() {
    var EMAIL_REGEXP = /^[a-z0-9!#$%&amp;'*+/=?^_`{|}~.-]+@example\.com$/i;

    return {
        require : 'ngModel',
        restrict : '',
        link : function( scope, elm, attrs, ctrl ) {
            // 仅当控制器（即NgModelController）存在，且其验证器包含email时，才覆盖
            if ( ctrl &amp;&amp; ctrl.$validators.email ) {
                // 覆盖默认的电子邮件验证器
                ctrl.$validators.email = function( modelValue ) {
                    return ctrl.$isEmpty( modelValue ) || EMAIL_REGEXP.test( modelValue );
                };
            }
        }
    };
} );</pre>
<div class="blog_h3"><span class="graybg">使用ngMessages模块</span></div>
<p>上面基于ngShow指令来显示验证错误信息的方式，比较繁琐，Angular 1.3引入了<pre class="crayon-plain-tag">ngMessages</pre> 模块，可以简化验证信息的展示。要使用该模块，你必须下载其JS文件并声明对ngMessages模块的依赖。</p>
<pre class="crayon-plain-tag">&lt;form name="formx"&gt;
    &lt;!-- 
        默认每次显示一条消息，ng-messages-multiple则允许显示多条信息
        ng-messages-includ可以把子节点的HTML存放到额外的模板中
    --&gt;
    &lt;div class="error" ng-messages="formx.name.$error" ng-messages-multiple&gt;
        &lt;div ng-message="required"&gt;当验证器required失败时显示的信息&lt;/div&gt;
        &lt;div ng-message="minlength"&gt;当验证器minlength失败时显示的信息&lt;/div&gt;
        &lt;div ng-message="maxlength"&gt;当验证器maxlength失败时显示的信息&lt;/div&gt;
    &lt;/div&gt;
&lt;/form&gt;</pre>
<div class="blog_h3"><span class="graybg">实现新的表单控件 </span></div>
<p>Angular实现了所有基本的HTML表单控件（input、select、textarea），这些控件能够应付大部分的需求。如果你需要更多的灵活性，可以通过指令来编写自己的表单控件。</p>
<p>要使自定义表单控件能够与ngModel联用，并支持双向数据绑定，你必须：</p>
<ol>
<li>为控件对应的ngModelController实现<pre class="crayon-plain-tag">$render</pre> 方法。该方法负责显示传递给<pre class="crayon-plain-tag">NgModelController.$formatters</pre> 的数据</li>
<li>每当用户和控件交互时，或者模型需要被更新时，调用<pre class="crayon-plain-tag">$setViewValue</pre> 方法</li>
</ol>
<p>下面是一个例子：</p>
<pre class="crayon-plain-tag">&lt;script type="text/javascript"&gt;
    //声明一个指令，其名称借用HTML5的contenteditable属性
    angular.module( 'app', [] ).directive( 'contenteditable', function() {
        return {
            require : 'ngModel',
            link : function( scope, elm, attrs, ctrl ) {
                // 失去焦点时，把视图的值设置到模型
                elm.on( 'blur', function() {
                    ctrl.$setViewValue( elm.html() );
                } );

                // 渲染函数：把模型反应到视图
                ctrl.$render = function() {
                    elm.html( ctrl.$viewValue );
                };

                // 从视图中初始化模型的值
                ctrl.$setViewValue( elm.html() );
            }
        };
    } );
&lt;/script&gt;
&lt;style type="text/css"&gt;
div[contentEditable] {
    border: 1px solid #B0B0B0; 
    cursor: pointer;
    width: 150px;
}
&lt;/style&gt;
&lt;/head&gt;
&lt;body ng-app="app"&gt;
    &lt;!-- contenteditable属性，表示内容可编辑的HTML元素，同时也是我们定义的指令 --&gt;
    &lt;div contenteditable="true" ng-model="content"&gt;Hello&lt;/div&gt;
    &lt;pre&gt;model = {{content}}&lt;/pre&gt;
&lt;/body&gt;</pre>
<div class="blog_h2"><span class="graybg">指令</span></div>
<p>概括的说，指令是DOM元素（属性、元素、注释、CSS类）上的标记。指令通知Angular的HTML编译器（<pre class="crayon-plain-tag">$compile</pre> ）为DOM元素<span style="background-color: #c0c0c0;">添加特定的行为</span>，或者对<span style="background-color: #c0c0c0;">DOM子树进行转换</span>。</p>
<p>Angular自带了很多指令，你也可以创建自己的指令。在启动（Bootstrap）阶段，Angular会遍历整个DOM树，依据DOM<span style="background-color: #c0c0c0;">匹配的指令</span>进行相应的处理。</p>
<p>指令在模块上通过API：<pre class="crayon-plain-tag">directive(normalizedName, factoryFunc)</pre> 进行注册。工厂函数factoryFunc的返回值是一个对象，该对象告知$compile当指令匹配时，需要执行什么样的行为。工厂函数只会在Angular编译器匹配指令到元素时执行一次，你可以在工厂函数中包含初始化逻辑。</p>
<p>选择自己的指令名称时，应当注意避免使用ng前缀，不要选择未来可能成为HTML内置标签名的单词，比避免冲突。</p>
<p>指令可以具有属性，当指令通过DOM元素标记时，指令的属性体现为DOM属性。</p>
<div class="blog_h3"><span class="graybg">指令匹配规则</span></div>
<p>当指令是DOM元素声明的一部分时，Angular认为<span style="background-color: #c0c0c0;">指令匹配元素</span>：</p>
<pre class="crayon-plain-tag">&lt;!-- input元素匹配ngModel指令 --&gt;
&lt;input ng-model="foo"&gt;
&lt;input data-ng-model="foo"&gt;
&lt;input x-ng_model="foo"&gt;

&lt;!-- person元素匹配person指令 --&gt;
&lt;person&gt;{{name}}&lt;/person&gt;</pre>
<div class="blog_h3"><span class="graybg">指令名称规范化</span></div>
<p>指令作为JavaScript对象，我们通常使用它的<span style="background-color: #c0c0c0;">正规化（normalized）名称</span>：大小写敏感的、驼峰式大小写形式。例如： <pre class="crayon-plain-tag">ngModel</pre> 。</p>
<p>但是HTML是大小写不敏感的，因此在DOM中我们使用纯小写来引用指令，并且通常使用短横线分隔风格。例如：<pre class="crayon-plain-tag">ng-model</pre> 。</p>
<p>DOM中的指令名称，到正式的指令名称的转换规则如下：</p>
<ol>
<li>去除<pre class="crayon-plain-tag">x- </pre> 或者<pre class="crayon-plain-tag">data- </pre> 前缀</li>
<li>把<pre class="crayon-plain-tag">:</pre> 、<pre class="crayon-plain-tag">-</pre> 、<pre class="crayon-plain-tag">_</pre> 分隔的单词，改为驼峰式大小写</li>
</ol>
<div class="blog_h3"><span class="graybg">指令的出现位置</span></div>
<p>$compile可以基于<span style="background-color: #c0c0c0;">DOM元素名称、属性、CSS类名、甚至注释</span>来匹配指令，Angular提供的指令支持这全部四种节点。下面的例子示例了在模板的不同位置使用demoDir指令的方式：</p>
<pre class="crayon-plain-tag">&lt;!-- 元素名称 --&gt;
&lt;demo-dir&gt;&lt;/demo-div&gt;

&lt;!-- 元素属性 --&gt;
&lt;span demo-dir="exp"&gt;&lt;/span&gt;

&lt;!-- 注释--&gt;
&lt;!-- directive: demo-dir exp --&gt;

&lt;!-- CSS类--&gt;
&lt;span class="demo-dir: exp;"&gt;&lt;/span&gt;</pre>
<p>最好只通过元素名称、 属性来引用指令，这样Angular更容易判断<span style="background-color: #c0c0c0;">给定的元素匹配什么指令</span>。</p>
<p>注释中携带指令，主要用于指令需要跨越多个兄弟DOM元素的情况。在Angular1.2中引入的<pre class="crayon-plain-tag">ng-repeat-start</pre> 和<pre class="crayon-plain-tag">ng-repeat-end</pre> 可以代替之。</p>
<p>可以使用restrict选项限制指令可以在哪些节点上引用：</p>
<pre class="crayon-plain-tag">app.directive('myCustomer', function() {
    return {
        restrict: 'AE'
        //默认值：AE表示可以在属性、元素名上引用指令
        //A：属性；E：元素；C：CSS类名
    };
});</pre>
<p>决定通过元素名称还是属性来引用指令时，参考以下意见： </p>
<ol>
<li>当为你的模板创建DSL（领域特定语言）时，考虑使用元素名称</li>
<li>当<span style="background-color: #c0c0c0;">装饰既有</span>HTML元素，以增加新功能时，考虑使用属性</li>
</ol>
<div class="blog_h3"><span class="graybg">使用指令进行模板展开</span></div>
<p>如果你有一段反复使用到的HTML片段，你可以通过指令将其封装起来，以便重用，减少重复代码。显示一个简单的例子：</p>
<pre class="crayon-plain-tag">&lt;script type="text/javascript"&gt;
    angular.module( 'app', [] ).controller( 'Ctrler', [ '$scope', function( $scope ) {
        $scope.user = {
            name : 'Alex Wong',
            address : 'No.20 Waterside Pearl Plaza'
        };
    } ] ).directive( 'userInfo', function() {
        //指令工厂函数返回一个对象，对象的每一个属性称为“选项”（option）
        return {
            template : 'Name: {{user.name}} &lt;br/&gt;Address: {{user.address}}'
        };
    } );
&lt;/script&gt;
&lt;body ng-app="app" ng-controller="Ctrler"&gt;
    &lt;span user-info&gt;&lt;/span&gt;
&lt;/body&gt;</pre>
<p>上面的例子中，我们通过<pre class="crayon-plain-tag">template</pre> 选项指定了模板的内容，如果模板内容很复杂，最好将其存放在<span style="background-color: #c0c0c0;">独立的HTML文件</span>中，通过<pre class="crayon-plain-tag">templateUrl</pre> 选项加载： </p>
<pre class="crayon-plain-tag">app.directive( 'userInfo', function() {
    return {
        templateUrl : '../userInfo.html'
    };
} );</pre>
<p>templateUrl选项还可以指定一个<span style="background-color: #c0c0c0;">返回URL或者HTML模板内容的函数</span>，该函数接受两个参数：指令对应的HTML元素、一个关联到该HTML元素的attr对象。注意在该函数中你<span style="background-color: #c0c0c0;">不能对Scope进行任何访问</span>，因为模板加载时Scope尚未初始化。下面的例子示例了templateUrl的这种用法：</p>
<pre class="crayon-plain-tag">&lt;script type="text/javascript"&gt;    
    app.directive( 'userInfo', function() {
        templateUrl: function(elem, attr){
            return 'user-info-'+attr.type+'.html';
          }
    } );
&lt;/script&gt;
&lt;body ng-app="app" ng-controller="Ctrler"&gt;
  &lt;div user-info type="name"&gt;&lt;/div&gt;
  &lt;div user-info type="address"&gt;&lt;/div&gt;
&lt;/body&gt;</pre>
<div class="blog_h3"><span class="graybg">隔离指令的作用域</span></div>
<p>上面的例子有个严重的缺陷，注意模板内容：<pre class="crayon-plain-tag">'Name: {{user.name}} &lt;br/&gt;Address: {{user.address}}'</pre> ，它假设了当前Scope存在user属性，这将导致指令<span style="background-color: #c0c0c0;">难以重用</span>。</p>
<p>Angular允许为指令指定<pre class="crayon-plain-tag">scope</pre> 选项，允许为指令创建一个隔离作用域（ isolate scope）。在scope对象内部，你可以<span style="background-color: #c0c0c0;">使用&amp;、@、=等限定符，指明隔离作用域和外部作用域的映射关系（绑定策略）</span>。</p>
<p>=用于进行双向绑定。建立关联后，在隔离作用域修改变量，该变量在外部作用域中映射的变量同时改变，反之依然。</p>
<p>下面使用隔离作用域，对上一个例子进行改造：</p>
<pre class="crayon-plain-tag">&lt;script type="text/javascript"&gt;
    angular.module( 'app', [] ).controller( 'Ctrler', function( $scope ) {
        //在当前Scope下定义两个属性
        $scope.alex = { name : 'Alex Wong', address : 'No.20 Waterside Pearl Plaza' };
        $scope.meng = { name : 'Meng Lee',  address : 'No.11 Jinxiu Homeland' };
    } ).directive( 'userInfo', function() {
        return {
            restrict : 'E',
            //隔离作用域，创建自己的scope
            scope : {
                //=uInfo表示通过HTML属性u-info来指定隔离Scope中info对应外部Scope的哪个属性
                info : '=uInfo'  // '='等价于'=info'。该属性值必须使用正规化的驼峰式大小写
            },
            //模板文本或HTML文件，使用指令的隔离作用域
            template : 'Name: {{info.name}} Address: {{info.address}}'
        };
    } );
&lt;/script&gt;
&lt;body ng-app="app" ng-controller="Ctrler"&gt;
    &lt;!-- info的值，对应当前Scope下的属性名 --&gt;
    &lt;user-info u-info="alex"&gt;&lt;/user-info&gt;&lt;br/&gt;&lt;user-info u-info="meng"&gt;&lt;/user-info&gt;
&lt;/body&gt;</pre>
<p>一旦指定了scope选项，那么外部作用域的任何属性，在指令的template中<span style="background-color: #c0c0c0;">默认都不可用</span>，除非在scope选项中声明。 </p>
<div class="blog_h3"><span class="graybg">从隔离作用域传递变量到外部作用域</span></div>
<p><span style="background-color: #c0c0c0;">&amp;允许指令的隔离作用域向外部作用域单向传递变量</span>，<span style="background-color: #c0c0c0;">用于估算定义在指令属性中的表达式的值</span>：</p>
<pre class="crayon-plain-tag">&lt;script type="text/javascript"&gt;
    angular.module( 'app', [] ).controller( 'Ctrler', function( $scope, $timeout ) {
        $scope.name = 'Alex';
        $scope.message = '';
        $scope.hideDialog = function( message ) {
            $scope.message = message;
            $scope.dialogIsHidden = true;
        };
    } ).directive( 'myDialog', function() {
        return {
            scope : {
                //隔离作用域的close属性，由指令匹配元素的on-close属性指定
                //&amp;绑定，允许指令在原始的作用域下执行任意表达式估算，而不是像=绑定那样哪些仅仅执行属性映射 
                'close' : '&amp;onClose' 
            },
            template : 
            '&lt;div class="alert"&gt; ' +
            //虽然close在隔离作用域下执行，但是onClose指向的表达式在原始作用域$scope下估算
            //使用&amp;绑定，可以通过一个映射，传递解析表达式需要的上下文变量，例如这里传递了一个message变量   
            '     &lt;a href class="close" ng-click="close({message: \'Closed\'})"&gt;&amp;times;&lt;/a&gt; ' +
            ' &lt;/div&gt;' 
        };
    } );
&lt;/script&gt;
&lt;body ng-app="app" ng-controller="Ctrler"&gt;
    {{message}}
    &lt;my-dialog ng-hide="dialogIsHidden" on-close="hideDialog(message)"&gt;&lt;/my-dialog&gt;
&lt;/body&gt;</pre>
<div class="blog_h3"><span class="graybg">在指令内部绑定外部作用域</span></div>
<p>设置指令的<pre class="crayon-plain-tag">transclude</pre> 选项为true，则指令内部的任意子节点，访问到的是<span style="background-color: #c0c0c0;">指令外部（Outside）作用域</span>，而非指令内部作用域。指令的子节点会自动附加为模板中标记了<pre class="crayon-plain-tag">ng-transclude</pre> 元素的内部。注意下面的例子：</p>
<pre class="crayon-plain-tag">&lt;script type="text/javascript"&gt;
    angular.module( 'app', [] ).controller( 'Ctrler', [ '$scope', function( $scope ) {
        $scope.name = 'Alex Wong';
    } ] ).directive( 'transcludeDirective', function() {
        return {
            restrict : 'AE',
            transclude : true,
            scope : {}, //创建该指令自己的作用域
            link : function( scope, element ) { 
                //这里的Scope引用新创建的隔离作用域，而非Ctrler创建的作用域
                scope.name = 'Meng Lee'; 
            },
            //子模板中的绑定：
            //如果存在隔离作用域，则绑定到隔离作用域中的属性，当然你可以把隔离作用域映射到外部作用域
            //如果不存在隔离作用域，则自动绑定到外部作用域
            //子模板中的ng-transclude元素，不应有任何内容，会被指令实例的内容所覆盖
            //每出现一个ng-transclude，就把指令实例的内容合并为子元素
            template : '&lt;div&gt;{{name}}&lt;/div&gt;&lt;div ng-transclude&gt;Content will be removed&lt;/div&gt;&lt;p ng-transclude&gt;&lt;/p&gt;'
        };
    } );
&lt;/script&gt;
&lt;body ng-app="app" ng-controller="Ctrler"&gt;
    &lt;!-- 指令实例子节点中的绑定，默认访问的是指令内部作用域，而使用了transclude后，反而访问指令外部的作用域 --&gt;
    &lt;transclude-directive&gt;&lt;div&gt;My name is {{name}}&lt;/div&gt;&lt;/transclude-directive&gt;
&lt;/body&gt;</pre>
<p>其生成的HTML内容如下： </p>
<pre class="crayon-plain-tag">&lt;body ng-app="app" ng-controller="Ctrler" class="ng-scope"&gt;
    &lt;transclude-directive class="ng-isolate-scope"&gt;
        &lt;div class="ng-binding"&gt;Meng Lee&lt;/div&gt; &lt;!-- 绑定到隔离作用域 --&gt;
        &lt;!-- 下面两个则绑定到外部作用域--&gt;
        &lt;div ng-transclude=""&gt;
            &lt;!-- 如果transclude-directive指令内部没有div元素，仅仅是文本节点，那么Angular自动创建一个span包裹文本节点--&gt;
            &lt;div class="ng-binding ng-scope"&gt;My name is Alex Wong&lt;/div&gt;
        &lt;/div&gt;
        &lt;p ng-transclude=""&gt;
            &lt;div class="ng-binding ng-scope"&gt;My name is Alex Wong&lt;/div&gt;
        &lt;/p&gt;
    &lt;/transclude-directive&gt;
&lt;/body&gt;</pre>
<p>Angular的这种特性，可以让指令包裹<span style="background-color: #c0c0c0;">任意</span>一段可以访问外部作用域的<span style="background-color: #c0c0c0;">HTML内容</span>，并通过<pre class="crayon-plain-tag">ng-transclude</pre> 将这段HTM<span style="background-color: #c0c0c0;">L嵌入到子模板内部（也就是说，允许通用的模板wrap任意的内容）</span>，并且与此同时，允许子模板使用隔离的作用域。<br /> </p>
<p>从指令使用者的角度来说，这种特性是合理的：</p>
<ol>
<li>指令使用者并不知道隔离作用域的细节，不应该让他使用隔离作用域</li>
<li>子模板工作在隔离的作用域中，可以增强可复用性——不需要对“外部作用域”中存在哪些变量做假设。这使得子模板使用隔离作用域是必要的</li>
<li>而用户在使用指令时，会很自然的认为自己在使用“外部作用域”</li>
</ol>
<p>这种指令开发者、使用者视角的差异，导致了transclude机制的出现。</p>
<div class="blog_h3"><span class="graybg">通过指令操控DOM</span></div>
<p>如果需要让指令能够操控DOM、注册DOM监听器，通常需要指定<pre class="crayon-plain-tag">link</pre> 选项。link选项的值是一个函数，其签名如下：</p>
<pre class="crayon-plain-tag">/**
 * @param scope 一个Angular作用域对象，如果指令创建了隔离作用域，该参数是此隔离作用域，否则是上级作用域
 * @param element jqLite元素对象，当前指令匹配的元素
 * @param attrs 规范化的元素属性名到其值的映射
 * @param controller 指令所在的控制器实例。如果指令本身创建了自己的控制器，则该参数指向这个新的控制器
 * @param transcludeFn 
 */
function link( scope, element, attrs, controller, transcludeFn ) {
}</pre>
<p>下面的例子中，我们创建一个指令，它依据用户指定的格式，每秒更新当前时间： </p>
<pre class="crayon-plain-tag">&lt;script type="text/javascript"&gt;
    angular.module( 'app', [] ).controller( 'Ctrler', function( $scope ) {
        $scope.format = 'yyyy-mm-dd HH:mm:ss';
    } ).directive( 'currentTime', function( $interval, dateFilter ) { //注入一个服务、一个过滤器作为依赖
        function link( scope, element, attrs ) {
            var format, timeoutId; //下面几个闭包的共享状态

            function updateTime() {
                element.text( dateFilter( new Date(), format ) ); //format可以使用scope.format代替
            }
            //对attrs.currentTime指定的表达式——format进行监控，也就是监控当前作用域下的format属性
            scope.$watch( attrs.currentTime, function( value ) { //等价于scope.$watch('format',...)
                format = value;
                //一旦发生变化，则触发一次更新操作
                updateTime();
            } );
            //开发新指令时，一定要注意执行必要的销毁操作
            //元素被销毁时的钩子，当由Angular编译器导出的DOM节点被销毁时，$destroy事件被触发
            //类似的，作用域被销毁时，也触发这一事件，你可以 scope.$on('$destroy', ...)
            element.on( '$destroy', function() {
                $interval.cancel( timeoutId ); //取消定时器
            } );
            //你也可以使用element.on添加若干DOM事件监听器

            // 开始循环处理，把句柄保存为共享闭包变量
            timeoutId = $interval( function() {
                updateTime();
            }, 1000 );
        }
        return {
            link : link
        };
    } );
&lt;/script&gt;
&lt;body ng-app="app" ng-controller="Ctrler"&gt;
    &lt;!-- 
        format仅仅是5个字母的文本，还是另有其义，完全取决于指令如何解释它
        下面的两个指令都把它作为表达式看待
    --&gt;
    时间格式：&lt;input ng-model="format"&gt; &lt;br/&gt;
    当前时间：&lt;span current-time="format"&gt;&lt;/span&gt;
&lt;/body&gt;</pre>
<div class="blog_h3"><span class="graybg">指令间的通信</span></div>
<p>template、templateUrl相当于指令的View，Angular还允许给指令添加<pre class="crayon-plain-tag">controller</pre> 选项， 这样指令的模板就附带一个控制器了。</p>
<p>下面是一个页签的例子，注意父子指令之间的通信：</p>
<pre class="crayon-plain-tag">&lt;script type="text/javascript"&gt;
    angular.module( 'app', [] ).controller('Ctrler',function($scope){
        $scope.title1 = 'Title1';
    }).directive( 'myTabs', function() {
        return {
            transclude : true,
            scope : {},
            //指令可以使用该选项，声明其模板关联的控制器，就像ngController关联控制器到模板一样 
            controller : [ '$scope', function( $scope ) {
                var panes = $scope.panes = [];
                $scope.select = function( pane ) {
                    angular.forEach( panes, function( pane ) {
                        pane.selected = false;
                    } );
                    pane.selected = true;
                };

                this.addPane = function( pane ) {
                    if ( panes.length === 0 ) {
                        $scope.select( pane );
                    }
                    panes.push( pane );
                };
            } ],
            template : 
            ' &lt;div&gt;                                                                    ' +
            '     &lt;span ng-repeat="pane in panes" style="margin-right:5px"&gt;            ' +
            '       &lt;a href="" ng-click="select(pane)"&gt;{{pane.title}}&lt;/a&gt;              ' +
            '     &lt;/span&gt;                                                              ' +
            //指令的内容，全部纳入到下面的div内部 
            '   &lt;div ng-transclude&gt;&lt;/div&gt;                                              ' +
            ' &lt;/div&gt;                                                                   '
        };
    } ).directive( 'myPane', function() {
        return {
            //指令的require选项，表示当前指令要求某个控制器的存在。如果找不到，$compile会抛出错误
            //前缀^表示，指令在其上级元素上寻找目标控制器。如果没有该前缀，仅在指令自己的元素上寻找 
            require : '^myTabs', 
            restrict : 'E',
            transclude : true,
            scope : {
                title : '@'  // 即@title，把指令title属性的值传入隔离作用域
            },
            //注意第四个参数tabsCtrl，它就是require来的控制器。该参数可以和require同时指定为数组
            link : function( scope, element, attrs, tabsCtrl ) {
                tabsCtrl.addPane( scope );
            },
            template : '&lt;div style="margin-top: 24px" ng-show="selected" ng-transclude&gt;&lt;/div&gt;'
        };
    } );
    
&lt;/script&gt;
&lt;body ng-app="app" ng-controller="Ctrler"&gt;
    &lt;my-tabs&gt;
        &lt;my-pane title="{{title1}}"&gt;
            面板一内容
        &lt;/my-pane&gt;
        &lt;my-pane title="面板二"&gt;
            面板二内容
        &lt;/my-pane&gt;
    &lt;/my-tabs&gt;
&lt;/body&gt;</pre>
<p>该例子使用到了<span style="background-color: #c0c0c0;">@限定符， 它允许从外部作用域单向的传递值到隔离作用域</span>，传递的媒介仍然是指令属性，支持<pre class="crayon-plain-tag">attr="text"</pre> 和<pre class="crayon-plain-tag">attr="text{{expr}}"</pre> 两种形式。</p>
<div class="blog_h3"><span class="graybg">指令选项列表</span></div>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 25%; text-align: center;">选项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>restrict</td>
<td>指令在DOM中可以何种形式被声明，支持E、A、C、M，可以混合使用</td>
</tr>
<tr>
<td>priority</td>
<td>优先级。同一元素如果匹配多个指令，那么<span style="background-color: #c0c0c0;">优先级数值越大的指令，越先执行</span>。大部分指令忽略此参数<br />ngRepeat在内置指令中具有最高的优先级</td>
</tr>
<tr>
<td>terminal</td>
<td>默认值false，停止运行当前元素上比本指令优先级低的指令</td>
</tr>
<tr>
<td>template</td>
<td>指令使用的模板</td>
</tr>
<tr>
<td>templateUrl</td>
<td>指令使用的模板URL</td>
</tr>
<tr>
<td>replace</td>
<td>默认值false，如果设置为true，模板将会<span style="background-color: #c0c0c0;">替换指令匹配的元素</span>，而不是作为其子元素（默认值）</td>
</tr>
<tr>
<td>scope</td>
<td>可以设置为true或者一个对象，默认false。<br />如果设置为true，则从父Scope继承并创建一个新的作用域<br />如果设置为对象，则创建隔离作用域，可以通过多种绑定策略在在隔离作用域和外部作用域之间进行映射或数据交换</td>
</tr>
<tr>
<td>transclude</td>
<td>允许指令内部嵌入任意HTML代码，这些代码访问外部作用域</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">动画</span></div>
<p>从1.3版本开始Angular为ngRepeat、ngSwitch、ngView等指令提供了<span style="background-color: #c0c0c0;">动画钩子</span>。</p>
<p>Angular中的动画<span style="background-color: #c0c0c0;">完全基于CSS样式类</span>，只要你为HTML元素添加了CSS类，就可以应用动画：</p>
<pre class="crayon-plain-tag">&lt;style&gt;
&lt;!--
.sample-show-hide {
    padding: 10px;
    border: 1px dashed black;
    background: #C0C0C0;
    width: 100px;
    /* 这里声明CSS变换规则 */
    -webkit-transition: all linear 0.5s;
    transition: all linear 0.5s;
}
/* 这里指定了隐藏时的样式，每当Angular隐藏div时，会自动添加ng-hide样式 */
.sample-show-hide.ng-hide {
    opacity: 0;
}
--&gt;
&lt;/style&gt;
&lt;script type="text/javascript" src="angular.js"&gt;&lt;/script&gt;
&lt;script type="text/javascript" src="angular-animate.js"&gt;&lt;/script&gt; &lt;!-- 必须引入额外的JS库 --&gt;
&lt;script type="text/javascript"&gt;
    angular.module('app', ['ngAnimate']); //要使用动画，必须依赖ngAnimate模块 
&lt;/script&gt;
&lt;/head&gt;
&lt;body ng-app="app"&gt;&lt;!-- 也可以直接使用ngAnimate模块 --&gt;
    &lt;div ng-init="checked=true"&gt;
        &lt;input type="checkbox" ng-model="checked" style="float: left; margin-right: 10px;"&gt;隐藏/显示
        &lt;div class="sample-show-hide" ng-show="checked" style="clear: both;"&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/body&gt;</pre>
<div class="blog_h3"><span class="graybg">ngClass动画钩子</span></div>
<p>Angular可以监控通过ngClass添加、移除CSS样式类的动作，并执行对应的动画：</p>
<pre class="crayon-plain-tag">&lt;style&gt;
&lt;!--
.css-class-add, .css-class-remove {
  -webkit-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 5s;
  -moz-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 5s;
  -o-transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 5s;
  transition:all cubic-bezier(0.250, 0.460, 0.450, 0.940) 5s;
}
/* 添加样式类后，最终需要变成大红字 */
.css-class, 
/* 在添加样式类的过程中，*-add、*-add-active类自动添加。它们的动画结果也必须时大红字，以便和css-class一致  */
.css-class-add.css-class-add-active { 
  color: #FF0055;
  font-size:2em;
}

.css-class-remove.css-class-remove-active { /* 类似，变换结果是默认元素样式  */
  font-size:1em;
  color: #000000;
}
--&gt;
&lt;/style&gt;
&lt;p&gt;
    &lt;input type="button" value="设置" ng-click="myCssVar='css-class'"&gt;
    &lt;input type="button" value="清空" ng-click="myCssVar=''"&gt;
    &lt;br&gt;
    &lt;span ng-class="myCssVar"&gt;CSS动画文本&lt;/span&gt;
&lt;/p&gt;</pre>
<div class="blog_h3"><span class="graybg">支持动画的指令列表</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">指令</td>
<td style="text-align: center;">支持的动画</td>
</tr>
</thead>
<tbody>
<tr>
<td>ngRepeat</td>
<td>进入、离开、移动</td>
</tr>
<tr>
<td>ngView</td>
<td>进入、离开</td>
</tr>
<tr>
<td>ngInclude</td>
<td>进入、离开</td>
</tr>
<tr>
<td>ngSwitch</td>
<td>进入、离开</td>
</tr>
<tr>
<td>ngIf</td>
<td>进入、离开</td>
</tr>
<tr>
<td>ngClass</td>
<td>添加、移除</td>
</tr>
<tr>
<td>ngShow / ngHide</td>
<td>添加、移除</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">在自定义指令中使用动画</span></div>
<p>可以调用注入的<pre class="crayon-plain-tag">$animate</pre> 服务，并执行动画：</p>
<pre class="crayon-plain-tag">module.directive( 'directive', function( $animate ) {
    return function( scope, element, attrs ) {
        element.on( 'click', function() {
            if ( element.hasClass( 'clicked' ) ) {
                $animate.removeClass( element, 'clicked' );
            }
            else {
                $animate.addClass( element, 'clicked' );
            }
        } );
    };
});</pre>
<div class="blog_h2"><span class="graybg">HTML编译器 </span></div>
<p>指令可以扩展既有HTML元素或者属性的行为，甚至创建具有新行为的HTML或属性。这样，在利用到HTML这一<span style="background-color: #c0c0c0;">“声明式”语言的优势</span>的基础上，我们可以对它进行<span style="background-color: #c0c0c0;">无限扩展</span>。</p>
<p>Angular的 HTML编译器，本质上是一个Angular服务，它的关键工作就是遍历整个DOM树，处理各种各样的指令。编译过程分为两个阶段：</p>
<ol>
<li>编译（Compile）：遍历DOM树，收集到所有指令。本阶段的产出是一个链接（Linking）函数。在此阶段，每个指令的compile函数被调用，任何可以跨指令实例的操作都应该放在compile中，而不是link中，以增强性能</li>
<li>链接（Link）:通过把<span style="background-color: #c0c0c0;">指令与特定Scope联合</span>，来产生一个<span style="background-color: #c0c0c0;">活动的视图</span>——Scope上发生的任何变化，都会立即反应到视图上；用户与视图的任何交互，也会立即反应到Scope的模型字段上。在此阶段，每个指令的link函数被调用</li>
</ol>
<p>这种编译、链接分离的设计，有助于提高性能。例如ngRepeat指令需要为循环中的每一个条目复制DOM元素一次，它只需要复制编译后的模板，而链接多次（编译一次）。</p>
<div class="blog_h3"><span class="graybg">指令如何被编译</span></div>
<ol>
<li>$compile遍历DOM，并匹配指令。如果一个指令匹配到元素，它会被加到元素的指令列表中，一个元素可以匹配多个指令</li>
<li>当元素匹配的所有指令都被识别出来后，$compile依据指令的priority对它们进行排序</li>
<li>每个指令的compile函数被执行，该函数可以修改DOM结构，compile函数返回一个link函数</li>
<li>每个指令返回的link函数被串联为组合的LINK函数。LINK依次调用link</li>
<li>$compile调用LINK，让Scope与每个指令链接。在这一步，$compile<span style="background-color: #c0c0c0;">会注册元素的监听器</span>、<span style="background-color: #c0c0c0;">设置$watch表达式</span></li>
</ol>
<p>链接的结果是一个活动视图，具有双向绑定特性。下面的伪代码可以帮助理解编译过程：</p>
<pre class="crayon-plain-tag">var $compile = ...; // 被注入的编译器
var scope = ...;
var parent = ...; // 编译后的模板的父元素

var html = '&lt;div ng-bind="exp"&gt;&lt;/div&gt;'; //模板

// 第一步：解析HTML文本为DOM元素
var template = angular.element(html);

// 第二步：编译模板。在此指令可以修改DOM结构，但是很少有指令具有compile函数
var linkFn = $compile(template);

// 第三步：链接编译后的模板到Scope。克隆一份编译后的副本，与scope链接
var element = linkFn(scope);

// 第四步：添加链接后的活动视图到父元素
parent.appendChild(element);</pre>
<div class="blog_h2"><span class="graybg"><a id="provider"></a>提供者（Providers）</span></div>
<p>Angular应用程序启动时，会创建一个Injector服务，该服务负责创建各种对象（包括来自ng模块的、来自应用主模块的、来自直接/间接依赖模块的），并把他们组织在一起（依赖注入）。Injector创建的对象可以分为两类：</p>
<ol>
<li>普通服务（Services ）：其API由开发者依据业务需要设计</li>
<li>特殊对象（Specialized objects）：用于扩展Angular框架，遵从Angular框架的API规范。特殊对象包括：控制器、指令、过滤器、动画</li>
</ol>
<p>在Angular中，你可以向Injector注册<span style="background-color: #c0c0c0;">“Recipe”（菜谱）</span>，来告知Injector<span style="background-color: #c0c0c0;">如何创建</span>上述两类对象。Recipe包含两个要素：</p>
<ol>
<li>该Recipe要创建的对象的标识符</li>
<li>关于如何创建对象的说明</li>
</ol>
<p>Recipe分为5种类型，要注册这些Recipe，需要分别调用Angular模块的五种API：</p>
<ol>
<li>Provider：这是最通用 的Recipe，其余四种，本质上是基于Provider的语法糖。Provider只是一个实现了<pre class="crayon-plain-tag">$get</pre> 方法的JavaScript对象，这个$get是此Provider所创建对象的工厂函数。Angular内置了很多Provider，供你在配置阶段修改相应Angular服务的行为。只有当你需要<span style="background-color: #c0c0c0;">在配置阶段（应用尚未启动）时暴露API</span>，才需要使用Provider：<br />
<pre class="crayon-plain-tag">var app = angular.module( 'app', [] );
//注册提供者 
app.provider( 'accountServiceProvider', function() {
    this.debugMode = false;
    this.$get = [ 'dao', function accountServiceFactory( dao ) {
        return this.debugMode ? new DebugAccountService() : new AccountService( dao );
    } ]
} );
//在应用程序配置阶段，修改提供者的配置 
app.config( function( accountServiceProvider ) {
    accountServiceProvider.debugMode = true;
} );</pre>
</li>
<li>Value：提供一个简单的“值”，可以注入到其它组件中：<br />
<pre class="crayon-plain-tag">var app = angular.module('app', []);
app.value('accountService', new AccountService());
app.controller('Ctrler', function (accountService) { this.accountService= accountService; }); //使用服务</pre>
</li>
<li>Factory：能够使用其它组件（依赖注入）、支持初始化、延迟初始化，比Value灵活的多。该类型的Recipe给出一个函数，调用的返回值就是要创建的服务的实例。注意Angular中所有服务都是单例的：<br />
<pre class="crayon-plain-tag">app.factory('accountService', function AccountServiceFactory() {
    return new AccountService(); //单例。根据业务规则，这里可能有复杂的代码
});</pre>
</li>
<li>Service：与Factory类似，但是通过调用<span style="background-color: #c0c0c0;">既有的构造函数</span>创建服务实例：<br />
<pre class="crayon-plain-tag">function AccountService(dao){
    this.dao = dao;
    ...
}
//第二个参数数组：前面的元素都是依赖，最后是构造函数
app.service('accountService',["accountDao", AccountService]);</pre>
</li>
<li>Constant：类似于Value，但是可以在Config阶段使用，不需要依赖或者初始化的简单对象：<br />
<pre class="crayon-plain-tag">app.constant('INTERFACE_VER', '1.0');</pre>
</li>
</ol>
<p>由于实质上所有Recipe都是Provider，因此Injector执行组件创建、依赖注入的唯一信息来源就是Provider。</p>
<div class="blog_h2"><span class="graybg">路由</span></div>
<p>在Angular中，路由功能用于管理应用程序的<span style="background-color: #c0c0c0;">多个视图（Multiple views）</span>，并在这些视图之间切换。路由功能位于<pre class="crayon-plain-tag">ngRoute</pre> 模块中，该模块和Angular核心分离，定义在独立的文件<pre class="crayon-plain-tag">angular-route.js</pre> 中。</p>
<div class="blog_h3"><span class="graybg">$location服务</span></div>
<p>该服务和路由功能密切相关，它提供了修改URL路径和处理各种形式导航的能力。该服务没有刷新整个页面的能力，可以使用<pre class="crayon-plain-tag">$window.location</pre> 代替。</p>
<p>$location服务提供以下接口：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 20%; text-align: center;">函数</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>path()</td>
<td>获取页面当前的路径，或者跳转到目标URL：<br />
<pre class="crayon-plain-tag">$location.path(); // 返回当前路径
$location.path('/'); // 把路径修改为'/'路由</pre></p>
<p> 该函数直接和HTML5的历史API交互，所以用户可以点击浏览器后退按钮，返回上一个视图</p>
</td>
</tr>
<tr>
<td>replace()</td>
<td>可以禁止把URL添加到历史记录中：<br />
<pre class="crayon-plain-tag">$location.path('/home');
$location.replace();
// 或者
$location.path('/home').replace();</pre>
</td>
</tr>
<tr>
<td>absUrl()</td>
<td>获取编码后的完整URL</td>
</tr>
<tr>
<td>hash()</td>
<td>获取URL中的hash片段</td>
</tr>
<tr>
<td>host()</td>
<td>获取URL中的主机</td>
</tr>
<tr>
<td>port()</td>
<td>获取URL中的端口号</td>
</tr>
<tr>
<td>protocol()</td>
<td>获取URL中的协议</td>
</tr>
<tr>
<td>search()</td>
<td>获取或者修改URL中的查询串：<br />
<pre class="crayon-plain-tag">// 用对象设置查询
$location.search({name: 'alex', username: 'auser'});
// 用字符串设置查询
$location.search('name=alex&amp;username=auser');</pre>
</td>
</tr>
<tr>
<td>url()</td>
<td>获取或者设置当前页面的URL：<br />
<pre class="crayon-plain-tag">$location.url('/home?name=alex#hashthing');</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">布局模板（Layout Template）</span></div>
<p>在多视图应用中，所谓布局模板是指<span style="background-color: #c0c0c0;">包含了所有视图的公共内容</span>的模板。在<span style="background-color: #c0c0c0;">当前路由（Route）改变</span>时，相应的<span style="background-color: #c0c0c0;">模板片断（partial templates，或叫视图模板）</span>会<span style="background-color: #c0c0c0;">被包含</span>到布局模板中。模板片断代表了“当前视图”。</p>
<p>路由的规则通过<pre class="crayon-plain-tag">$routeProvider</pre> 来配置，这是<pre class="crayon-plain-tag">$route</pre> 服务的提供者。通过$route服务，可以轻易的把<span style="background-color: #c0c0c0;">控制器、模板、浏览器当前URL</span>绑定在一起，这样你就可以实现深链（deep linking）—— 将应用视图关联到URL，并利用浏览器的浏览历史。</p>
<div class="blog_h3"><span class="graybg">ngView指令</span></div>
<p>$rouet服务通常与<pre class="crayon-plain-tag">ngView</pre> 指令联用，ngView的职责是把<span style="background-color: #c0c0c0;">当前路由关联的视图模板包含到布局模板</span>中。该指令会创建新的作用域，视图模板使用这一新作用域。该指令优先级为1000，并且禁止同一元素上低优先级的指令。</p>
<p>ngView遵守以下规则：</p>
<ol>
<li>每当发生<pre class="crayon-plain-tag">$routeChangeSuccess</pre> 事件，视图都会更新</li>
<li>如果当前路由关联到某个模板：
<ol>
<li>创建一个新的作用域</li>
<li>移除上一个视图，同时上一个作用域也会被清除</li>
<li>将新的作用域同当前模板关联在一起</li>
<li>把路由定义中对应的控制器（如果有的话）和当前作用域关联起来</li>
<li>触发<pre class="crayon-plain-tag">$viewContentLoaded</pre> 事件</li>
<li>如果提供了<pre class="crayon-plain-tag">onload</pre> 属性，调用该属性所指定的函数</li>
</ol>
</li>
</ol>
<div class="blog_h3"><span class="graybg">编写路由规则</span></div>
<p>调用$routeProvider的<pre class="crayon-plain-tag">when(path, route)</pre> 函数，可以编写路由规则。其中path匹配<pre class="crayon-plain-tag">$location.path</pre> ，route是一个对象，可以提供以下属性：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>controller</td>
<td>和新的作用域、视图模板关联的控制器。如果指定一个函数，则从该函数创建控制器；如果指定一个字符串，则从模块中查找既有控制器</td>
</tr>
<tr>
<td>template</td>
<td rowspan="2">模块或者模板URL，Angular会把模板渲染到ng-view指定的元素内部</td>
</tr>
<tr>
<td>templateUrl</td>
</tr>
<tr>
<td>resolve</td>
<td>为控制器注入一系列依赖，如果某个依赖是promise，它会<span style="background-color: #c0c0c0;">在$routeChangeSuccess触发前被resolve为有效值</span>。举例：<br />
<pre class="crayon-plain-tag">resolve : {
    'data' : [ '$http', function( $http ) {
        return $http.get( '/api' ).then( function success( resp ) {
            return response.data;
        }, function error( reason ) {
            return false;
        } );
    } ]
}</pre>
</td>
</tr>
<tr>
<td>redirectTo</td>
<td>执行重定向，可以指定字符串或者函数，函数签名为：<br />
<pre class="crayon-plain-tag">/**
 * @param route 从当前路径中提取出的路由参数
 * @param path 当前路径
 * @param search 当前URL中的查询串
 */
function redirectTo(route,path,search);</pre>
</td>
</tr>
<tr>
<td>reloadOnSearch</td>
<td>默认true，当<pre class="crayon-plain-tag">location.search()</pre> 变化时重新加载路由；如果设置为false，URL查询串那部分的变化不会引起重新加载路由。<br />该属性对路由嵌套和原地分页等需求非常有用</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">路由模式</span></div>
<p>选择不同的路由模式，则URL的风格也不同：</p>
<ol>
<li>标签模式（hashbang mode）：<pre class="crayon-plain-tag">$location</pre> 服务默认使用的模式，URL路径以<pre class="crayon-plain-tag">#</pre> 号开头，例如<pre class="crayon-plain-tag">http://gmem.cc/#!/index</pre> 。标签模式不需要重写<pre class="crayon-plain-tag">&lt;a&gt;</pre> 元素的href属性，也不需要任何服务器端的支持。下面的代码手工配置当前应用为标签模式：<br />
<pre class="crayon-plain-tag">angular.module( 'app', [ 'ngRoute' ] ).config( function( $locationProvider ) {
    $locationProvider.html5Mode( false );
    $locationProvider.hashPrefix( '!' );
} );</pre>
</li>
<li>HTML5 模式：更加RESTful的URL风格，例如<pre class="crayon-plain-tag">http://gmem.cc/index</pre> 。如果浏览器不支持HTML5，Angular会自动退化为标签模式</li>
</ol>
<p>HTML5 模式需要后端服务器支持URL重写，使用HTML5模式时，永远不要使用相对路径，否则可能导致Angular无法正确处理路由。</p>
<div class="blog_h3"><span class="graybg">路由事件</span></div>
<p><pre class="crayon-plain-tag">$route</pre> 服务在路由过程中的每个阶段都会触发不同的事件，可以为这些不同的路由事件设置监听器并做出响应：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 25%; text-align: center;">事件</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>$routeChangeStart</td>
<td>在路由变化之前会发布该事件，监听函数的规格：<br />
<pre class="crayon-plain-tag">/**
 * @param evt 当前事件对象
 * @param next 将要导航到的下一个URL
 * @param current 路由变化前的URL
 */
function listener( evt, next, current ) {}</pre>
</td>
</tr>
<tr>
<td>$routeChangeSuccess</td>
<td>在路由的依赖被成功加载后发布该事件，监听函数的规格：<br />
<pre class="crayon-plain-tag">/**
 * @param evt 当前事件对象
 * @param next  用户当前所处的路由
 * @param previous 上一个路由（如果当前是第一个路由，则为undefined）
 */
function listener( evt, next, previous ) {}</pre>
</td>
</tr>
<tr>
<td>$routeChangeError</td>
<td>在任何一个promise被拒绝或者失败时发布该事件，监听函数的规格：<br />
<pre class="crayon-plain-tag">/**
 * @param current 当前路由的信息
 * @param previous 上一个路由的信息
 * @param rejection 被拒绝的promise的错误信息
 */
function listener(current, previous, rejection ) {}</pre>
</td>
</tr>
<tr>
<td>$routeUpdate</td>
<td>在reloadOnSearch属性被设置为false的情况下，重新使用某个控制器的实例时，发布该事件</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">应用示例</span></div>
<p>这里是一个用户管理的简单示例：</p>
<pre class="crayon-plain-tag">&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;meta charset="UTF-8"&gt;
&lt;title&gt;User Management App&lt;/title&gt;
&lt;script type="text/javascript" src="angular.js"&gt;&lt;/script&gt;
&lt;script type="text/javascript" src="angular-route.js"&gt;&lt;/script&gt;
&lt;script type="text/javascript" src="index.js"&gt;&lt;/script&gt;
&lt;style&gt;
&lt;!--
body {
    font-family: Consolas;
}

table {
    table-layout: fixed;
    border: 0;
    border-spacing: 0;
    border-collapse: collapse;
}

td {
    background-color: #E5E5E5;
    border: 1px solid #fff;
    color: #333333;
    min-width: 100px;
    padding: 5px 0 0 5px;
}

thead td,.thead {
    text-align: center;
    font-weight: bold;
}
--&gt;
&lt;/style&gt;
&lt;/head&gt;
&lt;body ng-app="app"&gt;
    &lt;div&gt;User Management&lt;/div&gt;
    &lt;div ng-view&gt;&lt;/div&gt;
&lt;/body&gt;
&lt;/html&gt;</pre><br />
<pre class="crayon-plain-tag">&lt;table&gt;
    &lt;thead&gt;
        &lt;tr&gt;
            &lt;td&gt;ID&lt;/td&gt;
            &lt;td&gt;Name&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/thead&gt;
    &lt;tbody&gt;
        &lt;tr ng-repeat="user in users"&gt;
            &lt;td&gt;{{user.id}}&lt;/td&gt;
            &lt;td&gt;&lt;a href="#/users/{{user.id}}"&gt;{{user.name}}&lt;/a&gt;&lt;/td&gt;
        &lt;/tr&gt;
    &lt;/tbody&gt;
&lt;/table&gt;</pre><br />
<pre class="crayon-plain-tag">&lt;table&gt;
    &lt;tr&gt;
        &lt;td class="thead"&gt;ID&lt;/td&gt;
        &lt;td&gt;{{user.id}}&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td class="thead"&gt;Name&lt;/td&gt;
        &lt;td&gt;{{user.name}}&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td class="thead"&gt;Age&lt;/td&gt;
        &lt;td&gt;{{user.age}}&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td class="thead"&gt;Address&lt;/td&gt;
        &lt;td&gt;{{user.addr}}&lt;/td&gt;
    &lt;/tr&gt;
    &lt;tr&gt;
        &lt;td class="thead"&gt;E-mail&lt;/td&gt;
        &lt;td&gt;{{user.email}}&lt;/td&gt;
    &lt;/tr&gt;
&lt;/table&gt;
&lt;div&gt;
    &lt;a href="#/users"&gt;Back&lt;/a&gt;
&lt;/div&gt;</pre><br />
<pre class="crayon-plain-tag">'use strict';
angular.module( 'app', [ 'ngRoute' ] ).config( function( $routeProvider ) {
    // 需要在配置阶段，对路由提供者进行配置
    // when用于定义路由，即URL和模板+控制器的对应关系
    $routeProvider.when( '/users', { 
        // URL的HASH部分为#/users时
        templateUrl : 'partials/users.html',
        controller : 'UsersCtrler'
    } ).when( '/users/:id', {
        // URL的HASH部分为#/users/:id时，:开头表示URL中的路径变量
        templateUrl : 'partials/user-detail.html',
        controller : 'UsersDetailCtrler'
    } ).otherwise( {
        // 不匹配已声明的路由规则时的行为
        redirectTo : '/users'
    } );
} ).value( 'usersData', [ { // 简单的静态数据
    id : 10000,
    name : 'Alex Wong',
    age : 29,
    addr : 'No.20 Waterside Pearl Plaza',
    email : 'alex@gmem.cc'
}, {
    id : 10002,
    name : 'Meng Lee',
    age : 26,
    addr : 'No.11 Jinxiu Homeland',
    email : 'meng@gmem.cc'
}, {
    id : 10003,
    name : 'Wen Jun',
    age : 26,
    addr : 'No.10 YanHuang International',
    email : 'wenjun@gmem.cc'
} ] ).controller( 'UsersCtrler', function( $scope, usersData ) {
    // 每次切换到视图，控制器都创建一次，并执行这里的代码
    $scope.users = usersData;
} ).controller( 'UsersDetailCtrler', function( $scope, $routeParams, usersData ) {
    for ( var i = 0; i &lt; usersData.length; i++ ) {
        var user = usersData[i];
        if ( user.id == $routeParams.id ) $scope.user = user;
    }
} );
// 注意我们把所有Angular组件都定义在这一个模块中了，如果应用规模变大，应当注意职责分离</pre>
<div class="blog_h2"><span class="graybg">$http服务</span></div>
<div class="blog_h3"><span class="graybg">配置选项</span></div>
<p>该服务对浏览器原生的XMLHttpRequest进行了简单封装。它实质上是一个函数，其返回值是promise对象，其入参是一个配置对象，你可以使用以下配置项：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 25%; text-align: center;">配置项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>method</td>
<td>HTTP方法，支持：GET、DELETE、HEAD、JSONP、POST、PUT</td>
</tr>
<tr>
<td>url</td>
<td>绝对或相对的URL，请求的目标</td>
</tr>
<tr>
<td>params</td>
<td>查询参数，字符串或者对象，例如：<br />
<pre class="crayon-plain-tag">$http({
    params: {'name': 'alex'}
})</pre>
</td>
</tr>
<tr>
<td>data</td>
<td>请求体，字符串或者对象，通常在发送POST请求时使用<br />从1.3开始，支持发送二进制数据：<br />
<pre class="crayon-plain-tag">var blob = new Blob(['Hello World'], {type: 'text/plain'});
$http({ data: blob });</pre>
</td>
</tr>
<tr>
<td>headers</td>
<td>列表，每一个元素都是一个函数，它会返回代表随请求发送的HTTP头</td>
</tr>
<tr>
<td>transformRequest</td>
<td>一个函数或函数数组，用来对HTTP请求的请求体和头信息进行转换：<pre class="crayon-plain-tag">function(data,headers) {}</pre> </td>
</tr>
<tr>
<td>transformResponse</td>
<td>一个函数或函数数组，用来对HTTP响应的响应体和头信息进行转换：<pre class="crayon-plain-tag">function(data,headers) {}</pre> </td>
</tr>
<tr>
<td>cache</td>
<td>布尔型或缓存对象。如果设置为true，则Angular使用默认的$http缓存来对GET请求进行缓存。如果设置为<pre class="crayon-plain-tag">$cacheFactory</pre> 的实例，那么该实例被用于对GET请求进行缓存</td>
</tr>
<tr>
<td>timeout</td>
<td>如果设置为数值，那么请求延迟指定毫秒后再发送<br />如果设置为promise，那么此promise被resolvehou请求中止</td>
</tr>
<tr>
<td>withCredentials</td>
<td>默认的CORS请求不会发送cookie，withCredential为true则添加请求头Access-Control-Allow-Credentials，这样目标域的Cookie被包含在请求中</td>
</tr>
<tr>
<td>responseType</td>
<td>
<p>设置XMLHttpRequestResponseType属性，支持：blob、document、json、text等值</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">用法示例</span></div>
<p>基本的用法：</p>
<pre class="crayon-plain-tag">$http( {
    //配置选项
    method : 'GET',
    url : '/api/users.json'
} ).success( function( data, status, headers, config ) {
    // 当相应准备就绪时调用
} ).error( function( data, status, headers, config ) {
    // 当响应以错误状态返回时调用
} );</pre>
<p>或者：</p>
<pre class="crayon-plain-tag">// $http方法返回一个HttpPromise对象，我们可以调用它的then()、success()和error()方法
var promise = $http( {
    method : 'GET',
    url : '/api/users.json'
} );
//then会接收到完整的响应对象，而而success()和error()则会对响应进行解析
promise.then( function( resp ) {
    // resp是一个响应对象
}, function( resp ) {
    // 带有错误信息的resp
} );</pre>
<div class="blog_h3"><span class="graybg">响应对象</span></div>
<p>Angular传递给HttpPromise的<pre class="crayon-plain-tag">then()</pre> 方法的响应对象，包含以下属性：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 20%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>data</td>
<td>转换过后的响应体，如果定义了transformResponse的话</td>
</tr>
<tr>
<td>status</td>
<td>HTTP状态码</td>
</tr>
<tr>
<td>headers</td>
<td>函数，接收请求头名称作为入参，返回请求头的值</td>
</tr>
<tr>
<td>config</td>
<td>原始请求的配置选项</td>
</tr>
<tr>
<td>statusText</td>
<td>HTTP状态文本</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">快捷方法</span></div>
<p>$http服务提供了一些快捷方法：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 20%; text-align: center;">方法</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>get()</td>
<td>发送GET请求：<pre class="crayon-plain-tag">$http.get(url,config);</pre> ，返回HttpPromise</td>
</tr>
<tr>
<td>delete()</td>
<td>发送DELETE请求：<pre class="crayon-plain-tag">$http.delete(url,config);</pre> ，返回HttpPromise</td>
</tr>
<tr>
<td>head()</td>
<td>发送HEAD请求：<pre class="crayon-plain-tag">$http.head(url,config); </pre> ，返回HttpPromise</td>
</tr>
<tr>
<td>jsonp()</td>
<td>发送JsonP请求：<pre class="crayon-plain-tag">$http.jsonp(url,config); </pre> ，返回HttpPromise。url必须包含指定回调为<pre class="crayon-plain-tag">JSON_CALLBACK</pre> ，例如<pre class="crayon-plain-tag">/users.json?callback=JSON_CALLBACK</pre> </td>
</tr>
<tr>
<td>post()</td>
<td>发送POST请求：<pre class="crayon-plain-tag">$http.post(url,data,config);</pre> ，返回HttpPromise</td>
</tr>
<tr>
<td>put()</td>
<td>发送PUT请求：<pre class="crayon-plain-tag">$http.put(url,data,config);</pre> ，返回HttpPromise</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">缓存</span></div>
<p>Angular支持缓存GET请求的结果，可以设置配置选项cache来使用缓存，或者进行全局的缓存配置：</p>
<pre class="crayon-plain-tag">angular.module( 'app', [] ).config( function( $httpProvider, $cacheFactory ) {
    $httpProvider.defaults.cache = $cacheFactory( 'lru', { //基于LRU算法
        capacity : 20 //允许的缓存条目
    } );
} );</pre>
<div class="blog_h3"><span class="graybg">拦截器</span></div>
<p>拦截器用于为HTTP请求处理提供<span style="background-color: #c0c0c0;">全局性的功能</span>，例如身份验证、错误处理等。拦截器在在<span style="background-color: #c0c0c0;">请求发送前、响应到达后</span>进行执行特定逻辑。</p>
<p>每个拦截器本质上是一个服务，该服务提供四个函数：</p>
<pre class="crayon-plain-tag">angular.module( 'app', [] ).factory( 'testInterceptor', function( $q ) {
    var interceptor = {
        /**
         * 拦截待发送的请求，可以修改甚至替换配置对象
         * 
         * @param config
         *            $http的配置选项
         * @returns 配置选项或者配置选项的promise
         */
        'request' : function( config ) {
            return config; // 或者 $q.when(config)
        },
        /**
         * 拦截响应内容，可以修改甚至替换响应
         * 
         * @param response
         *            响应
         * @returns 响应或者响应的promise
         */
        'response' : function( response ) {
            return response; // 或者 $q.when(config);
        },
        /**
         * Angular在上一个请求拦截器抛出错误，或者promise被reject时调用此拦截器
         */
        'requestError' : function( rejection ) {
        },
        /**
         * AngularJS在上一个响应拦截器抛出错误，或者promise被reject时调用此拦截器
         */
        'responseError' : function( rejection ) {
        }
    };
    return interceptor;
} );</pre>
<p>可以通过<pre class="crayon-plain-tag">$httpProvider.interceptors</pre> 添加新的拦截器：</p>
<pre class="crayon-plain-tag">angular.module( 'app', [] ).config( function( $httpProvider ) {
    $httpProvider.interceptors.push( 'testInterceptor' );
} );</pre>
<div class="blog_h3"><span class="graybg">默认请求头</span></div>
<p>默认的请求头保存在<pre class="crayon-plain-tag">$httpProvider.defaults.headers</pre>对象中，其中common存储通用默认请求头，post、put则分别存储POST、PUT请求的默认请求头。可以在配置阶段修改这些对象。</p>
<div class="blog_h2"><span class="graybg">跨域XHR请求</span></div>
<div class="blog_h3"><span class="graybg">同源策略</span></div>
<p>一个“域”由<span style="background-color: #c0c0c0;">协议（HTTP/HTTPS）、域名（IP）、端口</span>唯一确定。默认的，浏览器限制一个<span style="background-color: #c0c0c0;">域中的脚本只能读写本域的资源</span>，此即同源策略。</p>
<p>同源策略限制了Angular应用向其它后端服务器发送XHR请求的能力，如果要和第三方服务器交互，必须绕过同源策略。</p>
<div class="blog_h3"><span class="graybg">JsonP</span></div>
<p>尽管浏览器不允许向异域发送Ajax请求，但是它允许加载异域的脚本文件。利用这一特性，我们可以传递一个回调函数的名称给异域，而异域依据此回调函数动态生成脚本，把需要交换的数据作为入参，传递给回调。例如，脚本：</p>
<pre class="crayon-plain-tag">&lt;script type="text/javascript" src="http://3rd.com/userdata?callback=JSON_CALLBACK"&gt;&lt;/script&gt;</pre>
<p> 的内容可以是：</p>
<pre class="crayon-plain-tag">JSON_CALLBACK({
    name : 'Alex'
});</pre>
<p>这样，脚本加载完毕后，JSON_CALLBACK函数自动被调用。这就实现了与异域进行数据交换的能力 。</p>
<p>Angular的$http服务已经内置了JsonP的支持。</p>
<div class="blog_h3"><span class="graybg">CORS</span></div>
<p>近年来，W3C制定了跨域资源共享（CORS）来通过标准的方式取代JSONP。</p>
<p>要通过Angular使用CORS，必须配置$httpProvider：</p>
<pre class="crayon-plain-tag">angular.module( 'app', [] ).config( function( $httpProvider ) {
    $httpProvider.defaults.useXDomain = true; //添加此设置
    delete $httpProvider.defaults.headers.common['X-Requested-With'];
} );</pre>
<p>异域的后端服务器也必须进行适当配置，并返回特殊的响应头： </p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%; text-align: center;">响应头</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>Access-Control-Allow-Origin</td>
<td>该响应头控制哪些域可以向本服务器发送跨站请求，设置为*表示任何域都可以</td>
</tr>
<tr>
<td>Access-Control-Allow-Credentials</td>
<td>默认情况下，CORS请求不发送Cookie。如果异域服务器启用该响应头，那么你可以启用$http服务的withCredentials选项，这样Cookie会一并发送给异域</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">服务器代理</span></div>
<p>也可以在服务器端放置代理，所有对异域的请求都通过服务器转发，这样就不存在异域问题了。</p>
<div class="blog_h2"><span class="graybg">身份验证</span></div>
<p>进行服务器端身份验证的两种基本方式为：</p>
<ol>
<li>基于Cookie的身份验证：这是最为广泛接受的方式，每个请求都附带一个Cookie，该Cookie关联了服务器端的一个用户会话</li>
<li>基于令牌（Token）的身份验证：这是最近流行的新方式，每个请求附带一个“令牌”，令牌可以存放在请求头中，令牌由服务器授予客户端</li>
</ol>
<p>基于令牌的身份验证，具有如下优势：</p>
<ol>
<li>CORS友好：基于Cookie的身份验证在跨域时运作的不是很好，而基于令牌的验证，可以让你方便的向任何服务器发送Ajax请求，因为身份信息就存放在请求头中</li>
<li>无状态：Cookie机制要求服务器端维持一个用户会话对象，并把Cookie和用户会话关联。而令牌不需要服务器端维持任何状态</li>
<li>解耦：不需要被某种验证机制/框架捆绑，客户端不需要理解服务器如何生成令牌</li>
<li>移动设备友好：在iOS、Android等Native移动平台上工作时，Cookie可能正常使用</li>
</ol>
<p>本节主要介绍AngularJS的令牌身份验证。</p>
<div class="blog_h3"><span class="graybg">服务器端实现</span></div>
<p>当未验证或者身份验证失败时，应当返回<pre class="crayon-plain-tag">401</pre> 状态码。当身份验证成功后，应当返回令牌，例如<pre class="crayon-plain-tag">{ token: '令牌字符串' }</pre> 。</p>
<div class="blog_h3"><span class="graybg">客户端登录</span></div>
<pre class="crayon-plain-tag">//假设当前作用域的user对象存放用户名、密码等信息
$http.post( '/authenticate', $scope.user ).success( function( data, status, headers, config ) {
    // 存放到sessionStorage，浏览器关闭后实现，类似还有localStorage等地方可以存储
    $window.sessionStorage.token = data.token;
} ).error( function( data, status, headers, config ) {
    // 验证失败，删除令牌
    delete $window.sessionStorage.token;
} );</pre>
<div class="blog_h3"><span class="graybg">客户端拦截器</span></div>
<p>我们可以通过Angular的拦截器机制，来把令牌自动附加到每个请求中去，并对错误的响应进行处理：</p>
<pre class="crayon-plain-tag">app.factory( 'authInterceptor', function( $rootScope, $q, $window ) {
    return {
        request : function( config ) {
            config.headers = config.headers || {};
            if ( $window.sessionStorage.token ) {
                // 自动设置Authorization请求头
                config.headers.Authorization = $window.sessionStorage.token;
            }
            return config;
        },
        response : function( rejection ) {
            // 错误处理
            switch ( rejection.status ) {
                case 401:
                    if ( rejection.config.url !== 'api/login' ) {
                        // 如果当前不是在登录页面，广播事件。监听器可以导航应用到登录视图
                        $rootScope.$broadcast( 'auth:loginRequired' );
                    }
                    break;
                case 403:
                    $rootScope.$broadcast( 'auth:forbidden' );
                    break;
                case 404:
                    $rootScope.$broadcast( 'page:notFound' );
                    break;
                case 500:
                    $rootScope.$broadcast( 'server:error' );
                    break;
            }
            return $q.reject( rejection );
        }
    };
} );

app.config( function( $httpProvider ) {
    $httpProvider.interceptors.push( 'authInterceptor' );
} );</pre>
<div class="blog_h2"><span class="graybg">Promise </span></div>
<p>promise是一个对象，它代表了一个函数调用<span style="background-color: #c0c0c0;">最终</span>可能的<span style="background-color: #c0c0c0;">返回值</span>或者抛出的<span style="background-color: #c0c0c0;">异常</span>。promise用于异步的处理数据，你可以将其看做远程对象的代理，promise和Java5引入的Future功能类似。</p>
<p>JavaScript中传统的异步处理方式是回调函数，但是，如果<span style="background-color: #c0c0c0;">回调函数中又涉及其它异步处理</span>，会让代码结构失控：</p>
<pre class="crayon-plain-tag">keyService.get( config, { // 异步请求1
    success : function( key ) {
        roomService.open( key, { // 异步请求2
            success : function( room ) {
                roomService.cleanUp( room, {// 异步请求3
                    success : function() {
                    }
                } );
            },
            failure : function() {
                // 失控，你需要编写多个错误处理代码
            }
        } );
    },
    failure : function() {
    }
} );</pre>
<p>当使用promise来设计API时，上述代码看起来像这样：</p>
<pre class="crayon-plain-tag">angular.module( 'app', [] ).factory( 'keyService', function( $q, $timeout ) {
    return {
        get : function( id ) {
            var deferred = $q.defer(); // 代表一个延迟完成的任务
            console.log( 'Finding key by id ' + id );
            $timeout( function() {
                // 可以在未来某个时刻，延迟的resolve或者reject promise
                if ( id &lt; 100 ) deferred.resolve( 'KEY' + id );
                else deferred.reject( 'Invalid key' );
            }, 1000 );
            return deferred.promise; // 必须返回一个promise对象，才能支持promise链
        }
    };
} ).factory( 'roomService', function( $q, $timeout ) {
    return {
        open : function( key ) {
            var deferred = $q.defer();
            console.log( 'Opening room with key ' + key );
            $timeout( function() {
                if ( key == 'KEY0' ) deferred.resolve( 'ROOM0' );
                else deferred.reject( 'Do not Disturb' );
            }, 3000 );
            return deferred.promise;
        },
        cleanUp : function( room ) {
            var deferred = $q.defer();
            console.log( 'Cleaning up room ' + room );
            $timeout( function() {
                deferred.resolve( room + ' CLEANED UP' );
            }, 6000 );
            return deferred.promise;
        },
    };
} ).run( function( keyService, roomService, $q ) {
    //promise chain
    keyService.get( 99 )

    .then( function( value ) {
        return roomService.open( value ); // 为了支持后续的链式调用，必须返回promise
    }, function( reason ) {
        // 记录原因，再创建一个拒绝的promise，这类似于记录日志然后重新抛出异常
        console.log( 'Failed to get key : ' + reason );
        // 明确创建一个因为reason而拒绝的promise
        // 返回如下的一个promise，否则链条上后续的任务还会继续执行
        return $q.reject( reason );
    } )

    .then( function( value ) {
        return roomService.cleanUp( value );
    }, $q.reject ) // 可以直接把第二个入参设置为$q.reject，这类似于直接重新抛出异常

    .then( function( value ) {
        console.log( 'Result: ' + value );
    }, function( reason ) {
        console.log( 'Failed to clean up room : ' + reason );
        return $q.reject( reason );
    } );
} );</pre>
<p>可以看到，使用promise的代码（run块）更加简洁，可读性更高，逃离了回调的地狱。</p>
<div class="blog_h3"><span class="graybg">创建和操控Promise</span></div>
<p>某些Angular API，调用后返回Promise对象，例如<pre class="crayon-plain-tag">$http</pre> 、<pre class="crayon-plain-tag">$timeout</pre> 。</p>
<p>注入<pre class="crayon-plain-tag">$q</pre> 服务，可以在任何代码中使用Promise：</p>
<pre class="crayon-plain-tag">angular.module( 'app', [] ).run( function( $q ) {
    var d = $q.defer(); //生成一个Deferred对象
    return d.promise;
} );</pre>
<p>一个Promise要么被resolve，要么被reject：</p>
<ol>
<li>resolve被调用时，带有一个履行值</li>
<li>reject被调用时要带一个拒绝原因</li>
</ol>
<p>可以调用<pre class="crayon-plain-tag">Deferred</pre> 提供的接口，操控promise：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">方法</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>resolve( value )</td>
<td>解析deferred关联的promise</td>
</tr>
<tr>
<td>reject( reason )</td>
<td>拒绝deferred关联的promise：<br />
<pre class="crayon-plain-tag">d.reject( reason );  
//等价于deferred.resolve($q.reject(reason));</pre>
</td>
</tr>
<tr>
<td>notify( value )</td>
<td>报告执行状态，例如执行的进度，在运行长时间执行的任务时有用</td>
</tr>
</tbody>
</table>
<p>promise对象本身提供了以下几个接口：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 25%; text-align: center;">方法</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>then()</td>
<td>签名：<pre class="crayon-plain-tag">then(successFn, errFn, notifyFn)</pre> <br />无论promise被resolve还是reject，当结果可用后，Angular会立即调用该函数指定的successFn、errFn回调<br />在promise结果可用前，notifyFn可能被调用0-N次</td>
</tr>
<tr>
<td>catch(errFn)</td>
<td>等价于<pre class="crayon-plain-tag">promise.then(null, errorCallback)</pre> </td>
</tr>
<tr>
<td>finally(callback)</td>
<td>可以当promise结果可用时，执行一定的逻辑，该逻辑不论resolve还是reject，都需要执行——例如资源清理代码</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">$q服务 </span></div>
<p>该服务用于异步的执行函数，提供了对deferred、promise的支持。$q提供以下接口：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">方法</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>defer()</td>
<td>创建一个Deferred对象，表示在未来会完成的一个任务</td>
</tr>
<tr>
<td>reject(reason)</td>
<td>创建一个已经被reason拒绝的promise对象，可以用于在promise chain中传递rejection，这和异常传播很类似</td>
</tr>
<tr>
<td>when</td>
<td>签名：<pre class="crayon-plain-tag">when(value, [successCallback], [errorCallback], [progressCallback]);</pre> <br />可以<span style="background-color: #c0c0c0;">装饰一个简单对象、或者第三方then-able的promise</span>为$q的promise实现。<br />resolve是该函数的别名</td>
</tr>
<tr>
<td>all(promises)</td>
<td>联合多个promise，只有所有成员resolved，该联合才resolved</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">与Apache Cordova的集成 </span></div>
<p>应当在Cordova的deviceready事件之后来启动Angular应用：</p>
<pre class="crayon-plain-tag">var onDeviceReady = function() {
    angular.bootstrap( document, [ 'app' ] );
};
document.addEventListener( 'deviceready', onDeviceReady );</pre>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/webapp-dev-with-angularjs">基于AngularJS开发Web应用</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/webapp-dev-with-angularjs/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Framework7学习笔记（一）：基础</title>
		<link>https://blog.gmem.cc/framework7-study-note-1</link>
		<comments>https://blog.gmem.cc/framework7-study-note-1#comments</comments>
		<pubDate>Sun, 27 Dec 2015 11:09:32 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[HTML]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Mobile]]></category>
		<category><![CDATA[F7]]></category>
		<category><![CDATA[学习笔记]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=10369</guid>
		<description><![CDATA[<p>Framework7简介 Framework7（以下简称F7）是用于开发具有iOS/Android原生外观的混合移动应用/Web应用的开源框架，同时它也可以作为快速原型工具使用。F7完全基于Web技术——HTML、CSS、JavaScript。 F7简单易用，我们可以仅仅编写常规的HTML，不需要开发定制的标签（Angular那样），或者通过JavaScript来描述我们的页面（ExtJS那样）。 F7原先专注于iOS平台，现在也支持Google的Material design specification——Android平台的UI设计规范。 F7内置了大量的组件，例如模态窗口、侧面板、表格、表单、列表等等，使用这些组件时，几乎不需要编写JavaScript。 F7的外观易于定制，所有样式被分离在较小的[crayon-69e29a4145432655121267-i/] 文件中。我们可以基于Less.js修改和编译样式。 F7还包括更多的特性： 原生的滚动效果（Native Scrolling） 不依赖任何第三方框架 高性能的，基于CSS的动画 支持缓存、历史、预加载（Preloading） 支持无限制的多视图，不需要任何JavaScript代码即可控制视图 简洁的JavaScript API DOM7：内置的DOM操控库，风格类似于jQuery 起步：第一个F7应用 基本布局：index.html 该页面声明了应用程序基本的布局： [crayon-69e29a4145437252644311/] <a class="read-more" href="https://blog.gmem.cc/framework7-study-note-1">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/framework7-study-note-1">Framework7学习笔记（一）：基础</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">Framework7简介</span></div>
<p>Framework7（以下简称F7）是用于开发具有<span style="background-color: #c0c0c0;">iOS/Android原生外观</span>的<span style="background-color: #c0c0c0;">混合移动应用/Web应用</span>的开源框架，同时它也可以作为<span style="background-color: #c0c0c0;">快速原型</span>工具使用。F7完全基于Web技术——HTML、CSS、JavaScript。</p>
<p>F7简单易用，我们可以仅仅编写常规的HTML，不需要开发定制的标签（Angular那样），或者通过JavaScript来描述我们的页面（ExtJS那样）。</p>
<p>F7原先专注于iOS平台，现在也支持Google的Material design specification——Android平台的UI设计规范。</p>
<p>F7内置了大量的组件，例如模态窗口、侧面板、表格、表单、列表等等，使用这些组件时，几乎不需要编写JavaScript。</p>
<p>F7的外观易于定制，所有样式被分离在较小的<pre class="crayon-plain-tag">.less</pre> 文件中。我们可以基于Less.js修改和编译样式。</p>
<p>F7还包括更多的特性：</p>
<ol>
<li>原生的滚动效果（Native Scrolling）</li>
<li>不依赖任何第三方框架</li>
<li>高性能的，基于CSS的动画</li>
<li>支持缓存、历史、预加载（Preloading）</li>
<li>支持无限制的多视图，不需要任何JavaScript代码即可控制视图</li>
<li>简洁的JavaScript API</li>
<li>DOM7：内置的DOM操控库，风格类似于jQuery</li>
</ol>
<div class="blog_h1"><span class="graybg"><a id="first-f7-app"></a>起步：第一个F7应用</span></div>
<div class="blog_h3"><span class="graybg">基本布局：index.html</span></div>
<p>该页面声明了应用程序基本的布局：</p>
<pre class="crayon-plain-tag">&lt;!DOCTYPE html&gt;
&lt;html&gt;
  &lt;head&gt;
    &lt;meta charset="utf-8"&gt;
    &lt;meta http-equiv="Content-Security-Policy" content="default-src 'self' data: gap: https://ssl.gstatic.com 'unsafe-eval'; style-src 'self' 'unsafe-inline'; media-src *"&gt;
    &lt;meta name="format-detection" content="telephone=no"&gt;
    &lt;meta name="msapplication-tap-highlight" content="no"&gt;
    &lt;meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no, minimal-ui"&gt;
    &lt;meta name="apple-mobile-web-app-capable" content="yes"&gt;
    &lt;meta name="apple-mobile-web-app-status-bar-style" content="black"&gt;
    &lt;title&gt;第一个F7应用&lt;/title&gt;
    &lt;!-- 主题样式表 --&gt;
    &lt;link rel="stylesheet" href="css/framework7.ios.css"&gt;
    &lt;!-- 配色样式表 --&gt;
    &lt;link rel="stylesheet" href="css/framework7.ios.colors.css"&gt;
    &lt;!-- Path to your custom app styles--&gt;
  &lt;/head&gt;
  &lt;body&gt;
    &lt;!-- PhoneGap应用全屏模式下，用于状态栏overlay --&gt;
    &lt;div class="statusbar-overlay"&gt;&lt;/div&gt;
    &lt;!-- 面板overlay--&gt;
    &lt;div class="panel-overlay"&gt;&lt;/div&gt;
    &lt;!-- 带reveal特效的左侧面板 --&gt;
    &lt;div class="panel panel-left panel-reveal"&gt;
      &lt;div class="content-block"&gt;
        &lt;p&gt;这里存放左侧面板的内容&lt;/p&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;!-- 所有视图 --&gt;
    &lt;div class="views"&gt;
      &lt;!-- 主视图，应当包含"view-main"样式类 --&gt;
      &lt;div class="view view-main"&gt;
        &lt;!-- 顶部导航栏--&gt;
        &lt;div class="navbar"&gt;
          &lt;div class="navbar-inner"&gt;
            &lt;!-- 在应用标题上添加sliding效果 --&gt;
            &lt;div class="center sliding"&gt;第一个F7应用&lt;/div&gt;
            &lt;div class="right"&gt;
              &lt;!-- 
                右侧内容仅仅是一个图标，因此添加icon-only类
                open-panel类提示F7，点击链接时打开面板
              --&gt;
              &lt;a href="#" class="link icon-only open-panel"&gt;&lt;i class="icon icon-bars"&gt;&lt;/i&gt;&lt;/a&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;!-- 页面容器，由于使用了fixed-through的导航栏、工具栏，需要添加合适的样式类--&gt;
        &lt;div class="pages navbar-through toolbar-through"&gt;
          &lt;!-- 页面，"data-page" 属性包含页面的名称 --&gt;
          &lt;div data-page="index" class="page"&gt;
            &lt;!-- 可滚动的页面内容 --&gt;
            &lt;div class="page-content"&gt;
              &lt;p&gt;页面内容&lt;/p&gt;
              &lt;a href="about.html"&gt;关于&lt;/a&gt;
            &lt;/div&gt;
          &lt;/div&gt;
        &lt;/div&gt;
        &lt;!-- 底部的工具栏 --&gt;
        &lt;div class="toolbar"&gt;
          &lt;div class="toolbar-inner"&gt;
            &lt;!-- 工具栏链接--&gt;
            &lt;a href="#" class="link"&gt;链接1&lt;/a&gt;
            &lt;a href="#" class="link"&gt;链接2&lt;/a&gt;
          &lt;/div&gt;
        &lt;/div&gt;
      &lt;/div&gt;
    &lt;/div&gt;
    &lt;!-- Cordova的JavaScript占位符--&gt;
    &lt;script type="text/javascript" src="cordova.js"&gt;&lt;/script&gt; 
    &lt;!-- Framework7的JavaScript文件 --&gt;
    &lt;script type="text/javascript" src="js/framework7.js"&gt;&lt;/script&gt;
    &lt;!-- 当前应用的JavaScript文件 --&gt;
    &lt;script type="text/javascript" src="js/index.js"&gt;&lt;/script&gt;
  &lt;/body&gt;
&lt;/html&gt;</pre>
<div class="blog_h3"><span class="graybg">页面片段：about.html</span></div>
<pre class="crayon-plain-tag">&lt;!-- 我们不需要在此页面中定义完整的布局，该页面是通过Ajax解析并纳入DOM的。这种做法和AngularJS的ngInclude类似 --&gt;

&lt;!-- 顶部导航栏 --&gt;
&lt;div class="navbar"&gt;
    &lt;div class="navbar-inner"&gt;
        &lt;div class="left"&gt;
            &lt;a href="#" class="back link"&gt; &lt;i class="icon icon-back"&gt;&lt;/i&gt; &lt;span&gt;返回&lt;/span&gt;
            &lt;/a&gt;
        &lt;/div&gt;
        &lt;div class="center sliding"&gt;关于&lt;/div&gt;
        &lt;div class="right"&gt;
            &lt;a href="#" class="link icon-only open-panel"&gt;&lt;i class="icon icon-bars"&gt;&lt;/i&gt;&lt;/a&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;!-- 声明页面 --&gt;
&lt;div class="pages"&gt;
    &lt;!-- about页面 --&gt;
    &lt;div data-page="about" class="page"&gt;
        &lt;div class="page-content"&gt;
            &lt;div class="content-block"&gt;
                &lt;p&gt;关于本应用&lt;/p&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;</pre>
<div class="blog_h3"><span class="graybg">脚本：index.js</span></div>
<p>初始化应用和视图：</p>
<pre class="crayon-plain-tag">// 初始化F7应用程序
var app = new Framework7();

// 快捷方式，用于操控DOM
var $$ = Dom7;

// 添加布局中声明的主视图
var mainView = app.addView( '.view-main', {
    // 启用动态导航栏
    dynamicNavbar : true
} );</pre>
<p>如果要针对某个页面的添加脚本，可以：</p>
<pre class="crayon-plain-tag">// 推荐的方式：使用onPageInit回调
app.onPageInit( 'about', function( page ) {
    // 在这里操控about页面
} );

// 另外一种方式，针对所有页面的pageInit事件处理器
$$( document ).on( 'pageInit', function( e ) {
    // 从事件中获得页面信息
    var page = e.detail.page;

    if ( page.name === 'about' ) {
        // 在这里操控about页面
    }
    ;
} );

// 或者，使用活动的pageInit事件处理器
$$( document ).on( 'pageInit', '.page[data-page="about"]', function( e ) {
    // 在这里操控about页面
} );</pre>
<div class="blog_h3"><span class="graybg">运行效果</span></div>
<p><img class="aligncenter wp-image-10653" src="https://blog.gmem.cc/wp-content/uploads/2015/12/f7-firstapp.png" alt="f7-firstapp" width="710" height="293" /></p>
<div class="blog_h1"><span class="graybg"> Framework7核心</span></div>
<div class="blog_h2"><span class="graybg">Dom7</span></div>
<p>在DOM操控方面，F7没有使用第三方库，而是自己实现了一个称为DOM7的组件。该组件的<span style="background-color: #c0c0c0;">接口风格类似于jQuery</span>：</p>
<pre class="crayon-plain-tag">var $$ = Dom7;
$$( '.something' ).on( 'click', function( e ) {
    $$( this ).addClass( 'hello' ).attr( 'title', 'world' ).insertAfter( '.something-else' );
} );</pre>
<div class="blog_h3"><span class="graybg">Dom7常用方法</span></div>
<p>下面的方法均属于元素集——即<pre class="crayon-plain-tag">Dom7()</pre> 调用的返回值：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 20%; text-align: center;">方法</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>addClass()</td>
<td>添加样式类：<br />
<pre class="crayon-plain-tag">//为所有p元素添加intro类
$$('p').addClass('intro');</pre>
</td>
</tr>
<tr>
<td>removeClass()</td>
<td>移除样式类：<br />
<pre class="crayon-plain-tag">//从所有具有big样式类的a元素上移除big样式类
$$('a.big').removeClass('big');</pre>
</td>
</tr>
<tr>
<td>hasClass()</td>
<td>判断是否存在至少一个匹配元素具有样式类：<br />
<pre class="crayon-plain-tag">$$('p').hasClass('intro');</pre>
</td>
</tr>
<tr>
<td>toggleClass()</td>
<td>如果匹配元素上有指定样式类，移除；反之则添加：<br />
<pre class="crayon-plain-tag">$$('h1, h2').toggleClass('small');</pre>
</td>
</tr>
<tr>
<td>prop() </td>
<td>读取或者写入Property：<br />
<pre class="crayon-plain-tag">var isChecked = $$('input').prop('checked'); //读取
//设置所有checkbox为选中
$$('input[type="checkbox"]').prop('checked', true);
//设置多个属性
$$('input').prop({
  checked: false,
  disabled: true
});</pre>
</td>
</tr>
<tr>
<td>attr()</td>
<td>读取或者写入Attribute，Attribute与Property类似，但是前者<span style="background-color: #c0c0c0;">相当于直接操作XML的属性节点</span>：<br />
<pre class="crayon-plain-tag">var link = $$( 'a' ).attr( 'href' );
$$( 'a' ).attr( 'href', 'http://google.com' );
$$( 'a' ).attr( {
    id : 'new-id',
    title : 'Link to Google',
    href : 'http://google.com'
} );
$$( 'img' ).removeAttr( 'src' );</pre>
</td>
</tr>
<tr>
<td>val()</td>
<td>得到第一个匹配元素的值，或者设置所有匹配元素的值：<br />
<pre class="crayon-plain-tag">//&lt;input id="myInput" type="text" value="Lorem ipsum"/&gt;
var inputVal = $$('#myInput').val(); 
$$('input#myInput').val('New value here');</pre>
</td>
</tr>
<tr>
<td>data()</td>
<td>读取第一个匹配元素上存储的（任意）数据，或者写入所有匹配元素的数据：<br />
<pre class="crayon-plain-tag">$$( 'a' ).data( 'user', {
    id : '123',
    name : 'John',
    email : 'john@doe.com'
} );
var user = $$( 'a' ).data( 'user' );
// {id: '123', name: 'John', email: 'john@doe.com'}
$$( 'a' ).removeData( 'user' );</pre>
</td>
</tr>
<tr>
<td>dataset()</td>
<td>返回元素上定义的数据集（即data- 属性）为简单对象：<br />
<pre class="crayon-plain-tag">&lt;div id="my-div" data-loop="true" data-animate-pages="false" data-index="0" data-hello="world"&gt;&lt;/div&gt;
&lt;script type="text/javascript"&gt;
var dataset = $$('#my-div').dataset();
/**
{
    loop: true,
    animatePages: false,
    index: 0,
    hello: 'world'
}
 */
&lt;/script&gt;</pre>
</td>
</tr>
<tr>
<td>transform()</td>
<td>添加CSS转换属性：<br />
<pre class="crayon-plain-tag">$$('a').transform('rotate(90deg)')</pre>
</td>
</tr>
<tr>
<td>transition()</td>
<td>设置CSS变换持续时间：<br />
<pre class="crayon-plain-tag">$$('p').transition(300)</pre>
</td>
</tr>
<tr>
<td>on()</td>
<td>注册事件处理器：<br />
<pre class="crayon-plain-tag">// 函数签名：
function on(eventName, handler, useCapture);
// delegatedTarget被代理事件源
function on(eventName, delegatedTarget, handler, useCapture);
//用法示例：
$$('a').on('click', function (e) { 
    console.log('clicked'); 
});
//同时监听多种事件
$$('input[type="text"]').on('keyup keydown change', function (e) { 
    console.log('input value changed'); 
});
//代理事件处理器
$$(document).on('click', 'a', function (e) { 
    console.log('link clicked'); 
});</pre>
</td>
</tr>
<tr>
<td>once()</td>
<td>注册只执行一次的事件处理器</td>
</tr>
<tr>
<td>off()</td>
<td>移除事件处理器：<br />
<pre class="crayon-plain-tag">//函数签名
function off(eventName, handler, useCapture);
function off(eventName, delegatedTarget, handler, useCapture);

//移除一个事件处理器，clickHandler必须已经注册为处理器
$$('a').off('click', clickHandler);</pre>
</td>
</tr>
<tr>
<td>trigger()</td>
<td>触发匹配元素上指定事件，并执行所有已注册的事件处理器：<br />
<pre class="crayon-plain-tag">//函数签名
function trigger(eventName, eventData);</pre>
</td>
</tr>
<tr>
<td>transitionEnd()</td>
<td>添加CSS变换结束处理器：<br />
<pre class="crayon-plain-tag">$$('a').transitionEnd(function(){  })</pre>
</td>
</tr>
<tr>
<td>animationEnd()</td>
<td>添加动画效果结束处理器：<br />
<pre class="crayon-plain-tag">$$('a').animationEnd(function(){  })</pre>
</td>
</tr>
<tr>
<td>width()</td>
<td>得到第一个匹配元素的实际宽度</td>
</tr>
<tr>
<td>outerWidth()</td>
<td>得到第一个匹配元素的宽度，包括补白和边框，如果入参true，则还包括margin</td>
</tr>
<tr>
<td>height()</td>
<td>得到第一个匹配元素的实际高度</td>
</tr>
<tr>
<td>outerHeight()</td>
<td>得到第一个匹配元素的高度，包括补白和边框，如果入参true，则还包括marg</td>
</tr>
<tr>
<td>offset()</td>
<td>得到第一个匹配元素相对于document的偏移量：<br />
<pre class="crayon-plain-tag">var coords = $$('.content').offset(); 
// {top: 100, left: 200}</pre>
</td>
</tr>
<tr>
<td>hide()</td>
<td>设置<pre class="crayon-plain-tag">display:none</pre> 以隐藏所有匹配元素</td>
</tr>
<tr>
<td>show()</td>
<td>设置<pre class="crayon-plain-tag">display:block</pre> 以显示所有匹配元素</td>
</tr>
<tr>
<td>css()</td>
<td>获取或者设置CSS属性：<br />
<pre class="crayon-plain-tag">//得到left属性
$$('.content').css('left'); 
//设置left属性
$$('.content').css('left', '100px');
//设置多个CSS属性
$$('a').css({
    left: '100px',
    top: '200px',
    color: 'red',
    width: '300px',
    marginLeft: '17px', //驼峰式转写
    'padding-right': '20px' //原始样式名称，非法标识符需要引号
});</pre>
</td>
</tr>
<tr>
<td>scrollTop()</td>
<td rowspan="3">滚动元素</td>
</tr>
<tr>
<td>scrollLeft()</td>
</tr>
<tr>
<td>scrollTo()</td>
</tr>
<tr>
<td>add()</td>
<td>添加元素到既有的Dom7元素集中：<br />
<pre class="crayon-plain-tag">var links = $$('a');
links.add('p').addClass('blue');
links.add($$('div')).addClass('red');</pre>
</td>
</tr>
<tr>
<td>each()</td>
<td>迭代元素集合，执行回调函数</td>
</tr>
<tr>
<td>html()</td>
<td>得到第一个元素的HTML内容，或者设置所有匹配元素的HTML内容</td>
</tr>
<tr>
<td>text()</td>
<td>得到第一个元素的文本内容，或者设置所有匹配元素的文本内容</td>
</tr>
<tr>
<td>is()</td>
<td>判断当前元素集是否匹配指定的CSS选择器、HTML元素或者Dom7元素集合：<br />
<pre class="crayon-plain-tag">$$('div').is(CSSSelector);
$$('div').is(HTMLElement);</pre>
</td>
</tr>
<tr>
<td>index()</td>
<td>得到第一个元素在其兄弟元素中的相对索引</td>
</tr>
<tr>
<td>eq(idx)</td>
<td>获得元素集中第idx个元素</td>
</tr>
<tr>
<td>append()</td>
<td>在元素集所有元素的结尾插入指定的HTML元素或者HTML字符串</td>
</tr>
<tr>
<td>appendTo(el)</td>
<td>把元素集插入到el元素的结尾</td>
</tr>
<tr>
<td>prepend()</td>
<td>在元素集所有元素的起始处插入指定的HTML元素或者HTML字符串</td>
</tr>
<tr>
<td>prependTo(el)</td>
<td>把元素集插入到el元素的起始处</td>
</tr>
<tr>
<td>insertBefore(tgt)</td>
<td>在目标（CSS选择器、HTML元素、Dom7元素集）之前插入当前匹配的元素集</td>
</tr>
<tr>
<td>insertAfter(tgt)</td>
<td>在目标（CSS选择器、HTML元素、Dom7元素集）之后插入当前匹配的元素集</td>
</tr>
<tr>
<td>next([sel])</td>
<td>获得元素集每个元素的下一个弟弟元素，如果提供selector，则仅返回匹配此选择器的弟弟元素</td>
</tr>
<tr>
<td>nextAll([sel])</td>
<td>获得元素集每个元素的全部弟弟元素，如果提供selector，则仅返回匹配此选择器的弟弟元素</td>
</tr>
<tr>
<td>prev([sel])</td>
<td>获得元素集每个元素的下一个哥哥元素，如果提供selector，则仅返回匹配此选择器的哥哥元素</td>
</tr>
<tr>
<td>prevAll([sel])</td>
<td>获得元素集每个元素的全部哥哥元素，如果提供selector，则仅返回匹配此选择器的哥哥元素</td>
</tr>
<tr>
<td>parent([sel])</td>
<td>获得元素集每个元素的第一个匹配选择器的祖先元素</td>
</tr>
<tr>
<td>parents([sel])</td>
<td>获得元素集每个元素的所有匹配选择器的祖先元素</td>
</tr>
<tr>
<td>find([sel])</td>
<td>获得元素集每个元素的所有匹配选择器的后代元素</td>
</tr>
<tr>
<td>children([sel])</td>
<td>获得元素集每个元素的所有匹配选择器的儿子元素</td>
</tr>
<tr>
<td>filter(callback)</td>
<td>过滤当前元素集：<br />
<pre class="crayon-plain-tag">var redLinks = $$('a').filter(function(index, el) {
    return $$(this).hasClass('red');
})</pre>
</td>
</tr>
<tr>
<td>remove()</td>
<td>从DOM树中移除当前元素集</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">Dom7事件快捷方法 </span></div>
<p>下表是一系列事件处理的快捷方式，它们都属于元素集对象。调用<pre class="crayon-plain-tag">event()</pre> 手工触发指定事件，调用<pre class="crayon-plain-tag">event(handleFunc)</pre> 则注册事件监听器：</p>
<table class=" full-width fixed-word-wrap">
<tbody>
<tr>
<td style="width: 25%;">blur</td>
<td style="width: 25%;">focus</td>
<td style="width: 25%;">focusin</td>
<td style="width: 25%;">focusout</td>
</tr>
<tr>
<td>click</td>
<td>keyup</td>
<td>keydown</td>
<td>keypress</td>
</tr>
<tr>
<td>mouseenter</td>
<td>mouseover</td>
<td>mousedown</td>
<td>mousemove</td>
</tr>
<tr>
<td>mouseout</td>
<td>mouseleave</td>
<td>mouseup</td>
<td>submit</td>
</tr>
<tr>
<td>touchstart</td>
<td>touchend</td>
<td>touchmove</td>
<td>change</td>
</tr>
<tr>
<td>resize</td>
<td>scroll</td>
<td> </td>
<td> </td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">Dom7工具函数</span></div>
<p>下列函数都直接定义在Dom7上：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">函数</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>each()</td>
<td>遍历“可迭代对象”，函数签名：<pre class="crayon-plain-tag">Dom7.each(object/array, callback);</pre> <br />示例：<br />
<pre class="crayon-plain-tag">var fruits = ['Apple', 'Orange', 'Pineapple', 'Bannana'];
$$.each(fruits, function (index, value) {});  
var person = {
    firstName: 'Alex',
    lastName: 'Wong',
    age: 25,
};
$$.each(person, function (key, value) {});</pre>
</td>
</tr>
<tr>
<td>parseUrlQuery()</td>
<td>解析URL并提取请求参数：<br />
<pre class="crayon-plain-tag">var query = $$.parseUrlQuery('http://google.com/?id=5&amp;foo=bar');
console.log(query); //-&gt; {id: 5, foo: 'bar'}</pre>
</td>
</tr>
<tr>
<td>isArray()</td>
<td>判断一个对象是否为数组</td>
</tr>
<tr>
<td>unique()</td>
<td>返回数组的一个去重副本</td>
</tr>
<tr>
<td>serializeObject()</td>
<td>以URL请求参数的形式串行化一个简单对象（PO）</td>
</tr>
<tr>
<td>toCamelCase()</td>
<td>从短横线连接风格（web-app）转换为驼峰式大小写（webApp）</td>
</tr>
<tr>
<td>dataset()</td>
<td>将元素（CSS选择器）中的data-*属性转换为简单对象（PO）</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">Ajax支持</span></div>
<p>我们可以调用<pre class="crayon-plain-tag">$$.ajax(parameters)</pre> 来发送Ajax请求并处理结果，其中parameters会一个配置对象，可以指定以下属性：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 15%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>async</td>
<td>是否异步发送请求，默认true</td>
</tr>
<tr>
<td>url</td>
<td>请求的目标地址</td>
</tr>
<tr>
<td>method</td>
<td>请求使用的HTTP方法，默认'GET'</td>
</tr>
<tr>
<td>cache</td>
<td>是否允许浏览器缓存请求，默认true。如果设置为false则自动添加<pre class="crayon-plain-tag">_nocache={timestamp}</pre> 到GET请求参数</td>
</tr>
<tr>
<td>contentType</td>
<td>内容类型，默认'application/x-www-form-urlencoded'，还可以指定为'multipart/form-data'、'text/plain'。<br />对于跨域请求，如果设置contentType为<span style="background-color: #c0c0c0;">前述三者之外的任意值</span>，会导致浏览器发送preflight的OPTIONS请求到服务器</td>
</tr>
<tr>
<td>crossDomain</td>
<td>如果要对本域强制使用跨站请求，可以设置为true</td>
</tr>
<tr>
<td>data</td>
<td>需要发送给服务器的数据，对于GET请求，会编码为请求参数（除非该参数已经是字符串）附加到URL后面</td>
</tr>
<tr>
<td>processData</td>
<td>默认true，设置为false可以阻止Dom7把Object形式的data编码为请求参数形式</td>
</tr>
<tr>
<td>dataType</td>
<td>期望服务器返回的数据类型， 默认'text'，可以指定为'json'</td>
</tr>
<tr>
<td>headers</td>
<td>以键值对形式指定额外的请求头</td>
</tr>
<tr>
<td>xhrFields</td>
<td>设置到浏览器原生XHR对象上的键值对</td>
</tr>
<tr>
<td>username</td>
<td>HTTP验证用户名</td>
</tr>
<tr>
<td>password</td>
<td>HTTP验证密码</td>
</tr>
<tr>
<td>timeout</td>
<td>请求处理超时的毫秒数</td>
</tr>
<tr>
<td>beforeSend</td>
<td>在XHR请求发送前执行的回调：<pre class="crayon-plain-tag">function (xhr)</pre> ，可以用于修改请求</td>
</tr>
<tr>
<td>error</td>
<td>在XHR请求执行失败后执行的回调：<pre class="crayon-plain-tag">function (xhr, status)</pre> </td>
</tr>
<tr>
<td>success</td>
<td>在XHR请求执行成功后执行的回调：<pre class="crayon-plain-tag">function (data, status, xhr)</pre> <br />如果dataType设置为json，那么data自动转换为JavaScript对象，转换失败则status显示为parseerror<br />Framework7要求严格规范的JSON格式</td>
</tr>
<tr>
<td>complete</td>
<td>在XHR请求处理完毕后执行的回调，该回调在error/success之后执行：<pre class="crayon-plain-tag">function (xhr, status)</pre> </td>
</tr>
<tr>
<td>statusCode</td>
<td>指定不同HTTP状态码对应的回调：<br />
<pre class="crayon-plain-tag">$$.ajax({
  url: 'somepage.html',
  statusCode: {
    404: function (xhr) {
      alert('page not found');
    }
  }
});</pre>
</td>
</tr>
</tbody>
</table>
<p>为了简化常用类型的XHR请求的发送，Dom7提供以下快捷函数：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">函数</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>Dom7.get()</td>
<td rowspan="3">这三个函数都接受三个入参：
<ol>
<li>url，请求的URL</li>
<li>data，发送给服务器的对象或者字符串</li>
<li>处理成功后的回调函数：<pre class="crayon-plain-tag">function (data, status, xhr)</pre> </li>
</ol>
</td>
</tr>
<tr>
<td>Dom7.post()</td>
</tr>
<tr>
<td>Dom7.getJSON()</td>
</tr>
</tbody>
</table>
<p>下表列出Dom7扩展的，全局的Ajax监听事件。这些事件都从<pre class="crayon-plain-tag">document</pre> 对象上发出：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">事件</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>ajaxStart</td>
<td>在XHR请求发送前触发，可以全局性的修改请求，例如添加请求头</td>
</tr>
<tr>
<td>ajaxError</td>
<td>当任何XHR请求处理失败时触发</td>
</tr>
<tr>
<td>ajaxSuccess</td>
<td>当任何XHR请求处理成功时触发</td>
</tr>
<tr>
<td>ajaxComplete</td>
<td>当任何XHR请求处理完毕后触发，该事件在ajaxError/ajaxSuccess之后触发</td>
</tr>
</tbody>
</table>
<p>使用上述事件的例子如下：</p>
<pre class="crayon-plain-tag">$$(document).on('ajaxComplete', function (e) {
  var xhr = e.detail.xhr;
  console.log('request performed');
});</pre>
<div class="blog_h2"><span class="graybg">布局</span></div>
<p>F7框架的文档中多次提到布局（Layout）这一个词，在这里<span style="background-color: #c0c0c0;">布局是指应用程序或者F7组件的HTML结构</span>。</p>
<div class="blog_h3"><span class="graybg">应用程序HTML布局</span></div>
<p>开发F7应用的第一步，就是创建包含应用程序骨架的index.html文件，此文件包含了应用程序的基本布局。</p>
<p>iOS的基础布局样例可以参考<a href="#first-f7-app">前面的章节</a>，Android Material风格的布局，与iOS稍微不同：</p>
<pre class="crayon-plain-tag">&lt;div data-page="index" class="page"&gt;

    &lt;!-- 顶部导航栏需要置于页的内部 --&gt;
    &lt;div class="navbar"&gt;
        &lt;div class="navbar-inner"&gt;
            &lt;div class="center"&gt;Awesome App&lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;

    &lt;!-- 底部工具栏需要置于页的内部 --&gt;
    &lt;div class="toolbar"&gt;
      &lt;div class="toolbar-inner"&gt;
          &lt;!-- Toolbar links --&gt;
          &lt;a href="#" class="link"&gt;Link 1&lt;/a&gt;
          &lt;a href="#" class="link"&gt;Link 2&lt;/a&gt;
      &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;</pre>
<div class="blog_h2"><span class="graybg">应用初始化 </span></div>
<p>可以调用下面的函数初始化F7应用，变量app代表了F7应用的实例：</p>
<pre class="crayon-plain-tag">var app = new Framework7( { /* 唯一的入参是包含所有初始化参数的对象 */
    // 模态窗口的默认标题
    modalTitle : '消息',

    // 启用哈希（#）导航
    pushState : true,

    // 在XHR请求前后，分别显示，隐藏指示器
    onAjaxStart : function( xhr ) {
        myApp.showIndicator();
    },
    onAjaxComplete : function( xhr ) {
        myApp.hideIndicator();
    }
} );</pre>
<div class="blog_h3"><span class="graybg"><a id="app-init-params"></a>应用初始化参数详解</span></div>
<p>F7提供了大量可用的构造参数，如下表：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">参数<em>/属性</em></td>
<td style="text-align: center;">类型和默认值</td>
<td style="width: 35%; text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center;" colspan="3"><em>Material Theme主题专用参数</em></td>
</tr>
<tr>
<td>material</td>
<td>boolean = false</td>
<td>设置为true则启用Material特有的JS行为</td>
</tr>
<tr>
<td>materialPageLoadDelay</td>
<td>number = 0</td>
<td>新加载页面动画的启动延迟，单位毫秒</td>
</tr>
<tr>
<td>materialRipple</td>
<td>boolean = true</td>
<td>启用Material的触碰波痕效果</td>
</tr>
<tr>
<td>materialRippleElements</td>
<td>string = <br />
<pre class="crayon-plain-tag">.ripple
a.link
a.item-link
.button
.modal-button
.tab-link
.label-radio
.label-checkbox
.actions-modal-button
a.searchbar-clear
.floating-button</pre>
</td>
<td>启用触碰波痕效果的元素的CSS选择器</td>
</tr>
<tr>
<td>materialPreloaderHtml</td>
<td>string = <br />
<pre class="crayon-plain-tag">&lt;span class="preloader-inner"&gt;
    &lt;span class="preloader-inner-gap"&gt;&lt;/span&gt;
    &lt;span class="preloader-inner-left"&gt;
        &lt;span class="preloader-inner-half-circle"&gt;&lt;/span&gt;
    &lt;/span&gt;
    &lt;span class="preloader-inner-right"&gt;
        &lt;span class="preloader-inner-half-circle"&gt;&lt;/span&gt;
    &lt;/span&gt;
&lt;/span&gt;</pre>
</td>
<td> Material主题预加载器（Preloader）的HTML</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>缓存</em></td>
</tr>
<tr>
<td>cache</td>
<td>boolean = true</td>
<td>是否启用GET方式的Ajax的缓存</td>
</tr>
<tr>
<td>cacheDuration</td>
<td>number = 1000*60*10</td>
<td>缓存有效期，单位毫秒</td>
</tr>
<tr>
<td>cacheIgnore</td>
<td>array = []</td>
<td>那些URL需要禁用缓存</td>
</tr>
<tr>
<td>cacheIgnoreGetParameters</td>
<td>boolean = false</td>
<td>缓存时是否忽略附在URL后面的参数，如果启用，那么不同查询参数只生成同一个缓存</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>快速点击库</em></td>
</tr>
<tr>
<td>fastClicks</td>
<td>boolean = true</td>
<td>Fast clicks是F7内置的组件，移除300ms的click事件触发延迟</td>
</tr>
<tr>
<td>fastClicksDelayBetweenClicks</td>
<td>number = 50</td>
<td>连续多次click事件的最小间隔</td>
</tr>
<tr>
<td>fastClicksDistanceThreshold</td>
<td>number = 10</td>
<td>如果轻触/移动（tap/move）的距离大于此值，则click事件不会触发</td>
</tr>
<tr>
<td>activeState</td>
<td>boolean = true</td>
<td>如果启用，F7为当前被触碰的元素添加active-state样式类</td>
</tr>
<tr>
<td>activeStateElemets</td>
<td>string = 'a, button, label, span'</td>
<td>CSS选择器，指定哪些元素支持activeState配置</td>
</tr>
<tr>
<td>tapHold</td>
<td>boolean = false</td>
<td>设置为true则启用轻触并保持（tap hold）事件</td>
</tr>
<tr>
<td>tapHoldDelay</td>
<td>number = 750</td>
<td>在轻触后，必须维持多少ms才触发tap hold事件</td>
</tr>
<tr>
<td>tapHoldPreventClicks</td>
<td>boolean = true</td>
<td>禁止在tap hold事件之后触发click事件</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>导航路由</em></td>
</tr>
<tr>
<td>router</td>
<td>boolean  = true</td>
<td>设置为false禁用内置的导航路由器</td>
</tr>
<tr>
<td>ajaxLinks</td>
<td>string = undefined</td>
<td>默认的，F7通过Ajax加载所有<pre class="crayon-plain-tag">&lt;a&gt;</pre> 链接。如果指定该参数，那么只有匹配CSS选择器的元素，才使用Ajax加载</td>
</tr>
<tr>
<td>dynamicPageUrl</td>
<td>string  ='content-{{index}}'</td>
<td>动态加载页的URL规则，支持占位符：<br /><pre class="crayon-plain-tag">{{index}}</pre>  在导航历史中的索引<br /><pre class="crayon-plain-tag">{{name}}</pre> 页面的<pre class="crayon-plain-tag">data-page</pre> 属性</td>
</tr>
<tr>
<td>uniqueHistory</td>
<td>boolean = false </td>
<td>设置为true，F7保持视图的导航历史条目的唯一性，重复的条目会被删除</td>
</tr>
<tr>
<td>uniqueHistoryIgnoreGetParameters</td>
<td>boolean = false</td>
<td>判断导航历史条目唯一性时，是否忽略URL参数</td>
</tr>
<tr>
<td>externalLinks</td>
<td>string = '.external'</td>
<td>CSS选择器，用于指定哪些链接属于“外部链接”，不应当由F7处理</td>
</tr>
<tr>
<td>allowDuplicateUrls</td>
<td>boolean = false</td>
<td>
<p>设置为true，则允许加载与当前活动页URL相同的页<br />在指向当前页面本身的导航时有用</p>
</td>
</tr>
<tr>
<td>animateNavBackIcon</td>
<td>boolean = false</td>
<td>仅IOS。让后退图标的动画效果更加Native</td>
</tr>
<tr>
<td>animatePages</td>
<td>boolean = true</td>
<td>设置为false可以禁用页面切换动画</td>
</tr>
<tr>
<td>preloadPreviousPage</td>
<td>boolean = true</td>
<td>swipe back page特性需要启用该选项</td>
</tr>
<tr>
<td>preroute</td>
<td>function(view, options);</td>
<td>
<p>该回调拦截路由默认的load/back动作，可以加载其它页或者做额外的工作</p>
<p>返回false则阻止当前路由</p>
<p>该回调在preprocess之前执行</p>
</td>
</tr>
<tr>
<td>preprocess</td>
<td>function(content, url, next);</td>
<td>
<p>导航时，可以在插入DOM之前，修改加载的内容。这意味着我们可以使用自己习惯的客户端模板引擎（例如Template7、AngularJS）</p>
<p>此回调中无法访问传递给页面的上下文，可以通过URL的GET参数向新页面传递信息</p>
<p>执行返回操作时，不会触发该回调</p>
</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>Push State</em></td>
</tr>
<tr>
<td>pushState</td>
<td>boolean = false</td>
<td>是否启用#导航，启用时，用户可以点击浏览器的前进、后退按钮，在应用的不同页面之间进行切换</td>
</tr>
<tr>
<td>pushStateSeparator</td>
<td>string = '#!/'</td>
<td>Push State的URL分隔符，URL会呈现为类似<pre class="crayon-plain-tag">http://gmem.cc/#!/hello.html</pre> 的形式</td>
</tr>
<tr>
<td>pushStateNoAnimation</td>
<td>boolean = false</td>
<td>如果设置为true，在Push State之间导航不会产生动画效果</td>
</tr>
<tr>
<td>pushStatePreventOnLoad</td>
<td>boolean = true</td>
<td>启用后，应用加载时第一个pop state事件被忽略，可以避免不必要的加载动作</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em> Swipe back（仅iOS主题）</em></td>
</tr>
<tr>
<td>swipeBackPage</td>
<td>boolean = true</td>
<td>启用后，可以用从屏幕左侧边缘滑入的手势切换到上一个页面</td>
</tr>
<tr>
<td>swipeBackPageThreshold</td>
<td>number = 0</td>
<td>滑动距离最小多少像素后，导致Swipe back操作触发</td>
</tr>
<tr>
<td>swipeBackPageActiveArea</td>
<td>number = 30</td>
<td>触发Swipe back操作的屏幕左侧边缘的宽度</td>
</tr>
<tr>
<td>swipeBackPageAnimateShadow</td>
<td>boolean = true</td>
<td>是否在Swipe back时启用阴影效果</td>
</tr>
<tr>
<td>swipeBackPageAnimateOpacity</td>
<td>boolean = true</td>
<td>是否在Swipe back时启用透明效果</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>可排序列表</em></td>
</tr>
<tr>
<td>sortable</td>
<td>boolean = true</td>
<td>如果不使用可排序列表（sortable lists ）特性，可以禁用以提升性能</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em> Swipeout</em></td>
</tr>
<tr>
<td>swipeout</td>
<td>boolean = true</td>
<td>如果不使用swipeout特性，可以禁用以提升性能</td>
</tr>
<tr>
<td>swipeoutNoFollow</td>
<td>boolean = false</td>
<td>用于老旧设备，可以提升性能</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>侧面板</em></td>
</tr>
<tr>
<td>swipePanel</td>
<td>string = false</td>
<td>
<p>可以设置为left（左侧）/right（右侧），默认禁用</p>
<p>启用后，可以通过swipe手势从左侧/右侧打开侧面板</p>
</td>
</tr>
<tr>
<td>swipePanelCloseOpposite</td>
<td>boolean = true</td>
<td>可以关闭对面的面板，即右面板之于左面板</td>
</tr>
<tr>
<td>swipePanelOnlyClose</td>
<td>boolean = false</td>
<td>仅允许通过swipe关闭面板，而不允许打开</td>
</tr>
<tr>
<td>swipePanelActiveArea</td>
<td>number = false</td>
<td>触发Swipe面板操作的屏幕左侧边缘的宽度</td>
</tr>
<tr>
<td>swipePanelNoFollow</td>
<td>boolean = false</td>
<td>用于老旧设备，可以提升性能</td>
</tr>
<tr>
<td>swipePanelThreshold</td>
<td>number = 0</td>
<td>滑动距离最小多少像素后，导致Swipe 面板操作触发</td>
</tr>
<tr>
<td>panelsCloseByOutside</td>
<td>boolean = true</td>
<td>是否允许在面板外部轻触，以关闭面板</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>模态窗口</em></td>
</tr>
<tr>
<td>modalTitle</td>
<td>string = 'Framework7'</td>
<td>模态窗口（Alert、Confirm、Prompt）的默认标题</td>
</tr>
<tr>
<td>modalButtonOk</td>
<td>string = 'OK'</td>
<td>OK按钮默认文本</td>
</tr>
<tr>
<td>modalButtonCancel</td>
<td>string = 'Cancel'</td>
<td>Cancel按钮默认文本</td>
</tr>
<tr>
<td>modalPreloaderTitle</td>
<td>string = 'Loading... '</td>
<td>正在加载的提示文本</td>
</tr>
<tr>
<td>modalCloseByOutside</td>
<td>boolean = false</td>
<td>是否允许在外部轻触以关闭模态窗口</td>
</tr>
<tr>
<td>actionsCloseByOutside</td>
<td>boolean = true</td>
<td>与上一选项类似， 但是用于Action Sheet modal</td>
</tr>
<tr>
<td>popupCloseByOutside</td>
<td>boolean = true</td>
<td>与上一选项类似， 但是用于Popup modal</td>
</tr>
<tr>
<td>modalTemplate</td>
<td>string = undefined</td>
<td>用于模态窗口的Template7模板</td>
</tr>
<tr>
<td>modalActionsTemplate</td>
<td>string  = undefined</td>
<td>用于Action Sheet模块窗口的Template7模板</td>
</tr>
<tr>
<td>modalActionsToPopoverTemplate</td>
<td>string</td>
<td>用于转换为Popover的Action Sheet的Template7模板</td>
</tr>
<tr>
<td>modalUsernamePlaceholder</td>
<td>string = 'Username'</td>
<td>用户名输入框的占位符</td>
</tr>
<tr>
<td>modalPasswordPlaceholder</td>
<td>string = 'Password'</td>
<td>密码输入框的占位符</td>
</tr>
<tr>
<td>modalStack</td>
<td>boolean = true</td>
<td>禁止同时打开多个模态窗口，如果多个模态窗口被触发，那么最顶层的被关闭后，下层模态窗口才打开</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>Smart Select</em></td>
</tr>
<tr>
<td>smartSelectOpenIn</td>
<td>string = 'page'</td>
<td>智能选择如何被打开<br />可选值page、popup、picker</td>
</tr>
<tr>
<td>smartSelectBackTemplate</td>
<td>string =<br />
<pre class="crayon-plain-tag">&lt;div class="left sliding"&gt;
  &lt;a href="#" class="back link"&gt;
    &lt;i class="icon icon-back"&gt;&lt;/i&gt;
    &lt;span&gt;{{backText}}&lt;/span&gt;
  &lt;/a&gt;
&lt;/div&gt;</pre>
</td>
<td>智能选择页面后退按钮的HTML模板</td>
</tr>
<tr>
<td>smartSelectPopupCloseTemplate</td>
<td>string = <br />
<pre class="crayon-plain-tag">&lt;div class="left"&gt;
  &lt;a href="#" class="link close-popup"&gt;
    &lt;i class="icon icon-back"&gt;&lt;/i&gt;
    &lt;span&gt;{{closeText}}&lt;/span&gt;
  &lt;/a&gt;
&lt;/div&gt;</pre>
</td>
<td>智能选择弹框关闭按钮的HTML模板</td>
</tr>
<tr>
<td>smartSelectBackText</td>
<td>string = 'Back'</td>
<td>后退按钮文本</td>
</tr>
<tr>
<td>smartSelectPopupCloseText</td>
<td>string = 'Close'</td>
<td>关闭按钮文本</td>
</tr>
<tr>
<td>smartSelectPickerCloseText</td>
<td>string = 'Done'</td>
<td>完毕按钮文本</td>
</tr>
<tr>
<td>smartSelectSearchbar</td>
<td>boolean = false</td>
<td>是否启用智能选择的搜索功能</td>
</tr>
<tr>
<td>smartSelectBackOnSelect</td>
<td>boolean = false</td>
<td>如果启用，当用户选择任意一项后，智能选择页面自动关闭</td>
</tr>
<tr>
<td>smartSelectFormTheme</td>
<td>string = undefined</td>
<td>智能选择页面/弹框的表单元素使用的颜色主题代码</td>
</tr>
<tr>
<td>smartSelectNavbarTheme</td>
<td>string = undefined</td>
<td>智能选择页面/弹框的导航栏使用的颜色主题代码</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>导航栏和工具栏</em></td>
</tr>
<tr>
<td>hideNavbarOnPageScroll</td>
<td>boolean = false</td>
<td>如果设置为true，导航栏在页面下滚时自动隐藏，上滚时自动显示</td>
</tr>
<tr>
<td>hideToolbarOnPageScroll</td>
<td>boolean = false</td>
<td>如果设置为true，工具栏在页面下滚时自动隐藏，上滚时自动显示</td>
</tr>
<tr>
<td>hideTabbarOnPageScroll</td>
<td>boolean = false</td>
<td>如果设置为true，页签栏（Tabbar）在页面下滚时自动隐藏，上滚时自动显示<br />仅针对基于Tab Bar的页面布局</td>
</tr>
<tr>
<td>showBarsOnPageScrollEnd</td>
<td>boolean = true</td>
<td>当滚动到页面底部时，自动显示导航、工具栏</td>
</tr>
<tr>
<td>showBarsOnPageScrollTop</td>
<td>boolean = true</td>
<td>当滚动到页面顶部时，自动显示导航、工具栏</td>
</tr>
<tr>
<td>scrollTopOnNavbarClick</td>
<td>boolean = false</td>
<td>如果设置为true，点击导航栏中的'center'元素，自动滚动当前活动页面到最顶部</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>图片延迟加载</em></td>
</tr>
<tr>
<td>imagesLazyLoadThreshold</td>
<td>number  = 0</td>
<td>默认的，图片只有显示在当前屏幕上时，才加载<br />修改此参数，则图片距离屏幕还剩N像素时就加载</td>
</tr>
<tr>
<td>imagesLazyLoadSequential</td>
<td>boolean = true</td>
<td>如果启用，图片会顺序的一个个加载</td>
</tr>
<tr>
<td>imagesLazyLoadPlaceholder</td>
<td>string</td>
<td>正在加载的图片的占位符，默认是一个1像素的图片</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>通知</em></td>
</tr>
<tr>
<td>notificationTitle</td>
<td>string = undefined</td>
<td>仅iOS，所有通知的默认标题</td>
</tr>
<tr>
<td>notificationSubtitle</td>
<td>string = undefined</td>
<td>仅iOS，所有通知的默认子标题</td>
</tr>
<tr>
<td>notificationMedia</td>
<td>string = undefined</td>
<td>仅iOS，所有通知的默认媒体（图标或者图片）</td>
</tr>
<tr>
<td>notificationHold</td>
<td>number = undefined</td>
<td>所有通知默认持续的毫秒数</td>
</tr>
<tr>
<td>notificationCloseOnClick</td>
<td>boolean = false</td>
<td>设置为true，则点击通知关闭之</td>
</tr>
<tr>
<td>notificationCloseIcon</td>
<td>boolean = true</td>
<td>仅iOS，设置为true默认显示关闭按钮</td>
</tr>
<tr>
<td>notificationCloseButtonText</td>
<td>string = 'Close'</td>
<td>仅Material，关闭按钮的默认文本</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>状态栏（仅iOS主题）</em></td>
</tr>
<tr>
<td>statusbarOverlay</td>
<td>boolean = undefined</td>
<td>自动检测全屏模式并处理状态栏overlay，在顶部添加额外的像素以改善UI效果</td>
</tr>
<tr>
<td>scrollTopOnStatusbarClick</td>
<td>boolean = false</td>
<td>如果设置为true，在状态栏上点击后，当前活动页面滚动到顶部</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>Template7</em></td>
</tr>
<tr>
<td>template7Pages</td>
<td>boolean = false</td>
<td>设置为true，使用Template7来渲染Ajax和动态页面</td>
</tr>
<tr>
<td>template7Data</td>
<td>object = {}</td>
<td>用于渲染Template7模板的数据</td>
</tr>
<tr>
<td>precompileTemplates</td>
<td>boolean = false</td>
<td>启用后，F7预编译所有Template7模板</td>
</tr>
<tr>
<td><em>templates</em></td>
<td>object = {}</td>
<td>所有预编译的模板，可以通过该属性访问</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>页面回调</em></td>
</tr>
<tr>
<td>onPageBeforeInit</td>
<td rowspan="7">
<p>function(app, page);</p>
<p>其中：</p>
<ol>
<li>app为当前F7应用对象</li>
<li>page为操作的页面数据对象</li>
</ol>
<p>这些参数与对应的<a href="#page-callback">页面回调</a>具有类似的功能</p>
</td>
<td>当F7插入新的页面到DOM中后，该回调被执行<br />该回调和pageBeforeInit事件具有一样的效果</td>
</tr>
<tr>
<td>onPageInit</td>
<td>当F7初始化被请求页面的组件和导航栏时，该回调被执行<br />该回调和pageInit事件具有一样的效果</td>
</tr>
<tr>
<td>onPageBeforeAnimation</td>
<td>当页面被初始化完毕，准备执行页面/导航栏动画时，该回调被执行<br />该回调和pageBeforeAnimation事件具有一样的效果</td>
</tr>
<tr>
<td>onPageAfterAnimation</td>
<td>当页面/导航栏动画执行完毕后，该回调被执行<br />该回调和pageAfterAnimation事件具有一样的效果</td>
</tr>
<tr>
<td>onPageBeforeRemove</td>
<td>当页面即将从DOM中移除时，该回调被执行<br />该回调和pageBeforeRemove事件具有一样的效果</td>
</tr>
<tr>
<td>onPageBack</td>
<td>在back转换之前，执行该回调，与onPageBeforeAnimation针对new页面不同，该回调是针对old页面（即将被切换掉的页面）<br />该回调和pageBack事件具有一样的效果</td>
</tr>
<tr>
<td>onPageAfterBack</td>
<td>在back转换之后，执行该回调，该回调同样针对old页面，而onPageAfterAnimation针对新页面<br />该回调和pageAfterBack事件具有一样的效果</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>Ajax回调</em></td>
</tr>
<tr>
<td>onAjaxStart</td>
<td rowspan="2">function(xhr);</td>
<td>在Ajax请求开始前执行的回调，以XHR数据作为参数</td>
</tr>
<tr>
<td>onAjaxComplete</td>
<td>在Ajax请求完毕后执行的回调，以XHR数据作为参数</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>Namespace</em></td>
</tr>
<tr>
<td>viewClass</td>
<td>string =  'view'</td>
<td>视图元素（View element）的CSS类名</td>
</tr>
<tr>
<td>viewMainClass</td>
<td>string = 'view-main'</td>
<td>主视图元素的CSS类名</td>
</tr>
<tr>
<td>viewsClass</td>
<td>string = 'views'</td>
<td>所有视图元素（Views element）的CSS类名</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>初始化</em></td>
</tr>
<tr>
<td>init</td>
<td>boolean = true</td>
<td>如果设置为false，则必须调用Framework7的<pre class="crayon-plain-tag">init()</pre> 方法手工初始化F7</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">preprocess：使用模板引擎</span></div>
<p>通过设置preprocess，我们可以使用任何客户端模板引擎来解析模板、生成DOM。例如：</p>
<pre class="crayon-plain-tag">var app = new Framework7( {
    preprocess : function( content, url, next ) {
        if ( url === 'user.html' ) {
            // 编译模板
            var template = Template7.compile( content );
            // 链接模板为DOM
            var resultContent = template( {
                title : 'User',
                user : [ 'Alex', 'Meng', 'Cai' ]
            } );
            return resultContent;
        }
    }
} );</pre>
<p>可以解析Template7模板：</p>
<pre class="crayon-plain-tag">&lt;div class="navbar"&gt;
  &lt;div class="navbar-inner"&gt;
    &lt;div class="center"&gt;{{title}}&lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;
&lt;div class="pages"&gt;
  &lt;div data-page="user" class="user"&gt;
    &lt;div class="user-content"&gt;
      &lt;ul&gt;
        {{#each user}}
        &lt;li&gt;{{this}}&lt;/li&gt;
        {{/each}}
      &lt;/ul&gt;
    &lt;/div&gt;
  &lt;/div&gt;
&lt;/div&gt;</pre>
<p> 为preprocess指定第四个参数next，可以异步的返回链接后的DOM：</p>
<pre class="crayon-plain-tag">var app = new Framework7( {
    preprocess : function( content, url, next ) {
        if ( url === 'user.html' ) {
            // 通过XHR加载用户的JSON数据，用以填充模板
            $$.get( 'user.json', function( data ) {
                // 编译
                var template = Template7.compile( content );
                // 链接
                var resultContent = template( data );
                // 把结果传递给next
                next( resultContent );
            } );
            // 使用next，则不需要返回任何值
        }
    }
} );</pre>
<div class="blog_h3"><span class="graybg">preroute：路由拦截 </span></div>
<p>使用该参数可以阻止F7路由机制的默认行为， 从而加载其他页面、重定向或者执行其他动作。例如当未登录用户请求某个页面时，自动切换到登录页面：</p>
<pre class="crayon-plain-tag">var app = new Framework7( {
    preroute : function( view, options ) {
        if ( !userLoggedIn ) {
            //加载其它页面
            view.router.loadPage( 'auth.html' ); 
            return false; // 返回false则阻止默认路由行为
        }
    }
} );</pre>
<div class="blog_h2"><span class="graybg">视图</span></div>
<p>在F7中，元素<pre class="crayon-plain-tag">&lt;div class="views"&gt;</pre> 是所有可视化视图（visual view，除了面板、模态窗口之外的视图）的容器，一个F7应用只能有一个views元素。<pre class="crayon-plain-tag">app.views</pre> 属性指向所有视图实例的数组。</p>
<p>元素<pre class="crayon-plain-tag">&lt;div class="view"&gt;</pre> 则代表了一个独立的<span style="background-color: #c0c0c0;">视图，可以拥有自己的设置、导航和历史</span>，每个视图可以具有自己的<span style="background-color: #c0c0c0;">导航栏/工具栏布局</span>以及不同的<span style="background-color: #c0c0c0;">样式</span>。</p>
<p>下面是视图的HTML结构示例：</p>
<pre class="crayon-plain-tag">&lt;body&gt;
    &lt;!-- 面板视图不包括在views内 --&gt;
    &lt;div class="panel panel-left panel-cover"&gt;
        &lt;div class="view panel-view"&gt;...&lt;/div&gt;
    &lt;/div&gt;
    &lt;!-- 所有可视化视图，必须作为子元素，这便于我们整个的移动应用——例如打开侧面板时 --&gt;
    &lt;div class="views"&gt;
        &lt;!-- view-main为主视图 --&gt;
        &lt;div class="view view-main"&gt;
            &lt;!-- 每个视图可以具有自己的导航栏页面、工具栏 --&gt;
            &lt;!-- Navbar--&gt;
            &lt;!-- Pages --&gt;
            &lt;!-- Toolbar--&gt;
        &lt;/div&gt;
        &lt;!-- 另外一个视图 --&gt;
        &lt;div class="view another-view"&gt;
            &lt;!-- Navbar--&gt;
            &lt;!-- Pages --&gt;
            &lt;!-- Toolbar--&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;!-- 弹窗、模态窗口不包括在views内 --&gt;
    &lt;div class="popup"&gt;
        &lt;div class="view popup-view"&gt;...&lt;/div&gt;
    &lt;/div&gt;
&lt;/body&gt;</pre>
<div class="blog_h3"><span class="graybg">主视图</span></div>
<p>主视图由样式类<pre class="crayon-plain-tag">view-main</pre>  声明，主视图的作用是：</p>
<ol>
<li>默认的，所有链接均在主视图中加载页面</li>
<li>PushState哈希导航仅支持主视图</li>
</ol>
<div class="blog_h3"><span class="graybg">视图初始化</span></div>
<p>可以使用如下方法添加新的视图：</p>
<pre class="crayon-plain-tag">/**
 * @param container  HTML元素或者CSS选择器，视图的容器元素
 * @param parameters 视图的初始化参数
 */
app.addView(container, parameters); //返回一个视图对象</pre>
<div class="blog_h3"><span class="graybg">视图初始化参数详解</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">参数<em>/属性</em></td>
<td style="text-align: center;">类型和默认值</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>dynamicNavbar</td>
<td>boolean = false</td>
<td>仅iOS主题。为视图启用动态导航栏</td>
</tr>
<tr>
<td>url</td>
<td>string = undefined</td>
<td>默认（初始）视图的URL，如果不设置，等同于document的URL</td>
</tr>
<tr>
<td>domCache</td>
<td>boolean = false</td>
<td>设置为true启用内联页面，这样先前访问的页面均保存在导航链中，不从DOM中移除</td>
</tr>
<tr>
<td>linksView</td>
<td>string / View instance = undefined</td>
<td>另外一个视图的CSS选择器，或者已经初始化的View实例，默认的，所有链接在此视图中加载页面<br />该参数可以在运行时动态改变</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>导航</em></td>
</tr>
<tr>
<td>uniqueHistory</td>
<td>boolean</td>
<td rowspan="5">
<p>覆盖相应的应用初始化参数</p>
<p>&nbsp;</p>
</td>
</tr>
<tr>
<td>uniqueHistoryIgnoreGetParameters</td>
<td>boolean = false</td>
</tr>
<tr>
<td>allowDuplicateUrls</td>
<td>boolean</td>
</tr>
<tr>
<td>animatePages</td>
<td>boolean</td>
</tr>
<tr>
<td>preloadPreviousPage</td>
<td>boolean</td>
</tr>
<tr>
<td>reloadPages</td>
<td>boolean = false</td>
<td>如果启用，视图总是重新载入当前的活动页 </td>
</tr>
<tr>
<td>preroute</td>
<td>function(view, options) </td>
<td rowspan="2">覆盖相应的应用初始化参数</td>
</tr>
<tr>
<td>preprocess</td>
<td>function(content, url, next)</td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>  Swipe back（仅iOS主题）<br />覆盖相应的应用初始化参数</em></td>
</tr>
<tr>
<td style="text-align: center;" colspan="3"><em>回调（仅iOS主题）   </em></td>
</tr>
<tr>
<td>onSwipeBackMove </td>
<td>function(callbackData)</td>
<td rowspan="5">执行Swipe back操作时，在各阶段执行这些回调，回调入参callbackData为一对象，包含以下属性：
<ol>
<li><span style="color: #333333; font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: small;"><span style="line-height: 22px;">percentage 移动的百分比</span></span></li>
<li>activePage 当前活动页对应的HTML元素</li>
<li>previousPage 先前（左边）页面对应的HTML元素</li>
<li>activeNavbar 当前导航栏对应的元素</li>
<li>previousNavbar 先前导航栏对应的元素</li>
</ol>
<p>    </p>
</td>
</tr>
<tr>
<td>onSwipeBackBeforeChange</td>
<td>function(callbackData)</td>
</tr>
<tr>
<td>onSwipeBackAfterChange </td>
<td>function(callbackData)</td>
</tr>
<tr>
<td>onSwipeBackBeforeReset</td>
<td>function(callbackData) </td>
</tr>
<tr>
<td>onSwipeBackAfterReset </td>
<td>function(callbackData)</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">视图的属性、方法和事件</span></div>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 28%; text-align: center;">属性/方法/事件</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>params</td>
<td>返回视图初始化参数对象，你可以重写某些参数：<br />
<pre class="crayon-plain-tag">mainView.params.linksView = '.another-view'</pre>
</td>
</tr>
<tr>
<td>history</td>
<td>返回视图历史记录的数组，数组的每个元素是已加载页面的URL</td>
</tr>
<tr>
<td>contentCache</td>
<td>返回缓存的页面，仅在使用动态生成的内容时，该属性有效</td>
</tr>
<tr>
<td>url</td>
<td>返回当前活动页面的URL</td>
</tr>
<tr>
<td>pagesContainer</td>
<td>返回作为页面容器的HTML元素</td>
</tr>
<tr>
<td>activePage</td>
<td>返回当前页面数据（Page data）对象，该对象代表了当前活动页面</td>
</tr>
<tr>
<td>main</td>
<td>如果当前视图是主视图，返回true</td>
</tr>
<tr>
<td>router</td>
<td>返回该视图的路由对象</td>
</tr>
<tr>
<td>hideNavbar()</td>
<td rowspan="2">隐藏或显示导航栏</td>
</tr>
<tr>
<td>showNavbar()</td>
</tr>
<tr>
<td>hideToolbar()</td>
<td rowspan="2">隐藏或显示工具栏</td>
</tr>
<tr>
<td>showToolbar()</td>
</tr>
<tr>
<td>destroy()</td>
<td>销毁已初始化的视图，解除swipe事件监听器，并禁用导航</td>
</tr>
<tr>
<td>⚡swipeBackMove</td>
<td rowspan="5">与上面的几个回调对应</td>
</tr>
<tr>
<td>⚡swipeBackBeforeChange</td>
</tr>
<tr>
<td>⚡swipeBackAfterChange</td>
</tr>
<tr>
<td>⚡swipeBackBeforeReset</td>
</tr>
<tr>
<td>⚡swipeBackAfterReset</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">视图默认URL</span></div>
<p>除了使用初始化参数url，你还可以在视图元素上设置data-url属性，来指定默认URL：</p>
<pre class="crayon-plain-tag">&lt;div class="view" data-url="index2.html"&gt;</pre>
<div class="blog_h3"><span class="graybg">得到当前视图</span></div>
<p>除了主视图外，我们还可能在popup、popover、面板、Tab中定义视图。某些情况下我们需要得到“当前的”视图——即位于<span style="background-color: #c0c0c0;">界面最顶层</span>的视图，这是可以调用：</p>
<pre class="crayon-plain-tag">app.getCurrentView(index)</pre>
<p>如果当前有<span style="background-color: #c0c0c0;">多个活动视图</span> ，例如使用拆分视图布局（Split View layout）时，可以传入index来指明需要返回哪个视图。该函数返回<span style="background-color: #c0c0c0;">视图实例，或者视图的数组</span>。</p>
<div class="blog_h3"><span class="graybg">从DOM访问视图实例</span></div>
<p>F7为<pre class="crayon-plain-tag">&lt;div class="view"&gt;</pre> 元素添加了特殊的属性<pre class="crayon-plain-tag">f7View</pre> ，它指向元素关联的视图实例：</p>
<pre class="crayon-plain-tag">var viewsElement = $$('.view-main')[0];
var viewInstance = viewsElement.f7View;</pre>
<p>使用app.views属性，可以遍历所有视图：</p>
<pre class="crayon-plain-tag">for (var i = 0; i &lt; app.views.length; i ++) {
    var view = app.views[i];
    if (view.main) app.alert('I found main View!')
}</pre>
<div class="blog_h2"><span class="graybg">页面</span></div>
<p>在F7中，页面的概念和传统的Web page类似。下面的代码示意了页面在F7布局里面的位置：</p>
<pre class="crayon-plain-tag">&lt;div class="views"&gt;
    &lt;div class="view view-main"&gt;
        &lt;!-- 所有页面的容器元素 --&gt;
        &lt;div class="pages"&gt;
            &lt;div class="page" data-page="home"&gt;&lt;!-- 页面唯一名称 --&gt;
                &lt;div class="page-content"&gt;页面内容，包括所有F7组件，必须声明在这里&lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;</pre>
<p><span style="background-color: #c0c0c0;">页面必然属于某个视图</span>，页面可以<span style="background-color: #c0c0c0;">静态的编写在主布局文件（内联）</span>中，也可以<span style="background-color: #c0c0c0;">动态的从模板中加载</span>进来，甚至通过API<span style="background-color: #c0c0c0;">动态创建</span>。</p>
<p>和视图类似，在主布局文件（index.html）中，所有<span style="background-color: #c0c0c0;">页面必须在容器</span><pre class="crayon-plain-tag">&lt;div class="pages"&gt;</pre> 里面声明，以确保页面能够正常切换。</p>
<p><pre class="crayon-plain-tag">data-page</pre> 属性指定了页面的唯一名称，该属性在页面回调（page callbacks）中非常有用，有助于鉴别哪个页面被加载。</p>
<p>所有<span style="background-color: #c0c0c0;">F7视觉组件</span>——例如列表、表单——<span style="background-color: #c0c0c0;">必须存放在</span><pre class="crayon-plain-tag">&lt;div class="page-content"&gt;</pre> 元素下，只有这样F7才能正确的确定布局、绘制样式和滚动视图。</p>
<div class="blog_h3"><span class="graybg">页面事件</span></div>
<p>页面导航的关键部分是页面事件，通过页面事件我们可以<span style="background-color: #c0c0c0;">操控刚刚加载的页面</span>，例如执行JS代码。页面事件的Target（事件源）均为<pre class="crayon-plain-tag">&lt;div class="page"&gt;</pre> 元素，可用的事件如下：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">事件</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>pageBeforeInit</td>
<td>当F7把新页面插入到DOM时触发该事件</td>
</tr>
<tr>
<td>pageInit</td>
<td>当F7初始化了新页面中的组件和导航栏后触发该事件</td>
</tr>
<tr>
<td>pageReinit</td>
<td>当缓存的页面再次可见时触发该事件，仅适用于内联页面（ Inline Pages，即DOM cached pages）</td>
</tr>
<tr>
<td>pageBeforeAnimation</td>
<td>当页面初始化完毕后，准备执行动画时，触发该事件</td>
</tr>
<tr>
<td>pageAfterAnimation</td>
<td>当页面动画执行完毕后，触发该事件</td>
</tr>
<tr>
<td>pageBeforeRemove</td>
<td>在页面从DOM中移除时，触发该事件<br />监听该事件，以便移除相关的事件处理函数、插件</td>
</tr>
<tr>
<td>pageBack</td>
<td>与pageBeforeAnimation类似，但是针对old页面——从屏幕中间向右滑出的那个</td>
</tr>
<tr>
<td>pageAfterBack</td>
<td>与pageAfterAnimation类似，但是针对old页面</td>
</tr>
</tbody>
</table>
<p>下面是注册页面事件处理函数的例子：</p>
<pre class="crayon-plain-tag">// 针对所有页面，推荐方式
$$( document ).on( 'pageInit', function( e ) {

} );
// 针对特定页面
$$( document ).on( 'pageInit', '.page[data-page="about"]', function( e ) {
} );</pre>
<div class="blog_h3"><span class="graybg"><a id="page-callback"></a>页面回调</span></div>
<p>除了通过应用初始化参数指定全局性页面回调以外，还可以调用Framework7对象的<span style="background-color: #c0c0c0;">相应方法</span>，来<span style="background-color: #c0c0c0;">针对特定页面回调</span>：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%; text-align: center;">回调</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>app.onPageBeforeInit</td>
<td rowspan="8">这些函数的签名均为：<br />
<pre class="crayon-plain-tag">function(pageName, callback(page));</pre></p>
<p>其中：</p>
<ol>
<li>pageName：该回调针对的<span style="background-color: #c0c0c0;">页面的名称</span>，可以用<span style="background-color: #c0c0c0;">空格分隔多个页面</span>。如果指定<span style="background-color: #c0c0c0;">*则针对所有页面</span></li>
<li>callback(page)：回调函数，入参是页面数据对象，返回值是一个callbackObject对象</li>
</ol>
<p>callbackObject对象具有以下方法：</p>
<ol>
<li><pre class="crayon-plain-tag">trigger()</pre> 手工触发回调函数</li>
<li><pre class="crayon-plain-tag">remove()</pre> 移除回调函数</li>
</ol>
<p>&nbsp;</p>
</td>
</tr>
<tr>
<td>app.onPageInit</td>
</tr>
<tr>
<td>app.onPageReinit</td>
</tr>
<tr>
<td>app.onPageBeforeAnimation</td>
</tr>
<tr>
<td>app.onPageAfterAnimation</td>
</tr>
<tr>
<td>app.onPageBeforeRemove</td>
</tr>
<tr>
<td>app.onPageBack</td>
</tr>
<tr>
<td>app.onPageAfterBack</td>
</tr>
</tbody>
</table>
<p>如果要<span style="background-color: #c0c0c0;">针对特定页面</span>执行逻辑，页面回调是非常好的选择。页面回调比起页面事件有如下<span style="background-color: #c0c0c0;">优势</span>：</p>
<ol>
<li>回调不是事件，意味着更少的内存使用、更低几率的内存泄漏。同样，你不需要考虑何时解除注册</li>
<li>代码结构角度来看，更加便利</li>
</ol>
<div class="blog_h3"><span class="graybg">针对初始页面（Inital Pages）的回调</span></div>
<p>有时我们需要对<span style="background-color: #c0c0c0;">初始页面</span>——即内联在index.html中的页面——进行回调，这些页面已经<span style="background-color: #c0c0c0;">随着应用的初始化而初始化</span>，后续注册的回调无法拦截。这时，我们可以手工初始化应用， 并在之前注册回调：</p>
<pre class="crayon-plain-tag">var app = new Framework7({
  init: false // 禁止自动初始化
});          
 
// 注册回调
myApp.onPageInit('home', function (page) {
});
 
myApp.init(); //手工初始化应用</pre>
<div class="blog_h3"><span class="graybg">页面数据</span></div>
<p> 在页面回调（包括应用初始化参数）、页面事件中，可以访问所谓“页面数据（Page data）”对象，该对象包含页面的相关信息：</p>
<pre class="crayon-plain-tag">//从回调中访问页面数据
app.onPageInit('about', function (page) {
});
 
// 从事件处理函数中访问页面数据
$$(document).on('pageInit', function (e) {
  var page = e.detail.page;
});</pre>
<p>页面数据对象包含以下属性：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>name</td>
<td>string ，页面名称，对应页面元素的data-page属性</td>
</tr>
<tr>
<td>url</td>
<td>string ，页面的URL</td>
</tr>
<tr>
<td>query</td>
<td>object ， 页面URL的查询参数部分转换为的对象，例如<pre class="crayon-plain-tag">index.html?id=10&amp;count=20&amp;color=blue</pre> 会转换为：<br />
<pre class="crayon-plain-tag">{
    id: '10',
    count: '20',
    color: 'blue'
}</pre>
</td>
</tr>
<tr>
<td>view</td>
<td>object ， 该页面所属的视图实例</td>
</tr>
<tr>
<td>container</td>
<td>HTMLElement，页面对应的HTML元素</td>
</tr>
<tr>
<td>from</td>
<td>string，页面“来自的方向”，对于新加载的页面，该属性值为“right”；对于通过Back按钮加载的历史页面，该属性值为“left”</td>
</tr>
<tr>
<td>navbarInnerContainer</td>
<td>HTMLElement，与此页面相关的navbar-inner元素，仅用于动态导航栏（Dynamic Navbars）</td>
</tr>
<tr>
<td>swipeBack</td>
<td>boolean，如果为真，则表示动画由Swipe back触发。仅适用于onPageBefore/AfterAnimation回调和事件</td>
</tr>
<tr>
<td>context</td>
<td>object，当使用Template7模板时，传递给当前页面的Template7上下文对象</td>
</tr>
<tr>
<td>fromPage</td>
<td>上一个活动页的页面数据对象 </td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg"><a id="navigation"></a>导航/路由</span></div>
<p>F7的页面导航（Navigation ）功能相当灵活，你可以通过多种方式处理页面：</p>
<ol>
<li>Ajax页面（Ajax Pages）：从其它文件中加载页面，这是<span style="background-color: #c0c0c0;">默认行为</span></li>
<li>动态页面（Dynamic Pages）：可以使用JavaScript API动态的创建并加载页面</li>
<li>内联页面（Inline Pages）：页面的内容可以直接编写在当前HTML里面，不需要额外的加载行为</li>
<li><a href="/framework7-study-note-3#template7-pages">Template7页面</a>：可以通过Template7模板加载页面。注意这并不是一种独立的页面类型，只是基于T7模板来解析Ajax页面和动态页面</li>
<li>以上方式可以混合使用</li>
</ol>
<p>前面我们提到，F7中的视图是应用中独立的可视化部分，可以具有自己的导航和历史。<span style="background-color: #c0c0c0;">导航路由器</span>（Navigation router）连接到特定视图，<span style="background-color: #c0c0c0;">作为视图的一部分</span>，负责页面路由、导航工作。</p>
<div class="blog_h2"><span class="graybg">视图导航API</span></div>
<div class="blog_h3"><span class="graybg"><a id="nav-methods"></a>导航方法</span></div>
<p>F7主要提供了两个方法，用于执行路由：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 28%; text-align: center;">方法</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>view.router.load(options)</td>
<td>加载请求的页面到视图中，同时触发动画效果</td>
</tr>
<tr>
<td>view.router.back(options)</td>
<td>返回导航历史中的上一个页面，同时触发动画效果</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg"><a id="nav-params"></a>导航参数</span></div>
<p>上面两个方法的options参数是一个配置对象，支持以下配置参数：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 18%; text-align: center;">参数</td>
<td style="width: 25%; text-align: center;">类型</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>url</td>
<td>string</td>
<td>目标页面的URL</td>
</tr>
<tr>
<td>content</td>
<td>string/HTMLElement/<br />jQuery Collection/<br />DOM7 Collection/<br />HTMLElement[]</td>
<td>路由到动态页面：动态页面的内容 </td>
</tr>
<tr>
<td>pageName</td>
<td>string</td>
<td>路由到内联页面：内联页面的名称，即data-page属性的值 </td>
</tr>
<tr>
<td>template</td>
<td>function(Template7已编译模板)</td>
<td>路由到动态页面：需要被链接并加载的Template7模板 </td>
</tr>
<tr>
<td colspan="3"><em>注：以上4个参数必须<span style="background-color: #c0c0c0;">互斥</span>的使用</em></td>
</tr>
<tr>
<td>context</td>
<td>object/array</td>
<td>Template7模板需要链接到的上下文对象</td>
</tr>
<tr>
<td>contextName</td>
<td>string</td>
<td> Template7模板需要链接到的上下文对象，为template7Data中的某个简单数据对象，或者其子对象（点号导航）</td>
</tr>
<tr>
<td>query</td>
<td>object</td>
<td>额外的查询参数，这些参数以后可以通过页面数据的query属性取回</td>
</tr>
<tr>
<td>force</td>
<td>boolean</td>
<td>仅用于back()方法，设置为true会忽视既有的历史记录，强制重新加载页面<br />默认的，当前页来自的那个页面会驻留在DOM中，不消除，因此重新不加载也可以显示之</td>
</tr>
<tr>
<td>ignoreCache</td>
<td>boolean</td>
<td>如果设置为true，忽略已经缓存的URL，通过XHR强制加载 </td>
</tr>
<tr>
<td>animatePages</td>
<td>boolean</td>
<td>覆盖对应的视图初始化参数 </td>
</tr>
<tr>
<td>reload</td>
<td>boolean</td>
<td>如果设置为true，不会把目标页面加载为新页面，而是仅仅替换掉<span style="background-color: #c0c0c0;">当前页面</span>，并替换<span style="background-color: #c0c0c0;">视图历史</span>中最新条目</td>
</tr>
<tr>
<td>reloadPrevious</td>
<td>boolean</td>
<td>与reload类似，但是替换视图历史中上一个页面</td>
</tr>
<tr>
<td>pushState</td>
<td>boolean</td>
<td>是否把加载的页面压入记录到浏览器的历史中 ，用于哈希导航</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg"><a id="nav-shortcuts"></a>导航快捷方法</span></div>
<p>除了load/back以外，F7还提供了一系列的快捷方法：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 35%; text-align: center;">快捷方法</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center;" colspan="2"><em>加载新页面的快捷方法</em></td>
</tr>
<tr>
<td>view.router.loadPage(url)</td>
<td>从指定URL加载页面到视图，在<pre class="crayon-plain-tag">&lt;a&gt;</pre> 元素上点击通常具有一样的效果</td>
</tr>
<tr>
<td>view.loadContent(content)</td>
<td>加载动态页面，content为新的页面的内容，可以是string、HTMLElement、HTMLElement[]、jQuery/Dom7元素集合</td>
</tr>
<tr>
<td style="text-align: center;" colspan="2">重新载入当前页面的快捷方法</td>
</tr>
<tr>
<td>view.router.reloadPage(url)</td>
<td rowspan="2">
<p>与上面两个快捷方法类似，但是替换掉当前页面（同时修改视图的历史记录），而不是加入新页面</p>
</td>
</tr>
<tr>
<td>view.router.reloadContent(content)</td>
</tr>
<tr>
<td style="text-align: center;" colspan="2">重新载入上一个页面（如果视图的DOM中存在两个页面）的快捷方法</td>
</tr>
<tr>
<td>view.router.reloadPreviousPage(url)</td>
<td rowspan="2">与上面两个快捷方法类似，但是替换的是上一个页面</td>
</tr>
<tr>
<td>view.router.reloadPreviousPage(url)</td>
</tr>
<tr>
<td style="text-align: center;" colspan="2">刷新页面的快捷方法</td>
</tr>
<tr>
<td>view.router.refreshPage()</td>
<td rowspan="2">相当于使用与当前页面相同的URL执行reload操作</td>
</tr>
<tr>
<td>view.router.refreshPreviousPage()</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">Ajax页面</span></div>
<p>默认的，Framework7会<span style="background-color: #c0c0c0;">使用XHR加载所有链接</span>，除非链接元素上标记样式类：<pre class="crayon-plain-tag">&lt;a class="external"&gt;</pre> ，或者<span style="background-color: #c0c0c0;">链接的href为空或者#</span>。应用初始化参数ajaxLinks可以改变此默认行为。因此，点击一个链接元素后，通常F7会：</p>
<ol>
<li>发送XHR请求到目标页面</li>
<li>解析目标页面</li>
<li>插入到目标页面到当前DOM中</li>
<li>执行动画以切换（transition）到新页面</li>
</ol>
<div class="blog_h3"><span class="graybg">单页面的结构</span></div>
<p>F7的页面解析器很智能，因此供内部使用的页面<span style="background-color: #c0c0c0;">不需要是完整的HTML</span>（可以不包含html、head、body）、也<span style="background-color: #c0c0c0;">不需要包含完整的F7布局</span>（不需要views、pages等）。考虑下面的页面：</p>
<pre class="crayon-plain-tag">&lt;div class="page" data-page="about"&gt;
    Content
&lt;/div&gt;</pre>
<p>F7的页面解析器<span style="background-color: #c0c0c0;">仅仅尝试</span>找到XHR加载得到的HTML中的<pre class="crayon-plain-tag">&lt;div class="page"&gt;</pre> 元素，并将其解析为页面。</p>
<div class="blog_h3"><span class="graybg">多页面的结构</span></div>
<p>F7允许<span style="background-color: #c0c0c0;">多个视图加载同一个URL，却显示不同的内容</span>。考虑下面的布局：</p>
<pre class="crayon-plain-tag">&lt;div class="views"&gt;
    &lt;!-- 左视图 --&gt;
    &lt;div class="view view-main left-view"&gt;
        &lt;a href="about.html"&gt; About &lt;/a&gt;
    &lt;/div&gt;
    &lt;!-- 右视图 --&gt;
    &lt;div class="view right-view"&gt;
        &lt;a href="about.html"&gt; About &lt;/a&gt;
    &lt;/div&gt;
&lt;/div&gt;</pre>
<p> 左右两个视图都包含执行about.html链接。about.html则可以针对不同视图提供差异化内容：</p>
<pre class="crayon-plain-tag">&lt;div class="view view-main left-view"&gt;
    &lt;div class="page" data-page="about-right"&gt;左视图显示的内容&lt;/div&gt;
&lt;/div&gt;
&lt;div class="view right-view"&gt;
    &lt;div class="page" data-page="about-right"&gt;右视图显示的内容&lt;/div&gt;
&lt;/div&gt;</pre>
<p>这里的关键之处是，你需要使用view元素包裹page元素。view元素的<span style="background-color: #c0c0c0;">CSS样式类必须和页面对应的视图一致</span>。 </p>
<div class="blog_h3"><span class="graybg">动态导航栏页面的结构</span></div>
<p>本节内容仅适用于iOS主题。</p>
<p>动态导航栏元素是独立在页面元素之外的，如果你需要在新页面中指定导航栏内容，可以：</p>
<pre class="crayon-plain-tag">&lt;div class="navbar"&gt;导航栏内容&lt;/div&gt;
&lt;div class="page" data-page="about"&gt;页面内容&lt;/div&gt;</pre>
<p>在使用多页面时，可以把上面两者包装到同一个view元素中。</p>
<div class="blog_h3"><span class="graybg">返回到原先的页面</span></div>
<p>可以在链接上特殊样式类back，这样链接被点击后，自动返回上一个页面：</p>
<pre class="crayon-plain-tag">&lt;div class="page" data-page="about"&gt;
    &lt;a href="index.html" class="back"&gt; Go back to home page &lt;/a&gt;
&lt;/div&gt;</pre>
<p>如果导航历史中<span style="background-color: #c0c0c0;">存在其他页面</span>，那么<span style="background-color: #c0c0c0;">href属性被忽略</span>。否则F7通过href载入“前一个”页面。</p>
<div class="blog_h3"><span class="graybg">禁用Swipe back</span></div>
<p>我们可以为特定页面禁用Swipe back：<pre class="crayon-plain-tag">&lt;div class="page no-swipeback"&gt;&lt;/div&gt;</pre> 。</p>
<div class="blog_h3"><span class="graybg">针对页面启/禁动画</span></div>
<p>应用程序初始化参数noAnimate可以在全局范围禁用页面切换动画。</p>
<p>我们也可以针对页面启/禁动画：</p>
<pre class="crayon-plain-tag">&lt;div class="page" data-page="about"&gt;
    &lt;!-- 禁用动画 --&gt;
    &lt;a href="about.html" class="no-animation"&gt; Load About page immediately &lt;/a&gt;
    &lt;!-- 对返回链接禁用动画 --&gt;
    &lt;a href="index.html" class="back no-animation"&gt; Go back immediately &lt;/a&gt;
    &lt;!-- 启用动画 --&gt;
    &lt;a href="about.html" class="with-animation"&gt; Load About page with animation &lt;/a&gt;
    &lt;!-- 对返回链接启用动画 --&gt;
    &lt;a href="index.html" class="back with-animation"&gt; Go back with animation &lt;/a&gt;
&lt;/div&gt;</pre>
<div class="blog_h3"><span class="graybg">使用额外导航参数加载页面</span></div>
<p>把驼峰式大小写的<a href="#nav-params">导航参数</a>改为连字符+小写格式，附加<pre class="crayon-plain-tag">data-</pre> 前缀，就可以在<pre class="crayon-plain-tag">&lt;a&gt;</pre> 元素的属性中声明<span style="background-color: #c0c0c0;">任意额外的</span>导航参数：</p>
<pre class="crayon-plain-tag">&lt;!-- reload=true,ignoreCache=true --&gt;
&lt;a href="about.html" data-reload="true" data-ignore-cache="true"&gt;&lt;/a&gt;
&lt;!-- force=true --&gt;
&lt;a href="about.html" class="back" data-force="true"&gt;&lt;/a&gt;
&lt;!-- animatePages=true --&gt;
&lt;a href="about.html" data-animate-pages="false"&gt;&lt;/a&gt;
&lt;!-- 导航到已有T7模板 --&gt;
&lt;a href="#" data-template="contactsTemplate"&gt;Contacts&lt;/a&gt;</pre>
<div class="blog_h3"><span class="graybg">使用JS加载页面或返回</span></div>
<p>与HTML链接元素等价的JS加载/返回页面方式是：</p>
<pre class="crayon-plain-tag">view.router.loadPage('about.html');
view.router.back();</pre>
<div class="blog_h2"><span class="graybg">动态页面</span></div>
<p>F7允许即使的创建并加载动态页面，而不进行额外的XHR请求。动态页面无法通过 <pre class="crayon-plain-tag">&lt;a&gt;</pre> 元素完成，必须使用路由API（两个<a href="#nav-methods">导航方法</a>reload/back或者相应<a href="#nav-shortcuts">快捷方法</a>）。</p>
<p>关于Ajax页面结构的说明，完全适用于动态页面。</p>
<div class="blog_h3"><span class="graybg">在JS中编写动态页面</span></div>
<pre class="crayon-plain-tag">var mainView = new Framework7().addView('.view-main');
// 动态页面的内容，结构必须符合F7规范
var newPageContent = '&lt;div class="page" data-page="my-page"&gt;' +
                        '&lt;div class="page-content"&gt;' +
                          '&lt;p&gt;Here comes new page&lt;/p&gt;' +
                        '&lt;/div&gt;' +
                      '&lt;/div&gt;';
 
//加载页面
mainView.router.loadContent(newPageContent);
//另一种方式
mainView.router.load({
  content: newPageContent,
  animatePages: false
});</pre>
<div class="blog_h3"><span class="graybg">使用内联模板</span></div>
<p>在JavaScript中编写HTML很麻烦，我们可以在主布局文件中声明：</p>
<pre class="crayon-plain-tag">&lt;script type="text/template" id="myPage"&gt;
    &lt;div class="navbar"&gt;
        &lt;div class="navbar-inner"&gt;
            &lt;div class="center"&gt;My Page&lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
    &lt;div class="page" data-page="my-page"&gt;
        &lt;div class="page-content"&gt;
            &lt;p&gt;Here comes page content&lt;/p&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/script&gt;</pre>
<p>然后通过路由API加载： </p>
<pre class="crayon-plain-tag">view.router.loadContent($('#myPage').html());</pre>
<div class="blog_h3"><span class="graybg">动态页面的URL </span></div>
<p>默认情况下动态页面的URL属于哈希导航，遵循<pre class="crayon-plain-tag">#content-{{index}}</pre> 格式，其中index是导航历史中的页面索引号。该行为可以通过应用初始化参数dynamicPageUrl修改。</p>
<div class="blog_h2"><span class="graybg">内联页面（DOM缓存）</span></div>
<p>内联页面指直接声明在主布局文件（index.html）中的页面。这种页面不需要额外的Ajax加载动作/动态创建动作，会随着应用的初始化自动加载。</p>
<p>默认情况下，内联页面是禁用的，要<span style="background-color: #c0c0c0;">为某个视图启用</span>内联页面功能，你需要：</p>
<pre class="crayon-plain-tag">var mainView = myApp.addView('.view-main', {
    domCache: true //启用内联页面功能
});</pre>
<p>如果不启用domCache参数，则无法通过<pre class="crayon-plain-tag">#pageName</pre> 导航到其它内联页面。</p>
<div class="blog_h3"><span class="graybg">内联页面DOM结构</span></div>
<p>必须为<span style="background-color: #c0c0c0;">非当前活动页面</span>添加额外的cached样式类。<span style="background-color: #c0c0c0;">最后一个</span>没有标注cached的内联页面会自动显示：</p>
<pre class="crayon-plain-tag">&lt;div class="views"&gt;
    &lt;div class="view view-main"&gt;
        &lt;div class="pages"&gt;
            &lt;!-- 内联页面，当前活动页面 --&gt;
            &lt;div class="page" data-page="index"&gt;
                &lt;div class="page-content"&gt;
                    &lt;p&gt;Home page&lt;/p&gt;
                &lt;/div&gt;
            &lt;/div&gt;

            &lt;!-- 已缓存的内联页面 --&gt;
            &lt;div class="page cached" data-page="about"&gt;
                &lt;div class="page-content"&gt;
                    &lt;p&gt;About page&lt;/p&gt;
                &lt;/div&gt;
            &lt;/div&gt;

            &lt;!-- 已缓存的内联页面 --&gt;
            &lt;div class="page cached" data-page="services"&gt;
                &lt;div class="page-content"&gt;
                    &lt;p&gt;Services page&lt;/p&gt;
                &lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;</pre>
<div class="blog_h3"><span class="graybg">使用动态导航栏时的内联页面的DOM结构 </span></div>
<p>本节内容仅适用于iOS主题。</p>
<p>动态导航栏元素是独立在页面元素之外的，要为每个内联页面定制动态导航栏，需要额外的HTML标签：</p>
<pre class="crayon-plain-tag">&lt;div class="views"&gt;
    &lt;div class="view view-main"&gt;
        &lt;!-- 所有内联页面的导航栏设置 --&gt;
        &lt;div class="navbar"&gt;
            &lt;!-- 页面1的导航栏，注意data-page属性和对应的页面保持一致 --&gt;
            &lt;div class="navbar-inner" data-page="index"&gt;&lt;div class="center"&gt;Home&lt;/div&gt;&lt;/div&gt;
            &lt;!-- 页面2的导航栏，注意已缓存的页面的导航栏会加上cached标签 --&gt;
            &lt;div class="navbar-inner cached" data-page="about"&gt;&lt;div class="center"&gt;About&lt;/div&gt;&lt;/div&gt;
            &lt;!-- 页面3的导航栏 --&gt;
            &lt;div class="navbar-inner cached" data-page="services"&gt;&lt;div class="center"&gt;Services&lt;/div&gt;&lt;/div&gt;
        &lt;/div&gt;
        &lt;!-- 所有内联页面 --&gt;
        &lt;div class="pages navbar-through"&gt;
            &lt;!-- 页面1（当前） --&gt;
            &lt;div class="page" data-page="index"&gt;&lt;div class="page-content"&gt;&lt;p&gt;Home page&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
            &lt;!-- 页面2 （已缓存）--&gt;
            &lt;div class="page cached" data-page="about"&gt;&lt;div class="page-content"&gt;&lt;p&gt;About page&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
            &lt;!-- 页面3（已缓存） --&gt;
            &lt;div class="page cached" data-page="services"&gt;&lt;div class="page-content"&gt;&lt;p&gt;Services page&lt;/p&gt;&lt;/div&gt;
            &lt;/div&gt;
        &lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;</pre>
<div class="blog_h3"><span class="graybg">内联页面的URL</span></div>
<p>内联页面的URL属于哈希导航，遵循<pre class="crayon-plain-tag">#{{pageName}}</pre> 格式，我们可以使用下面的URL链接到内联页面about：</p>
<pre class="crayon-plain-tag">&lt;a href="#about"&gt;Go to About page&lt;/a&gt;</pre>
<div class="blog_h3"><span class="graybg">通过JS加载内联页面</span></div>
<pre class="crayon-plain-tag">view.router.load({pageName: 'about'});</pre>
<div class="blog_h2"><span class="graybg">跨视图链接</span></div>
<p>使用JavaScript API，可以很方便的使用view实例加载任何页面。此外，F7还允许点击视图1中的某个链接，而把目标页面加载到视图2中，这就是所谓跨视图链接：</p>
<pre class="crayon-plain-tag">&lt;!-- 只需要通过data-view属性指定目标视图的CSS选择器，就可以进行跨视图链接 --&gt;
&lt;a href="products.html" data-view=".right-view"&gt; Products &lt;/a&gt;
&lt;!-- 右视图 --&gt;
&lt;div class="view right-view"&gt;...&lt;/div&gt;</pre>
<p>对于“back”链接，同样可以使用data-view属性。</p>
<div class="blog_h1"><span class="graybg">样式（Styling）</span></div>
<div class="blog_h2"><span class="graybg">色彩方案</span></div>
<p>F7为iOS主题、Material主题预置了<span style="background-color: #c0c0c0;">多套色彩方案</span>。并分别提供<span style="background-color: #c0c0c0;">深/浅两套</span>布局主题（Layout Theme）。</p>
<p>要启用色彩方案的支持，必须在F7主样式表文件后附加样式表文件：</p>
<pre class="crayon-plain-tag">&lt;!-- iOS主题色彩方案样式表 --&gt;
&lt;link rel="stylesheet" href="path/to/framework7.ios.colors.min.css"&gt;
&lt;!-- Material主题色彩方案样式表 --&gt;
&lt;link rel="stylesheet" href="path/to/framework7.material.colors.min.css"&gt;</pre>
<div class="blog_h3"><span class="graybg">色彩方案列表</span></div>
<p> <img class="aligncenter size-large wp-image-10969" src="https://blog.gmem.cc/wp-content/uploads/2015/12/f7-colors-521x1024.png" alt="f7-colors" width="521" height="1024" /></p>
<div class="blog_h3"><span class="graybg">应用色彩方案</span></div>
<p>可以在多个元素上应用色彩方案，只需要添加相应的<pre class="crayon-plain-tag">theme-*</pre> 样式类：</p>
<pre class="crayon-plain-tag">&lt;body class="theme-red"&gt;&lt;/body&gt; &lt;!-- 全局 --&gt;
&lt;div class="page theme-green"&gt;&lt;/div&gt; &lt;!-- 页面 --&gt;
&lt;div class="list-block theme-pink"&gt;&lt;/div&gt; &lt;!-- 列表 --&gt;
&lt;div class="navbar theme-orange"&gt;&lt;/div&gt; &lt;!-- 导航栏 --&gt;
&lt;div class="buttons-row theme-yellow"&gt;&lt;/div&gt; &lt;!-- 按钮 --&gt;</pre>
<div class="blog_h3"><span class="graybg">应用布局主题 </span></div>
<p>可以指定应用是深色风格还是浅色风格：</p>
<pre class="crayon-plain-tag">&lt;body class="layout-dark"&gt;&lt;/body&gt;
&lt;div class="page layout-white"&gt;&lt;/div&gt;
&lt;div class="list-block layout-dark"&gt;&lt;/div&gt;</pre>
<div class="blog_h3"><span class="graybg">辅助样式类</span></div>
<p>F7提供了若干与色彩方案相关的辅助样式类：</p>
<ol>
<li>color-*：指定元素的文本颜色</li>
<li>bg-*：指定元素的背景颜色</li>
<li>border-*：指定元素的边框颜色 </li>
</ol>
<p>注意，不支持在指定色彩方案的容器内指定辅助样式类：</p>
<pre class="crayon-plain-tag">&lt;div class="theme-red"&gt;
    &lt;a href="#" class="color-blue"&gt;链接将是红色的，而不是蓝色&lt;/a&gt;
&lt;/div&gt;</pre>
<div class="blog_h2"><span class="graybg">边框（细线）</span></div>
<p> 从F7的1.x版本开始，支持使用<pre class="crayon-plain-tag">:after</pre> 和<pre class="crayon-plain-tag">:before</pre> 伪元素代替CSS边框。这样就可以支持iOS视网膜屏幕0.5px、0.33px（iPhone 6P）的细线。</p>
<p>伪元素使用规则很简单：</p>
<ol>
<li>:after对应bottom、right细线</li>
<li>:before对应left、top细线</li>
</ol>
<div class="blog_h3"><span class="graybg">使用示例</span></div>
<pre class="crayon-plain-tag">/* 修改导航栏底部细线为红色 */
.navbar:after {
  background-color: red;
}
/* 移除导航栏、工具栏的细线 */
.navbar:after {
  display:none;
}
.toolbar:before {
  display:none;
}</pre>
<div class="blog_h3"><span class="graybg"><strong>no-border类 </strong></span></div>
<p>该样式类也可以用于移除细线，但是目前不支持导航栏、工具栏、卡片（包括头/尾）。</p>
<div class="blog_h1"><span class="graybg">快速点击库</span></div>
<p>本章介绍的功能是F7内置的快速点击库的一部分，必须在应用初始化参数中启用快速点击才可以使用。</p>
<div class="blog_h2"><span class="graybg">活动状态</span></div>
<p>F7使用所谓活动状态（active state） 来高亮显示被触碰的按钮与链接。</p>
<p>类似于CSS选择器<pre class="crayon-plain-tag">:active</pre> ，当你<span style="background-color: #c0c0c0;">触碰元素并持续一小段时间</span>后，F7会自动添加<pre class="crayon-plain-tag">active-state</pre> 样式类。通过定制CSS，可以为被触碰的元素添加额外样式：</p>
<pre class="crayon-plain-tag">/* 一般状态 */
.my-button {
    color: red;
}
/* 活动状态（触碰） */
.my-button.active-state {
    color: blue;
}</pre>
<div class="blog_h2"><span class="graybg">长按事件</span></div>
<p> F7支持触碰并保持（Tap Hold）事件，亦即长按（Long Tap）事件。此功能默认禁用，可以设置应用初始化参数tapHold、tapHoldDelay、tapHoldPreventClicks以启用并定制。</p>
<p>你可能需要禁用移动浏览器默认的长按动作，向目标元素添加额外样式规则：</p>
<pre class="crayon-plain-tag">-moz-user-select: none;        
-webkit-user-select: none;        
user-select: none;</pre>
<p> 你可以监听<pre class="crayon-plain-tag">taphold</pre> 事件，指定长按时需要执行的动作。</p>
<div class="blog_h2"><span class="graybg">波纹效果</span></div>
<p>仅用于Material主题，可以在触碰点生成一个扩散的涟漪效果。 此效果默认全局范围内启用，设置应用初始化参数<pre class="crayon-plain-tag">materialRipple:false</pre> 则可禁用。哪些元素启用此效果，则由应用初始化参数<pre class="crayon-plain-tag">materialRippleElements</pre> 指定。</p>
<p>要为某个元素启用波纹效果，可以：</p>
<pre class="crayon-plain-tag">&lt;a href="#" class="my-link ripple"&gt;Link With Ripple Effect&lt;/a&gt;</pre>
<p>要为某个元素禁用波纹效果，可以：</p>
<pre class="crayon-plain-tag">&lt;a href="#" class="button no-ripple"&gt;Button Without Ripple Effect&lt;/a&gt;</pre>
<div class="blog_h3"><span class="graybg">指定波纹颜色</span></div>
<p>可以使用内置的<pre class="crayon-plain-tag">ripple-*</pre> 样式类，或者自定义：</p>
<pre class="crayon-plain-tag">.button .ripple-wave {
    background-color: #ff0000;
}</pre>
<div class="blog_h1"><span class="graybg">实用工具</span></div>
<div class="blog_h2"><span class="graybg">设备API</span></div>
<p>F7内置了一个设备检测库，在应用初始化后，你可以访问<pre class="crayon-plain-tag">app.device</pre> 对象。该对象包含若干与设备相关的有用信息：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 15%; text-align: center;">属性</td>
<td style="width: 15%; text-align: center;">类型</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>os</td>
<td>string</td>
<td>操作系统类型， 可以是android、ios。对于其它任何系统，均为undefined</td>
</tr>
<tr>
<td>osVersion</td>
<td>string</td>
<td>操作系统版本字符串</td>
</tr>
<tr>
<td>android</td>
<td>boolean</td>
<td>是否为安卓设备</td>
</tr>
<tr>
<td>ios</td>
<td>boolean</td>
<td>是否为iOS设备</td>
</tr>
<tr>
<td>ipad</td>
<td>boolean</td>
<td>是否为iPad</td>
</tr>
<tr>
<td>iphone</td>
<td>boolean</td>
<td>是否为iPhone</td>
</tr>
<tr>
<td>pixelRatio</td>
<td>number</td>
<td>像素比率，在高分屏中，多少物理像素对应一个逻辑像素</td>
</tr>
<tr>
<td>webView</td>
<td>boolean</td>
<td>如果应用程序运行在UIWebView中，则为true。使用Phonegap时，此属性为true</td>
</tr>
<tr>
<td>minimalUi</td>
<td>boolean</td>
<td>minimal-ui模式是否处于启用状态</td>
</tr>
<tr>
<td>statusBar</td>
<td>boolean</td>
<td>仅iOS，当应用在全屏模式下运行，并且需要状态栏Overlay时为true</td>
</tr>
</tbody>
</table>
<p>要在应用初始化之前使用设备API，可以：</p>
<pre class="crayon-plain-tag">var device = Framework7.prototype.device;
if (device.iphone) {
    console.log('this is iPhone')
}</pre>
<div class="blog_h3"><span class="graybg">额外的样式类 </span></div>
<p>设备API会自动为html元素添加额外的样式类，便于开发者针对不同设备定制UI： </p>
<pre class="crayon-plain-tag">&lt;!-- 为iOS 7.1设备添加的额外样式类 --&gt;
&lt;html class="ios ios-7 ios-7-1 ios-gt-6 pixel-ratio-1"&gt;
&lt;!-- 为iOS 7.1视网膜屏、运行全屏应用程序时添加的样式类 --&gt;
&lt;html class="ios ios-7 ios-7-1 ios-gt-6 retina pixel-ratio-2 with-statusbar-overlay"&gt;
&lt;!-- 为iOS 8.0的iPhone 6 Plus添加的样式类 --&gt;
&lt;html class="ios ios-8 ios-8-0 ios-gt-6 ios-gt-7 retina pixel-ratio-3 with-statusbar-overlay"&gt;
&lt;!-- 为 Android 4.4 设备添加的额外样式类 --&gt;
&lt;html class="android android-4 android-4-4"&gt;</pre>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/framework7-study-note-1">Framework7学习笔记（一）：基础</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/framework7-study-note-1/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>HTTP知识集锦</title>
		<link>https://blog.gmem.cc/http-faq</link>
		<comments>https://blog.gmem.cc/http-faq#comments</comments>
		<pubDate>Mon, 27 May 2013 02:39:49 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[HTML]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Network]]></category>
		<category><![CDATA[CORS]]></category>
		<category><![CDATA[HTTP]]></category>
		<category><![CDATA[JsonP]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=6288</guid>
		<description><![CDATA[<p>Cookies相关知识 Cookies是一种最常使用的客户端存储机制，Cookies由Netscape在1994年提出，用来为HTTP协议引入状态机制。 为了使基于Cookies的状态机制能够工作，Web服务器会在HTTP响应的Set-Cookie头中设置Cookie，保存用户身份、状态信息，而浏览器在下一次发送HTTP请求的时候，会把先前Web服务器设置的Cookie绑定在HTTP请求头中，发送回去。 由于每次HTTP请求都会重复的发送Cookies，所有Cookies不适合存放过大的数据，事实上，大部分浏览器对每个域名可设置Cookies的大小、都有严格限制，通常在数十个、几KB以内。 使用Cookies的注意点 Cookies的Domain可以设置为IP地址，但是不能使用通配符，例如.0.1这样的Domain是非法的 Domain：域名至少包含两个点号。域设置为.gmem.cc的Cookie可以让gmem.cc域名下所有网站使用，例如blog.gmem.cc Path：限制使用Cookie的网页目录，路径设置为/wp-admin/的Cookie可以让gmem.cc/wp-admin/下所有子URL对应的网页访问 Secure：可以限制Cookie只能通过加密连接（SSL）发送 Expires：可以限制Cookie的生存期，如果不设置，那么浏览器关闭后Cookie即消失 同源策略 所谓源，是指由三元组：协议 + 域名 + 端口确定的唯一网络实体。 修改源 通过JavaScript可以修改当前页面的源，但是只能域名为当前域名的超级域名： [crayon-69e29a4146f85112194017/] 修改后，同源策略检查结果将受到影响。使用这种技术，可以让位于子域名中的多个网站方便的交互。 跨源资源访问 原则： <a class="read-more" href="https://blog.gmem.cc/http-faq">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/http-faq">HTTP知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div id="cookie-faq" class="blog_h2"><span class="graybg">Cookies相关知识</span></div>
<p>Cookies是一种最常使用的客户端存储机制，Cookies由Netscape在1994年提出，用来为HTTP协议引入状态机制。</p>
<p>为了使基于Cookies的状态机制能够工作，Web服务器会在HTTP响应的Set-Cookie头中设置Cookie，保存用户身份、状态信息，而浏览器在下一次发送HTTP请求的时候，会把先前Web服务器设置的Cookie绑定在HTTP请求头中，发送回去。</p>
<p>由于每次HTTP请求都会重复的发送Cookies，所有Cookies不适合存放过大的数据，事实上，大部分浏览器对每个域名可设置Cookies的大小、都有严格限制，通常在数十个、几KB以内。</p>
<div class="blog_h3"><span class="graybg">使用Cookies的注意点</span></div>
<ol>
<li>Cookies的Domain可以设置为IP地址，但是不能使用通配符，例如.0.1这样的Domain是非法的</li>
<li>Domain：域名至少包含两个点号。域设置为.gmem.cc的Cookie可以让gmem.cc域名下所有网站使用，例如blog.gmem.cc</li>
<li>Path：限制使用Cookie的网页目录，路径设置为/wp-admin/的Cookie可以让gmem.cc/wp-admin/下所有子URL对应的网页访问</li>
<li>Secure：可以限制Cookie只能通过加密连接（SSL）发送</li>
<li>Expires：可以限制Cookie的生存期，如果不设置，那么浏览器关闭后Cookie即消失</li>
</ol>
<div class="blog_h2"><span class="graybg">同源策略</span></div>
<p>所谓源，是指由三元组：协议 + 域名 + 端口确定的唯一网络实体。</p>
<div class="blog_h3"><span class="graybg">修改源</span></div>
<p>通过JavaScript可以修改当前页面的源，但是只能域名为当前域名的超级域名：</p>
<pre class="crayon-plain-tag">document.domain = "gmem.cc";</pre>
<p>修改后，同源策略检查结果将受到影响。使用这种技术，可以让位于<span style="background-color: rgb(192, 192, 192);">子域名中的多个网站方便的交互</span>。</p>
<div class="blog_h3"><span class="graybg">跨源资源访问</span></div>
<p>原则：</p>
<ol>
<li>通常允许跨域执行写操作：例如链接、重定向、表单提交</li>
<li>通常允许跨域资源嵌入</li>
<li>通常不允许跨域读操作：例如Ajax请求</li>
</ol>
<p>其中第一条，为CSRF（跨站请求伪造，Cross-site request forgery）埋下隐患。攻击者可能引导用户访问一个恶意站点，而此恶意站点包含了修改敏感信息的表单，此表单自动跨源发送，伪造用户身份篡改信息。可以使用不可测的CSRF标记来防止跨域写。</p>
<p>第二条，可以嵌入的资源包括：</p>
<ol>
<li>script标签</li>
<li>link标签，嵌入css。CSS的跨域需要一个设置正确的Content-Type消息头</li>
<li>img标签</li>
<li>video、audio标签</li>
<li>object、embed、applet插件</li>
<li>@font-face引入的字体。某些浏览器要求同源字体</li>
<li>frame、iframe。站点可以使用<pre class="crayon-plain-tag">X-Frame-Options</pre> 头禁止这类资源嵌入</li>
</ol>
<p>你可以使用CORS、JsonP等技术允许来进行跨源的资源共享。</p>
<div class="blog_h2"><span class="graybg">JsonP相关知识</span></div>
<p>JsonP即JSON with Padding，其实与JSON没有直接关系，是一种跨站资源共享的方式。</p>
<p>由于浏览器的同源策略，blog.gmem.cc一般是无法与诸如soulspark.im之类的其它网站进行通信的，但是HTTP的<pre class="crayon-plain-tag">&lt;script&gt;</pre> 脚本是一个例外，例如此脚本，可以动态的从其它网站上获取信息。</p>
<p>假设blog.gmem.cc/postinfo/{postid}提供一个获取文章信息的RESTful服务，其返回值是text/json格式，示例如下：</p>
<pre class="crayon-plain-tag">{
    "post_author" : "alex",
    "post_title"  : "HTTP协议知识集锦"
}</pre>
<p> 那么soulspark.im的某个页面如何才能访问并获取某篇文章的信息呢？</p>
<p>如果直接使用Ajax请求访问，同源策略可能导致访问被禁止，那么如果使用<pre class="crayon-plain-tag">&lt;script&gt;</pre> 脚本，上述返回值并不能被soulspark.im的脚本提取并使用。</p>
<p>要支持JsonP，blog.gmem.cc必须改造postinfo服务，允许其传入一个“Padding”参数，该参数由调用者soulspark.im指定，通常是一个JavaScript函数的名称，改造后的postinfo服务的URL示例为：<pre class="crayon-plain-tag">blog.gmem.cc/postinfo/{postid}?jsonp=decodePost</pre> ，而返回值则是如下形式的脚本：</p>
<pre class="crayon-plain-tag">//decodePost即所谓Padding，由客户端提供的简短字符串，通常就是所谓“回调函数”
decodePost(
    {
        "post_author" : "alex",
        "post_title"  : "HTTP协议知识集锦"
    }
);</pre>
<p>这样，soulspark.im就可以调用decodePost()，从而获取文章的信息了。</p>
<p>需要注意的时，<pre class="crayon-plain-tag">&lt;script&gt;</pre> 脚本不一定非要硬编码在HTML文档中，在页面运行过程中，可以随时动态创建该元素以使用JsonP。</p>
<div class="blog_h3"><span class="graybg">JsonP的安全风险</span></div>
<p>JsonP允许远端Web服务注入任意JavaScript代码，因此具有很大的安全性风险，如果远端服务器具有安全缺陷，或者响应内容在网络上被篡改，将导致本地Web页面受到威胁。</p>
<div class="blog_h2"><span class="graybg"><a id="cors"></a>CORS相关知识</span></div>
<p>CORS，即Cross-origin resource sharing（跨来源资源共享）是一份浏览器技术规范。相比起JsonP，它有如下优势：</p>
<ol>
<li>支持除了GET以外的其它HTTP请求方法</li>
<li>支持使用一般的Ajax（XMLHttpRequest、Fetch）直接获取资源</li>
<li>支持跨源使用Webfont、WebGL贴图、样式表、脚本</li>
</ol>
<p>所谓跨源，指发起<span style="background-color: #c0c0c0;">请求的那个网页所在的源（协议 + 域名 + 端口）</span>，与<span style="background-color: #c0c0c0;">请求的目标网页的源不一致</span>。</p>
<p>大部分现代浏览器，包括IE 8+，均支持CORS。</p>
<div class="blog_h3"><span class="graybg">服务器</span></div>
<p>要支持CORS，服务器端必须设置<pre class="crayon-plain-tag">Access-Control-Allow-Origin</pre> 响应头，下面是某个Python Web服务的代码片段：</p>
<pre class="crayon-plain-tag">headers.setdefault('Access-Control-Allow-Origin', '*')
"""
上述配置允许任何其它的Domain向服务器发起请求。
把*换成http://soulspark.im，则仅仅允许soulspark.im发起请求
"""</pre>
<p>如果服务器不进行设置，客户端在尝试CORS访问时将会报错。 </p>
<p>与CORS有关的响应头包括：</p>
<table class=" fixed-word-wrap full-width">
<tbody>
<tr>
<td style="width: 30%;">Access-Control-Allow-Origin</td>
<td>必须字段。允许哪些源发起请求，源由客户端在请求头<pre class="crayon-plain-tag">Origin</pre> 中设置</td>
</tr>
<tr>
<td>Access-Control-Allow-Credentials</td>
<td>布尔值，可选字段。是否允许跨域的发送Cookie，默认情况下Cookie不包含在CORS请求中</td>
</tr>
<tr>
<td>Access-Control-Expose-Headers</td>
<td>默认的，CORS方式发送Ajax时，只能拿到Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma这几个响应头。如果允许获得其它响应头，需要在这里指定</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">客户端</span></div>
<p>在浏览器中，Origin头会在需要时，自动的被浏览器设置，JavaScript代码无法干预。因此<span style="background-color: rgb(192, 192, 192);">CORS依赖浏览器的正确行为</span>来工作。</p>
<p>发送CORS的Ajax请求时要想带着Cookie，不但需要服务器同意，客户端也要显式的指定：</p>
<pre class="crayon-plain-tag">var xhr = new XMLHttpRequest();
xhr.withCredentials = true;  // 附带Cookie</pre>
<p>启用上述设置时，Access-Control-Allow-Origin不允许指定为*，同时Cookie仍然受到同源策略的限制。</p>
<p>由于CORS依赖于浏览器的行为，因此服务器不能对客户端做假设。</p>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/http-faq">HTTP知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/http-faq/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>HTML5学习笔记</title>
		<link>https://blog.gmem.cc/html5-study-note</link>
		<comments>https://blog.gmem.cc/html5-study-note#comments</comments>
		<pubDate>Wed, 13 Apr 2011 07:54:53 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[HTML]]></category>
		<category><![CDATA[学习笔记]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=11245</guid>
		<description><![CDATA[<p>相对HTML4的变化 文档声明 HTML5文件的扩展名仍然是html或者htm，内容类型仍然是text/html，但是文档类型、编码格式等信息的声明有所简化： [crayon-69e29a414764d810751850/] 语法 HTML5在语法方面充分考虑了对HTML4的兼容性，以及便利性。 标记的省略 类型 说明 不允许结束标记 这类元素不得书写结束标记，仅支持[crayon-69e29a4147652339249284-i/] 或者[crayon-69e29a4147654880331677-i/] 的形式。大部分不包含文本节点的元素在此类中，例如img、br、input、link、meta、param等 可选结束标记 结束标记可有可无，均合法。例如p、li、option、thead、tbody、tfoot、tr、td、th 可以完全省略 html、head、body、tbody、colgroup等元素可以完全省略不写注意，这些元素不显式声明时，仍然存在于DOM模型中 boolean值的属性 disabled、checked、readonly等boolean值属性，当不提供值、值等于名或空格时，相当于设置属性为true；不提供属性名则相当于false。 属性值省略引号 当属性值不包含HTML特殊字符（&#60; &#62; = ' <a class="read-more" href="https://blog.gmem.cc/html5-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/html5-study-note">HTML5学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h1"><span class="graybg">相对HTML4的变化</span></div>
<div class="blog_h2"><span class="graybg">文档声明</span></div>
<p>HTML5文件的扩展名仍然是html或者htm，内容类型仍然是text/html，但是文档类型、编码格式等信息的声明有所简化：</p>
<pre class="crayon-plain-tag">&lt;!DOCTYPE html&gt; &lt;!-- HTML DocType声明--&gt;
&lt;html lang="en"&gt;
&lt;head&gt;
    &lt;meta charset="UTF-8"&gt;&lt;!-- 编码格式声明，推荐UTF-8 --&gt;
    &lt;title&gt;HTML5 Study&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;!-- body元素可以省略 --&gt;
&lt;/body&gt;
&lt;/html&gt;</pre>
<div class="blog_h2"><span class="graybg">语法</span></div>
<p>HTML5在语法方面充分考虑了对HTML4的兼容性，以及便利性。</p>
<div class="blog_h3"><span class="graybg">标记的省略</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">类型</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>不允许结束标记</td>
<td>这类元素不得书写结束标记，仅支持<pre class="crayon-plain-tag">&lt;br/&gt;</pre> 或者<pre class="crayon-plain-tag">&lt;br&gt;</pre> 的形式。大部分不包含文本节点的元素在此类中，例如img、br、input、link、meta、param等</td>
</tr>
<tr>
<td>可选结束标记</td>
<td>结束标记可有可无，均合法。例如p、li、option、thead、tbody、tfoot、tr、td、th</td>
</tr>
<tr>
<td>可以完全省略</td>
<td>html、head、body、tbody、colgroup等元素可以完全省略不写<br />注意，这些元素不显式声明时，仍然存在于DOM模型中</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">boolean值的属性</span></div>
<p>disabled、checked、readonly等boolean值属性，当<span style="background-color: #c0c0c0;">不提供值、值等于名或空格</span>时，相当于设置属性为true；<span style="background-color: #c0c0c0;">不提供属性</span>名则相当于false。</p>
<div class="blog_h3"><span class="graybg">属性值省略引号</span></div>
<p>当属性值不包含HTML特殊字符（&lt; &gt; = ' "等）时，可以省略包围属性值的引号。</p>
<div class="blog_h2"><span class="graybg">新增的API</span></div>
<p>HTML5新增了大量的<a href="http://html5index.org/">JavaScript API</a>，下表列举常用的部分：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">API</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>CSS选择器</td>
<td>为Document、DocumentFragment、Element类型引入方法：<pre class="crayon-plain-tag">querySelector(DOMString selectors)</pre> 和<pre class="crayon-plain-tag">querySelectorAll(DOMString selectors)</pre> 。分别返回第一个匹配选择器的元素，或者全部匹配元素的<pre class="crayon-plain-tag">NodeList</pre> 对象</td>
</tr>
<tr>
<td>离线应用</td>
<td>引入ApplicationCache类型，用于更新离线缓存，或者得到缓存更新的通知</td>
</tr>
<tr>
<td>多媒体</td>
<td>HTMLAudioElement、HTMLVedioElement相关的方法和属性</td>
</tr>
<tr>
<td>Web SQL DB</td>
<td>已经从标准中废弃，在浏览器上使用SQLLite数据库。Chrome等浏览器支持</td>
</tr>
<tr>
<td>Indexed DB</td>
<td>用于存放简单的Key-Value形式的数据，通常应基于BTree实现</td>
</tr>
<tr>
<td>Web workers</td>
<td>
<p>产生后台线程，执行JavaScript代码，与UI线程并行执行</p>
</td>
</tr>
<tr>
<td>Web storage</td>
<td>WindowLocalStorage、WindowSessionStorage，用于本地存储</td>
</tr>
<tr>
<td>Web sockets</td>
<td>支持与服务器的双向通信</td>
</tr>
<tr>
<td>Server-sent Events</td>
<td>用于服务器端单向推送</td>
</tr>
<tr>
<td>XMLHttpRequest2</td>
<td>支持跨域请求、上传进度事件等新功能</td>
</tr>
<tr>
<td>Geolocation</td>
<td>用于获取客户端的地理位置信息</td>
</tr>
<tr>
<td>Canvas</td>
<td>支持2D绘图</td>
</tr>
<tr>
<td>Form API</td>
<td>扩展了新的表单API，例如表单校验</td>
</tr>
<tr>
<td>File API</td>
<td>访问file类型input关联的文件</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">增删的元素</span></div>
<p>HTML5新增以下元素：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 15%; text-align: center;">新增元素</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>section</td>
<td>表示页面中的一个内容区块，例如章节/页眉/页脚等</td>
</tr>
<tr>
<td>article</td>
<td>表示于上下文不相关的独立内容，例如一篇文章</td>
</tr>
<tr>
<td>aside</td>
<td>表示于article相关的辅助内容</td>
</tr>
<tr>
<td>header</td>
<td>表示内容区块或者整个页面的标题</td>
</tr>
<tr>
<td>hgroup</td>
<td>用于对内容区块或者整个页面的几个标题元素进行组合，通常用于对h1～h6分组</td>
</tr>
<tr>
<td>footer</td>
<td>表示内容区块或者整个页眉的脚注</td>
</tr>
<tr>
<td>nav</td>
<td>表示页面的导航链接部分</td>
</tr>
<tr>
<td>figure</td>
<td>表示一段独立的流内容。通常是图片、统计图、代码示例，甚至音视频插件。</td>
</tr>
<tr>
<td>figurecaption</td>
<td>为figure添加标题</td>
</tr>
<tr>
<td>video</td>
<td>插入视频</td>
</tr>
<tr>
<td>audio</td>
<td>插入音频</td>
</tr>
<tr>
<td>embed</td>
<td>可用于插入多种多媒体格式</td>
</tr>
<tr>
<td>mark</td>
<td>用于显示高亮元素</td>
</tr>
<tr>
<td>meter</td>
<td>用于显示进度条</td>
</tr>
<tr>
<td>time</td>
<td>用于显示时间</td>
</tr>
<tr>
<td>wbr</td>
<td>软换行，当父元素空间不足时换行</td>
</tr>
<tr>
<td>canvas</td>
<td>提供一张画布</td>
</tr>
<tr>
<td>details</td>
<td>与summary联用，summary应该是其第一个子元素，点击后显示details的完整内容</td>
</tr>
<tr>
<td>datalist</td>
<td>与input联用，提供可选值的下拉列表，并且支持自动完成。示例：<br />
<pre class="crayon-plain-tag">&lt;input type=text list="cars"/&gt;
&lt;datalist id="cars"&gt;
    &lt;option value="BMW"&gt;
    &lt;option value="BENZ"&gt;
    &lt;option value="Volvo"&gt;
&lt;/datalist&gt;</pre>
</td>
</tr>
<tr>
<td>keygen</td>
<td>用于生成密钥对，提交表单时，私钥存储在本地，公钥发送到服务器</td>
</tr>
<tr>
<td>output</td>
<td>定义不同类型的输出，比如脚本的输出</td>
</tr>
<tr>
<td>source</td>
<td>作为video/audio等的子元素，定义多个备选的媒体源</td>
</tr>
<tr>
<td>input</td>
<td>HTML5增加了多种input类型：<br />email：必须输入电子邮件<br />url：必须输入URL<br />number：必须输入数字<br />range：范围选择滑块<br />date、month、week、time、datetime、datetime-local：日期选取器</td>
</tr>
</tbody>
</table>
<p>HTML5中删除的元素包括：</p>
<ol>
<li>纯粹样式类元素：basefont、big、center、font、s、strike、tt、u，其功能使用CSS代替</li>
<li>不再使用frame元素，但是允许iframe</li>
<li>仅部分浏览器支持的元素：applet、bgsound、blink、marquee等</li>
</ol>
<div class="blog_h2"><span class="graybg">增删的属性</span></div>
<div class="blog_h3"><span class="graybg">新增表单属性</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>autofocus</td>
<td>适用于text/select/textarea/button类型的表单元素，页面加载时自动获得焦点</td>
</tr>
<tr>
<td>placeholder</td>
<td>适用于text/textarea，未输入时的提示文本</td>
</tr>
<tr>
<td>required</td>
<td>适用于text/textarea，提交表单时执行校验，强制要求输入值</td>
</tr>
<tr>
<td>form</td>
<td>声明表单元素属于哪个（或哪些）表单，进而允许将表单元素放置在页面的任何位置，而不限于表单内</td>
</tr>
<tr>
<td>autocomplete</td>
<td>适用于input元素，是否使用输入字段的自动完成功能<br />可以设置为on/off或者""，最后一个值表示由浏览器决定是否启用自动完成</td>
</tr>
<tr>
<td>min</td>
<td>适用于input元素，规定输入字段的最小值</td>
</tr>
<tr>
<td>max</td>
<td>适用于input元素，规定输入字段的最打值</td>
</tr>
<tr>
<td>multiple</td>
<td>适用于input元素，允许一次上传多个文件</td>
</tr>
<tr>
<td>list</td>
<td>适用于text类型的input，指定此元素使用的datalist</td>
</tr>
<tr>
<td>pattern</td>
<td>适用于input元素，规定输入字段必须匹配的正则式</td>
</tr>
<tr>
<td>step</td>
<td>适用于input元素，规定数字输入框的步进</td>
</tr>
<tr>
<td>disabled</td>
<td>适用于fieldset元素，可以让其内部所有表单元素禁用</td>
</tr>
<tr>
<td>novalidate</td>
<td>适用于input/button/form元素，用于取消提交时表单验证</td>
</tr>
<tr>
<td>form*</td>
<td>formaction、formenctype、formmethod、formnovalidate、formtarget，用于input/button元素，覆盖所属表单的对应属性<br />使用formaction，可以在点击不同按钮后，提交到不同的页面</td>
</tr>
<tr>
<td>spellcheck</td>
<td>适用于text/textarea，是否允许拼写检查</td>
</tr>
<tr>
<td>validity</td>
<td>适用于表单、表单元素，此属性的值类型是<pre class="crayon-plain-tag">ValidityState</pre> 对象，其包含多个属性，其中valid属性说明表单或者表单元素是否通过验证</td>
</tr>
<tr>
<td>accept</td>
<td>用于限定file类型的input能接受的文件MIME类型，<pre class="crayon-plain-tag">image/*</pre> 表示接受所有图片类型</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">新增链接属性</span></div>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 20%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>media</td>
<td>适用于a/area，链接目标文档为何种媒介/设备优化，必须和href属性联用</td>
</tr>
<tr>
<td>hreflang</td>
<td rowspan="2">为area添加，以便和a/link元素一致<br />rel，规定当前文档与被链接文档之间的关系<br />hreflang，链接目标文档的语言</td>
</tr>
<tr>
<td>rel</td>
</tr>
<tr>
<td>target</td>
<td>适用于base，保持和a元素一致</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">其他新增属性</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>reversed</td>
<td>适用于ol元素，倒序显示列表</td>
</tr>
<tr>
<td>start</td>
<td>适用于ol元素，指定编号的起始值</td>
</tr>
<tr>
<td>charset</td>
<td>适用于meta元素，指定文档的编码</td>
</tr>
<tr>
<td>scoped</td>
<td>适用于style元素，使样式仅应用到DOM的某个子树</td>
</tr>
<tr>
<td>async</td>
<td>适用于script元素，允许脚本异步执行</td>
</tr>
<tr>
<td>sandbox</td>
<td rowspan="3">适用于iframe元素，提高安全性</p>
<p>&nbsp;</p>
</td>
</tr>
<tr>
<td>seamless</td>
</tr>
<tr>
<td>srcdoc</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">废除的属性</span></div>
<p>一些和样式有关的属性被废除，例如align、valign、bgcolor、background、border、cellpadding、cellspacing、width、height、nowrap等。</p>
<div class="blog_h2"><span class="graybg">全局属性</span></div>
<p>HTML5引入全局属性的概念，全局属性<span style="background-color: #c0c0c0;">适用于任何元素</span>。 </p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 20%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>contentEditable</td>
<td>可以设置为true/false，添加此属性的任意（支持contentEditable的）元素，其内容可以编辑</td>
</tr>
<tr>
<td>designMode</td>
<td>可以设置为on/off，如果设置为on，则整个页面下，所有支持Editable的元素均可编辑</td>
</tr>
<tr>
<td>hidden</td>
<td>可以设置为true/false，可以设置任何元素为不可见状态</td>
</tr>
<tr>
<td>tabindex</td>
<td>支持新的属性值-1，让元素无法通过Tab获得焦点</td>
</tr>
<tr>
<td>draggable</td>
<td>可以设置为true/false，允许元素拖放。img/a元素默认支持备份</td>
</tr>
<tr>
<td>classList</td>
<td>元素附加的CSS样式列表对象，支持add/remove/toggle等操作</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">文档结构</span></div>
<p>HTML5引入的article、aside、section、header、footer等元素，让文档的结构更加清晰，容易阅读。需要注意这些元素<span style="background-color: #c0c0c0;">和样式无关</span>。</p>
<p>要在不完全支持HTML5的浏览器上使用这些元素，需要显式的声明这些元素如div一样，块式显示：</p>
<pre class="crayon-plain-tag">article, aside, dialog, figure, footer, header, legend, nav, section {
    display: block;
}</pre>
<p>另外，IE8或者更低版本不支持直接通过上述样式表来定义未知元素的样式，需要先调用类似<pre class="crayon-plain-tag">document.createElement('article')</pre> 的脚本手工创建元素。 </p>
<div class="blog_h1"><span class="graybg">表单</span></div>
<p>HTML5在以下方面增强了表单：</p>
<ol>
<li>大量增加表单input元素的类型</li>
<li>表单和表单元素在DOM中可以不存在前后代关系 </li>
<li>支持自动完成、占位符</li>
<li>支持多种日期/时间控件</li>
<li>支持完善的表单验证。属性required、min、max、pattern、setp以及input类型都为验证提供依据。表单提交时，会自动验证；手工调用表单或者表单元素的<pre class="crayon-plain-tag">checkValidity()</pre> 方法可以立即验证，此方法返回boolean值。验证完毕后，检查表单或者表单元素的<pre class="crayon-plain-tag">validity</pre> 属性可以获得详细的验证结果</li>
</ol>
<div class="blog_h1"><span class="graybg">文件操作</span></div>
<p>HTML5提供了一套文件操作的API，使得从Web页面访问本地文件系统更加方便。 </p>
<div class="blog_h2"><span class="graybg">文件和文件列表对象</span></div>
<p>在HTML5中，一个file类型的input可以关联多个文件。input.files代表用户选择的文件对象的数组，此数组的每一个元素为File对象，代表单个文件：</p>
<pre class="crayon-plain-tag">&lt;script type="text/javascript"&gt;
    function showFileNames() {
        var file;
        var fileList = document.getElementById( 'file' ).files;//获取FileList对象
        for ( var i = 0; i &lt; fileList.length; i++ ) {
            file = fileList[ i ];
            console.log( file.name );
        }
    }
&lt;/script&gt;
&lt;input type="file" id="file" multiple="multiple"/&gt;
&lt;input type="button" value="Upload" onclick="showFileNames()"&gt;</pre>
<p>你可以访问File对象的以下属性：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 20%; text-align: center;">属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>name</td>
<td>文件的名称</td>
</tr>
<tr>
<td>type</td>
<td>文件的MIME类型，例如image/jpeg</td>
</tr>
<tr>
<td>size</td>
<td>文件的字节数</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">文件读取器FileReader</span></div>
<p>Firefox 3.6+和Chrome 6.0+实现了FileReader接口，使用它可以读取file类型input选取的文件的内容。要使用此接口，可以：</p>
<pre class="crayon-plain-tag">var fr = new FileReader();</pre>
<p>此接口提供了以下几个方法：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 35%; text-align: center;">方法</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>readAsBinaryString(file)</td>
<td>
<p>将文件读取为二进制编码，通常用于发送到服务器端</p>
<p>注意，此方法时<span style="background-color: #c0c0c0;">异步</span>的，不会立即返回读取结果，结果存放在<pre class="crayon-plain-tag">FileReader.result</pre> 字段中，下面的2个方法也是这样</p>
</td>
</tr>
<tr>
<td>readAsText(file, [encoding])</td>
<td>将文件读取为文本</td>
</tr>
<tr>
<td>readAsDataURL(file)</td>
<td>将文件读取为Data URL字符串，可以直接在页面上使用（赋值给img.src属性），适用于小的图像</td>
</tr>
<tr>
<td>abort()</td>
<td>终止读取操作</td>
</tr>
</tbody>
</table>
<p>你必须注册回调函数还异步的处理读取结果，FileReader支持以下回调：</p>
<ol>
<li>onabort：数据读取操作被终止时调用</li>
<li>onerror：数据读取出错时调用</li>
<li>onloadstart：数据读取开始时调用</li>
<li>onprogress：数据读取过程中调用</li>
<li>onload：数据读取成功时调用</li>
<li>onloadend：数据读取完成时调用，无论成功失败</li>
</ol>
<div class="blog_h1"><span class="graybg">拖放操作</span></div>
<p>尽管以前通过mousedown/mousemove/mouseup事件组合可以实现拖放操作，HTML5更进一步，直接支持原生的拖放事件。特别的，HTML5还支持与<span style="background-color: #c0c0c0;">其它应用程序进行拖放交互</span>，不限定在浏览器窗格内部。</p>
<div class="blog_h2"><span class="graybg">实现拖放的方法</span></div>
<ol>
<li>检测浏览器是否支持原生HTML5拖放，可以使用Modernizr：<pre class="crayon-plain-tag">if (Modernizr.draganddrop) {...}</pre></li>
<li>将需要拖放的元素的draggable属性设置为true</li>
<li>编写拖放相关的事件监听器：<br />
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 100px; text-align: center;">事件</td>
<td style="width: 170px; text-align: center;">Target</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>dragstart</td>
<td>拖放源</td>
<td>按下鼠标，开始拖动操作的瞬间触发</td>
</tr>
<tr>
<td>drag</td>
<td>拖放源</td>
<td>拖动鼠标的过程中不断触发</td>
</tr>
<tr>
<td>dragenter</td>
<td>拖放时鼠标经过的元素</td>
<td>拖动鼠标进入某元素边界时触发</td>
</tr>
<tr>
<td>dragover</td>
<td>拖放时鼠标经过的元素</td>
<td>拖动鼠标在某元素内移动时不断触发</td>
</tr>
<tr>
<td>dragleave</td>
<td>拖放时鼠标经过的元素</td>
<td>拖动鼠标离开某元素边界时触发</td>
</tr>
<tr>
<td>drop</td>
<td>拖放目标</td>
<td>在拖放目标范围内，放开鼠标时触发</td>
</tr>
<tr>
<td>dragend</td>
<td>拖放源</td>
<td>拖放操作完毕后触发 </td>
</tr>
</tbody>
</table>
</li>
</ol>
<div class="blog_h2"><span class="graybg">示例</span></div>
<p>下例展示了三个横向排列的可拖拽小方块，在不同的事件触发时，添加了样式： </p>
<pre class="crayon-plain-tag">&lt;style&gt;
    /* 阻止选择draggable中的文本内容 */
    [draggable] {
        -moz-user-select: none;
        -khtml-user-select: none;
        -webkit-user-select: none;
        user-select: none;
        /* 让老的webkit浏览器支持拖拽 */
        -khtml-user-drag: element;
        -webkit-user-drag: element;
    }

    .column {
        height: 75px;
        width: 75px;
        float: left;
        border: 1px solid #669999;
        background-color: #cdd;
        margin-right: 10px;
        text-align: center;
        cursor: move;
    }

    .column header {
        color: #fff;
        background: #003333;
    }

    .column.gragover {
        border: 2px dotted #88AAAA;
    }
&lt;/style&gt;
&lt;div id="columns"&gt;
    &lt;!-- 只要设置此属性，就可以拖动，在Chrome下默认拖动效果是元素的半透明“重像副本”跟随鼠标移动 --&gt;
    &lt;div class="column" draggable="true"&gt;
        &lt;header&gt;A&lt;/header&gt;
    &lt;/div&gt;
    &lt;div class="column" draggable="true"&gt;
        &lt;header&gt;B&lt;/header&gt;
    &lt;/div&gt;
    &lt;div class="column" draggable="true"&gt;
        &lt;header&gt;C&lt;/header&gt;
    &lt;/div&gt;
&lt;/div&gt;
&lt;script type="text/javascript"&gt;
    var cols = document.querySelectorAll( '#columns .column' );
    Array.prototype.forEach.call( cols, function ( col ) {
        col.addEventListener( 'dragstart', function ( e ) {
            //开始拖拽后，设置源的透明度
            this.style.opacity = '0.3';
        }, false );
        col.addEventListener( 'dragenter', function ( e ) {
            //拖动进入本元素时，改变样式
            this.classList.add( 'gragover' );
        }, false );
        col.addEventListener( 'dragleave', function ( e ) {
            //拖动离开本元素时，恢复样式
            this.classList.remove( 'gragover' );
        }, false );
        //下面的事件会反复触发
        col.addEventListener( 'dragover', function ( e ) {
            //如果拖动源是链接之类的元素，需要阻止默认行为，否则浏览器会导航到链接目标
            e.preventDefault();
        }, false );
        col.addEventListener( 'drop', function ( e ) {
            e.stopPropagation(); //阻止浏览器的默认的重定向操作
        }, false );
        col.addEventListener( 'dragend', function ( e ) {
            //完成拖拽后，恢复源的透明度
            this.style.opacity = '1';
            //移除可能的高亮样式
            [].forEach.call( cols, function ( col ) {
                col.classList.remove( 'gragover' );
            } );
        }, false );
    } );
&lt;/script&gt;</pre>
<div class="blog_h2"><span class="graybg">拖拽时的数据传输 </span></div>
<p>使用<pre class="crayon-plain-tag">DataTransfer</pre> 对象，可以在拖拽的过程中完成数据的传输，在源和目标之间进行信息交换。你可以在<span style="background-color: #c0c0c0;">dragstart时设置</span>此对象，并在<span style="background-color: #c0c0c0;">drop时读取并处理</span>之。DataTransfer对象提供以下属性和方法：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 30%; text-align: center;">属性/方法</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>dropEffect</td>
<td>控制用户在 dragenter 和 dragover 事件期间收到的视觉反馈。允许的值包括none、copy、link、move</td>
</tr>
<tr>
<td>effectAllowed</td>
<td>限制用户可以在源上执行的“拖拽操作类型”，以初始化dragenter和dragover事件中的dropEffect。允许的值包括none、copy、copyLink、copyMove、link、linkMove、move、all 、 uninitialized</td>
</tr>
<tr>
<td>files</td>
<td>从资源管理器拖拽文件到浏览器中时，存放拖入的文件列表对象</td>
</tr>
<tr>
<td>setDragImage(img element, x, y)</td>
<td>设置拖拽时的图标，而不是使用默认的“重像副本”</td>
</tr>
<tr>
<td>clearData(format)</td>
<td>清除数据，format为MIME类型，如果传递此参数，则清除所有数据</td>
</tr>
<tr>
<td>setData(format,data)</td>
<td>设置数据</td>
</tr>
<tr>
<td>getData(format)</td>
<td>读取数据</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">从桌面拖拽到浏览器</span> </div>
<pre class="crayon-plain-tag">function handleDrop( e ) {
    e.stopPropagation(); // 阻止浏览器默认的重定向行为
    e.preventDefault();

    var files = e.dataTransfer.files;
    for ( var i = 0, f; f = files[ i ]; i++ ) {
        //遍历文件，进行处理
    }
}</pre>
<div class="blog_h1"><span class="graybg">画布（canvas）</span></div>
<p>HTML5引入的canvas元素，以及一套相关的API，大大增强了HTML绘制2D图形的能力。使用canvas绘制图形的一般性步骤如下：</p>
<ol>
<li>取得canvas元素的引用</li>
<li>调用<pre class="crayon-plain-tag">canvas.getContext()</pre>  获得图形上下文（graphics context），此上下文封装了很多绘图功能</li>
<li>绘制图形，例如矩形、圆、路径，并设置图形样式</li>
</ol>
<div class="blog_h2"><span class="graybg">示例：绘制矩形</span></div>
<pre class="crayon-plain-tag">&lt;!-- 注意，通过CSS设置画布大小，会导致其内部绘制变形 --&gt;
&lt;canvas id="canvas" style="border: 1px dashed lightgray"&gt;&lt;/canvas&gt;
&lt;script type="text/javascript"&gt;
    /**
     * @type {HTMLCanvasElement}
     */
    var canvas = document.getElementById( 'canvas' );
    var ctx = canvas.getContext( '2d' );
    ctx.clearRect( 50, 50, 50, 50 ); //清除矩形区域内的全部内容
    ctx.fillStyle = '#88AAAA'; //填充样式
    ctx.lineWidth = 2; //边框宽度
    ctx.strokeStyle = "#558888"; //边框颜色
    ctx.fillRect( 50, 50, 50, 50 ); //填充矩形
    ctx.strokeRect( 50, 50, 50, 50 );//绘制矩形边框
&lt;/script&gt;</pre>
<div class="blog_h2"><span class="graybg">示例：绘制路径</span></div>
<p>要绘制矩形之外的形状，需要使用到路径，<span style="background-color: #c0c0c0;">路径</span>可以勾勒出<span style="background-color: #c0c0c0;">图形的轮廓</span>。下面的例子绘制一个半圆：</p>
<pre class="crayon-plain-tag">ctx.beginPath();//开始创建新路径
//绘制圆弧，用此方法也可以绘制圆形。入参：x, y, radius, startAngle, endAngle, anticlockwise
ctx.arc( 50, 50, 25, 0, 3 );
ctx.closePath();//关闭路径
//注意，此时尚未绘制任何图形，只是把路径给创建出来
ctx.fillStyle = 'rgba(128, 255, 255, 0.3)';
ctx.fill();</pre>
<div class="blog_h2"><span class="graybg">Context常用方法</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 25%; text-align: center;">方法</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>moveTo(x y)</td>
<td>移动笔触到目标坐标</td>
</tr>
<tr>
<td>lineTo(x y)</td>
<td>从当前点开始，向目标坐标绘制一直线</td>
</tr>
<tr>
<td>bezierCurveTo()</td>
<td>绘制Bezier曲线</td>
</tr>
<tr>
<td>quadraticCurveTo()</td>
<td>绘制二次曲线</td>
</tr>
<tr>
<td>createLinearGradient()</td>
<td>绘制线性渐变</td>
</tr>
<tr>
<td>addColorStop()</td>
<td>追加渐变颜色</td>
</tr>
<tr>
<td>createRadialGradient()</td>
<td>绘制径向渐变（以圆心向外渐变）</td>
</tr>
<tr>
<td>translate(x,y)</td>
<td>坐标变换，平移</td>
</tr>
<tr>
<td>scale(x,y)</td>
<td>坐标变换，放大</td>
</tr>
<tr>
<td>rotate(angle)</td>
<td>坐标变换，旋转</td>
</tr>
<tr>
<td>transform()</td>
<td>矩阵变换</td>
</tr>
<tr>
<td>drawImage()</td>
<td>绘制图片</td>
</tr>
<tr>
<td>fillText()</td>
<td>绘制文字</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">多媒体</span></div>
<p>HTML5新增了<pre class="crayon-plain-tag">video</pre> 和<pre class="crayon-plain-tag">audio</pre> 两个元素，用于支持音视频，改变了以前依赖于第三方插件进行媒体播放的情况。下面是几个示例</p>
<pre class="crayon-plain-tag">&lt;!DOCTYPE html&gt;
&lt;!-- 音频 --&gt;
&lt;audio src="song.mp3"&gt;Your browser doesn't support HTML5 audio element&lt;/audio&gt;
&lt;!-- 视频 --&gt;
&lt;video width="640" height="480" src="movie.mp4"&gt;&lt;/video&gt;
&lt;!-- 可以指定多个媒体源，让浏览器选择自己支持的一种格式播放  --&gt;
&lt;video&gt;
    &lt;source src="movie.ogv" type="video/ogg"/&gt;
    &lt;source src="movie.mov" type="video/quicktime" /&gt;
&lt;/video&gt;</pre>
<p>各浏览器对音视频格式的支持不一。视频方面，VP8受到广泛支持；音频则一般需要提供OGG和MP3两种格式。</p>
<div class="blog_h2"><span class="graybg">属性、方法和事件</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 150px; text-align: center;">属性/方法/事件</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>src</td>
<td>媒体数据的URL</td>
</tr>
<tr>
<td>autoplay</td>
<td>是否在页眉加载后自动播放</td>
</tr>
<tr>
<td>preload</td>
<td>是否进行预先加载以缓冲，支持none/metadata/auto<br />metadata表示仅预加载媒体的元数据（媒体长度、第一帧、播放列表、持续时间等）</td>
</tr>
<tr>
<td>poster</td>
<td>仅video，当视频不可用时，显示一张图片在视频位置</td>
</tr>
<tr>
<td>loop</td>
<td>是否循环播放</td>
</tr>
<tr>
<td>controls</td>
<td>是否显示浏览器自带的控制工具条</td>
</tr>
<tr>
<td>width</td>
<td rowspan="2">仅video，媒体的尺寸</td>
</tr>
<tr>
<td>height</td>
</tr>
<tr>
<td>error</td>
<td>
<p>在读取、播放过程中，如果出现错误，此字段不为null：<br />MEDIA_ERR_ABORTED：下载过程中因为用户操作而中止<br />MEDIA_ERR_NETWORK：下载时出现网络错误而中止<br />MEDIA_ERR_DECODE：解码失败<br />MEDIA_ERR_SRC_NOT_SUPPORTED：媒体格式不被支持</p>
<p>监听error事件，以使用该属性：</p>
<pre class="crayon-plain-tag">videoEl.addEventListener('error', function(){
    var error = video.error.code
});</pre>
</td>
</tr>
<tr>
<td>networkState</td>
<td>
<p>在媒体下载过程中，读取网络状态：<br />NETWORK_EMPTY：初始状态<br />NETWORK_IDLE：浏览器已经选择好用什么编码格式来播放媒体，但尚未建立网络链接<br />NETWORK_LOADING：正在下载媒体数据<br />NETWORK_NO_SOURCE：没有支持的编码格式，不进行下载
<p>监听progress事件，以使用该属性</p>
</td>
</tr>
<tr>
<td>currentSrc</td>
<td>当前正在使用的媒体URL</td>
</tr>
<tr>
<td>buffered</td>
<td>返回一个实现TimeRanges接口的对象，检查已经缓冲的时间范围 </td>
</tr>
<tr>
<td>readyState</td>
<td>媒体在“当前位置”的就绪状态：<br />HAVE_NOTHING：当前播放位置没有可播放数据<br />HAVE_METADATA：已获得元数据，但是当前位置无有效的媒体数据<br />HAVE_CURRENT_DATA：当前位置有数据可以播放，但是没有获得下一帧的数据，或者当前时最后一帧<br />HAVE_FUTURE_DATA：当前位置有数据可以播放，并且获得了下一帧数据<br />HAVE_ENOUGH_DATA：当前位置有数据可以播放，并且有足够数据供后续播放</td>
</tr>
<tr>
<td>seeking</td>
<td>布尔值，浏览器是否正在请求某一特定位置的播放数据</td>
</tr>
<tr>
<td>seekable</td>
<td>可Seek的TimeRanges对象 </td>
</tr>
<tr>
<td>currentTime</td>
<td>当前播放位置 </td>
</tr>
<tr>
<td>startTime</td>
<td>开始播放的位置，通常为0 </td>
</tr>
<tr>
<td>duration</td>
<td>媒体持续时间 </td>
</tr>
<tr>
<td>played</td>
<td>已经播放过的TimeRanges </td>
</tr>
<tr>
<td>paused</td>
<td>是否处于暂停状态 </td>
</tr>
<tr>
<td>ended</td>
<td>是否已经播放完毕 </td>
</tr>
<tr>
<td>defaultPlaybackRate</td>
<td>默认播放速率 </td>
</tr>
<tr>
<td>playbackRate</td>
<td>用于修改播放速率 </td>
</tr>
<tr>
<td>volume</td>
<td>音量，范围0-1 </td>
</tr>
<tr>
<td>muted</td>
<td>是否处于静音状态 </td>
</tr>
<tr>
<td>play()</td>
<td>播放 </td>
</tr>
<tr>
<td>pause()</td>
<td>暂停 </td>
</tr>
<tr>
<td>load()</td>
<td>重新载入媒体 </td>
</tr>
<tr>
<td>canPlayType()</td>
<td>是否支持指定的MIME类型，返回值：空串表示不支持；maybe表示可能支持； probably表示肯定支持</td>
</tr>
<tr>
<td>⚡loadstart</td>
<td>开始加载媒体数据</td>
</tr>
<tr>
<td>⚡progress</td>
<td>正在获取媒体数据 </td>
</tr>
<tr>
<td>⚡suspend</td>
<td>暂停获取媒体数据，但是下载未停止 </td>
</tr>
<tr>
<td>⚡abort</td>
<td>在下载完毕前中止获取媒体数据 </td>
</tr>
<tr>
<td>⚡error</td>
<td>下载或播放出现错误 </td>
</tr>
<tr>
<td>⚡emptied</td>
<td>网络变为未初始化状态，可能原因：下载过程中出现致命错误；load()被调用</td>
</tr>
<tr>
<td>⚡stalled</td>
<td>尝试获取媒体数据失败 </td>
</tr>
<tr>
<td>⚡play</td>
<td>即将开始播放，调用play()后触发 </td>
</tr>
<tr>
<td>⚡pause</td>
<td>播放暂停，调用puase()后触发 </td>
</tr>
<tr>
<td>⚡loadedmetadata</td>
<td>已经获取到视频的元数据 </td>
</tr>
<tr>
<td>⚡loadeddata</td>
<td>已经加载当前播放位置的媒体数据，准备播放</td>
</tr>
<tr>
<td>⚡waiting</td>
<td>因得不到下一帧数据而暂停 </td>
</tr>
<tr>
<td>⚡playing</td>
<td>正在播放</td>
</tr>
<tr>
<td>⚡canplay</td>
<td>浏览器能播放媒体，但是以当前速率不能播放到结尾，期间需要缓冲 </td>
</tr>
<tr>
<td>⚡canplaythrough</td>
<td>浏览器能播放媒体到结尾</td>
</tr>
<tr>
<td>⚡seeking</td>
<td>正在执行Seek操作 </td>
</tr>
<tr>
<td>⚡seeked</td>
<td>Seek操作执行完毕</td>
</tr>
<tr>
<td>⚡timeupdate</td>
<td>当前播放位置改变 </td>
</tr>
<tr>
<td>⚡ended</td>
<td>播放结束后停止播放 </td>
</tr>
<tr>
<td>⚡ratechange</td>
<td>播放速率改变</td>
</tr>
<tr>
<td>⚡durationchange</td>
<td>时长改变</td>
</tr>
<tr>
<td>⚡volumechange</td>
<td>音量改变或静音</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">本地存储</span></div>
<p>传统的Cookie机制具有以下缺点：</p>
<ol>
<li>尺寸限制：4KB</li>
<li>浪费贷款：随HTTP请求发送到服务器</li>
<li>复杂性：Cookie可能和特定的页面关联，比较难以操控</li>
</ol>
<p>HTML5引入了两种新的客户端存储机制，用来代替Cookie，它们时Web Storage和本地数据库。</p>
<div class="blog_h2"><span class="graybg">Web Storage</span></div>
<p>Web Storage分为两种：</p>
<ol>
<li>Session Storage：关闭浏览器后即消失。使用全局变量<pre class="crayon-plain-tag">sessionStorage</pre> 访问</li>
<li>Local Storage：保存在磁盘中，即使关闭浏览器也不会消失 。使用全局变量<pre class="crayon-plain-tag">localStorage</pre> 访问</li>
</ol>
<p>Web Storage的API非常简单，它们都使用基于Key-Value的存储方式，提供以下属性/方法：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 25%; text-align: center;">属性/方法</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>clear()</td>
<td>清空存储</td>
</tr>
<tr>
<td>getItem(key)</td>
<td>根据Key获取值</td>
</tr>
<tr>
<td>removeItem(key)</td>
<td>根据Key移除值</td>
</tr>
<tr>
<td>setItem(key,value)</td>
<td>根据Key设置值</td>
</tr>
<tr>
<td>length</td>
<td>数据条目总数</td>
</tr>
<tr>
<td>key( index )</td>
<td>根据索引号得到第index-1条数据</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">本地数据库（Web SQL DB）</span></div>
<p>此特性已经从HTML5标准中废弃，但是Chrome、Opera、Safari、Android Browser支持它。</p>
<p>HTML5内置了基于SQLLite的关系型数据库。 SQLLite是一种自包含、零配置的嵌入式数据库引擎。此本地数据库的用法和后端数据库很类似：</p>
<pre class="crayon-plain-tag">&lt;script type="text/javascript"&gt;
    /**
     * 打开或创建一个名为gmem的，预计大小为1MB的数据库
     * @type {Database}
     */
    var db = openDatabase( 'gmem', '1.0', 'Green memory database', 1 * 1024 * 1024 );
    //开启一个读写事务
    db.transaction( function ( tx ) {
        tx.executeSql( 'DROP TABLE IF EXISTS T_CORP' );
        tx.executeSql( 'CREATE TABLE IF NOT EXISTS T_CORP (CORP_ID, NAME, REG_CAPI)' );
        /**
         * SQL执行成功时的回调
         * @param {SQLTransaction} tx
         * @param {SQLResultSet} results
         */
        function dataHdl( tx, results ) {
            console.log( 'ID of inserted row: ' + results.insertId );
        }

        /**
         * SQL执行失败时的回调
         * @param {SQLTransaction} tx
         * @param {SQLError} err
         */
        function errHdl( tx, err ) {
            console.log( err.message );
        }

        var sql = 'INSERT INTO T_CORP VALUES (?, ?, ?) ';
        tx.executeSql( sql, [ 10000, "Sparknet. co.,ltd", 1500 ], dataHdl, errHdl );
        tx.executeSql( sql, [ 10001, "Gmem studio", 25 ], dataHdl, errHdl );
    } );
    //开启一个读事务
    db.readTransaction( function ( tx ) {
        tx.executeSql( 'SELECT * FROM T_CORP', [], function ( tx, results ) {
            Array.prototype.forEach.call( results.rows, function ( row ) {
                console.log( row.NAME );
            } )
        } );
    } );
&lt;/script&gt;</pre>
<div class="blog_h1"><span class="graybg">离线应用程序</span></div>
<p>传统Web应用的致命缺点是，与Internet断开连接时，无法使用。HTML5引入一个<span style="background-color: #c0c0c0;">本地缓存机制</span>，允许在离线状态下访问Web页面，当无法连接Internet时，浏览器自动访问本地缓存中的HTML、CSS、Javascript等文件。</p>
<div class="blog_h2"><span class="graybg">与浏览器缓存的区别</span></div>
<ol>
<li>本地缓存只处理你指定的网页，对哪些资源进行缓存。浏览器缓存无法进行这些细致的控制</li>
<li>浏览器缓存仍然需要网络连接才能生效</li>
</ol>
<div class="blog_h2"><span class="graybg">manifest文件</span></div>
<p>manifest文件负责声明哪些资源需要被缓存，以及资源的路径。你可以为每个页面指定manifest文件，或者为<span style="background-color: #c0c0c0;">整个应用</span>声明一个manifest文件。manifest文件的格式如下：</p>
<pre class="crayon-plain-tag">CACHE MANIFEST
#必须以上一行开头

#有三种类别的声明，每个类别都可以重复编写：
CACHE
#声明需要缓存到本地的资源文件，为一个页面指定manifest时，页面本身自动在此类别下，不需要声明
NETWORK
#声明不得进行缓存的资源文件
#通配符表示没有显式声明的文件，默认都不缓存
*
FALLBACK
#每行指定两个文件，第一个为能够在线访问时使用的文件，第二个为离线时使用的文件
online.js    offline.js</pre>
<p>HTML5规定manifest的MIME类型为text/cache-manifest，你的Web服务器必须支持此MEME类型，以Apache2为例：</p>
<pre class="crayon-plain-tag">text/cache-manifest    manifest</pre>
<p>编写好manifest文件后，需要到HTML文件中引用之：</p>
<pre class="crayon-plain-tag">&lt;html manifest="global.manifest"&gt;&lt;/html&gt;</pre>
<div class="blog_h2"><span class="graybg">applicationCache对象</span></div>
<p>此对象表示本地缓存，当本地缓存更新后，可以通过此对象获得通知，你也可以通过此对象手工更新缓存：</p>
<pre class="crayon-plain-tag">applicationCache.onUpdateReady = function () {
    console.log( 'Local cache updated' );
}
applicationCache.swapCache();//手工更新缓存，页面刷新后生效</pre>
<div class="blog_h1"><span class="graybg">通信</span></div>
<div class="blog_h2"><span class="graybg">跨文档消息传输</span></div>
<p>HTML5支持在不同网页之间进行消息收发，只需要获得目标窗口的引用就可以发送消息，可以跨域通信：</p>
<pre class="crayon-plain-tag">window.addEventListener( 'message', function () {

} );
var targetOrigin = "*"; //一般使用目标窗口的URL,例如http://localhost:8080/，*表示通配符
otherWindow.postMessage( message, targetOrigin ); </pre>
<div class="blog_h2"><span class="graybg"><a id="websockets"></a>WebSockets</span></div>
<p>使用HTML5的Web Sockets API，可以在客户端和服务器之间建立一个非HTTP的<span style="background-color: #c0c0c0;">双向通信</span>连接。使用WebSockets可以轻易的实现<span style="background-color: #c0c0c0;">数据推送</span>，而避免客户端轮询。</p>
<p>进行WebSockets通信时，必须使用ws或者wss（加密通信）的URL前缀：</p>
<pre class="crayon-plain-tag">var socket = new WebSocket("ws://gmem.cc:8808/socket");</pre>
<p>使用WebSockets收发数据非常简单：</p>
<pre class="crayon-plain-tag">//向服务器发送数据
socket.send('文本数据');
//通过回调函数来接收服务器发送来的数据
socket.onmessage = function(event){
    var data = event.data;
}
//监听套接字开关事件
socket.onopen = function(event){}
socket.onclose = function(event){}</pre>
<p>不再需要使用时，可以关闭WebSocket：</p>
<pre class="crayon-plain-tag">socket.close()</pre>
<p>另外，你可以读取<pre class="crayon-plain-tag">readyState</pre> 属性，获取WebSocket的状态，可能的值包括：CONNECTING（正在连接）；OPEN（已经连接）；CLOSING（正在关闭）；CLOSED（已经关闭）。</p>
<div class="blog_h3"><span class="graybg">WebSocket优势</span></div>
<ol>
<li>客户端和服务器仅仅建立一个TCP连接</li>
<li>支持从服务器推送数据到客户端</li>
</ol>
<div class="blog_h2"><span class="graybg">XMLHttpRequest2</span></div>
<p>即第二级XMLHttpRequest，HTML5增强了其功能，并且支持CORS。</p>
<div class="blog_h3"><span class="graybg">接收数据</span></div>
<p>XHR2支持设置响应类型，你可以将<pre class="crayon-plain-tag">xhr.responseType</pre> 设置为text、arraybuffer、blob、document等值，responseType的设置的值不同，则response中数据的组织方式也不同。</p>
<p>下面的例子中，我们以二进制方式读取服务器响应，并作为图片插入到DOM：</p>
<pre class="crayon-plain-tag">xhr.responseType = 'blob';
var bb = new BlobBuilder();
bb.append( xhr.response );
var blob = bb.getBlob( 'image/png' );
var img = document.createElement( 'img' );
img.onload = function ( e ) {
    window.URL.revokeObjectURL( img.src ); // 清理
};
img.src = window.URL.createObjectURL( blob );
document.body.appendChild( img );</pre>
<p>类似的，当设置响应类型为arraybuffer时，可以：</p>
<pre class="crayon-plain-tag">var uInt8Array = new Uint8Array( this.response );
var byte3 = uInt8Array[ 4 ];</pre>
<div class="blog_h3"><span class="graybg">发送数据</span></div>
<p>以前XHR限制仅能发送DOMString或者Document（XML）类型的数据，XHR2修改过的<pre class="crayon-plain-tag">send()</pre> 方法支持多种类型的数据，包括DOMString、Document、FormData、Blob、File、ArrayBuffer。下面的代码示例了如何发送文本数据：</p>
<pre class="crayon-plain-tag">var xhr = new XMLHttpRequest();
xhr.open( 'POST', '/server', true );
xhr.responseType = 'text';
xhr.onload = function ( e ) {
    if ( this.status == 200 ) {
        console.log( this.response );
    }
};
xhr.send( 'text' );</pre>
<p>下面的代码示例了如何提交表单：</p>
<pre class="crayon-plain-tag">var form = document.getElementById( 'formId' );
//获取表单数据
var formData = new FormData( form );
formData.append( 'secret_token', '1234567890' ); // 附加额外数据
var xhr = new XMLHttpRequest();
xhr.open( 'POST', form.action, true );
xhr.onload = function ( e ) {
};
xhr.send( formData );</pre>
<p>下面的数据示例了如何上传BLOB：</p>
<pre class="crayon-plain-tag">var xhr = new XMLHttpRequest();
xhr.open( 'POST', '/server', true );
xhr.onload = function ( e ) {
};
// 监听上传进度
var progressBar = document.querySelector( 'progress' );
xhr.upload.onprogress = function ( e ) {
    if ( e.lengthComputable ) {
        var pcnt = (e.loaded / e.total) * 100; //进度
    }
};
var bb = new BlobBuilder();
bb.append( 'hello world' );
bb.getBlob( 'text/plain'
xhr.send( bb );
&lt;/script&gt;</pre>
<p>上传文件时处理方式类似，只需要通过File API读取文件的内容即可。 </p>
<div class="blog_h1"><span class="graybg">Web Workers和多线程</span></div>
<p>在以前，Web应用都是“单线程”的，通过Web Workers，HTML5引入了类似后端开发的多线程功能。 使用Web Workers，你可以把一部分工作安排在后端线程中执行，而不必影响UI渲染线程。</p>
<p>要创建后台线程，你可以：</p>
<pre class="crayon-plain-tag">//入参为新线程需要执行的脚本
var worker = new Worker('worker.js');</pre>
<p>后台线程<span style="background-color: #c0c0c0;">不能访问页面、窗口等对象</span>，需要注意这个限制。如果要同后台线程交互，可以通过消息机制：</p>
<pre class="crayon-plain-tag">/* 父线程 */
//发送消息给子线程
worker.postMessage('Hello');
//监听消息
worker.onmessage = function (event) {
    console.log(event.data);
}


/* 子线程的脚本文件中 */
//监听消息
self.onmessage = function (event) {
    //向父线程发送消息
    self.postMessage('World');
}</pre>
<p>注：上述代码中的onmessage属性可以用<pre class="crayon-plain-tag">addEventListener('message',function(e){})</pre> 代替。</p>
<div class="blog_h2"><span class="graybg">线程支持的变量、函数和对象</span></div>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 45%; text-align: center;">变量/函数/对象</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>self</td>
<td>表示本线程的作用域对象</td>
</tr>
<tr>
<td>postMessage(message)</td>
<td>向创建线程的源窗口发送消息</td>
</tr>
<tr>
<td>onmessage</td>
<td>获得用于接收事件的回调函数</td>
</tr>
<tr>
<td>importScripts(urls)</td>
<td>导入其它脚本文件，支持多个脚本</td>
</tr>
<tr>
<td>navigator</td>
<td>类似于window.navigator</td>
</tr>
<tr>
<td>sessionStorage/localStorage</td>
<td>本地存储</td>
</tr>
<tr>
<td>XMLHttpRequest</td>
<td>支持发送XHR请求</td>
</tr>
<tr>
<td>Web Workers</td>
<td>支持嵌套创建的子线程</td>
</tr>
<tr>
<td>setTimeout()/setInterval()</td>
<td>支持定时器</td>
</tr>
<tr>
<td>terminate()</td>
<td>当一个线程被创建后，它会持续的监听外部消息，即使脚本已经完毕<br />必须显式调用此方法，来关闭线程，释放资源</td>
</tr>
<tr>
<td>eval()/isNaN()/escape() ...</td>
<td>支持任何JavaScript核心函数</td>
</tr>
<tr>
<td>WebSockets</td>
<td>支持Web Sockets</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">地理位置信息</span></div>
<p>HTML5包含了一套Geolocation API，可以用来获取客户端的地理位置信息，这要求浏览器支持、并且设备具有定位功能。通过<pre class="crayon-plain-tag">window.navigator.geolocation</pre> 可以访问这些API，主要包括下面三个方法：</p>
<table class=" full-width fixed-word-wrap">
<tbody>
<tr>
<td style="width: 35%;">void getCurrentPosition<br />    (onSuccess, onError, options)</td>
<td>立即获取当前位置信息，三个入参：<br />onSuccess，成功获取位置时执行的回调，格式：<br />
<pre class="crayon-plain-tag">//入参为一个position对象
function(position){}</pre></p>
<p>onError，获取位置失败时执行的回调，可选<br />options，选项，可选，可以指定以下属性：<br />　　enableHighAccuracy，是否要求高精度的地理位置<br />　　timeout，获取地理位置信息的超时ms<br />　　maximumAge，地理位置缓存最大ms</p>
</td>
</tr>
<tr>
<td>int watchCurrentPosition<br />    (onSuccess, onError, options)</td>
<td>持续监控当前位置信息，定期回调，返回一个watchId</td>
</tr>
<tr>
<td>void clearWatch(watchId)</td>
<td>取消既有的持续监控</td>
</tr>
</tbody>
</table>
<p>成功回调函数的入参是一个position对象，其包含了多种位置信息参量：</p>
<table class=" fixed-word-wrap full-width">
<tbody>
<tr>
<td style="width: 35%;">latitude</td>
<td>当前维度</td>
</tr>
<tr>
<td>longitude</td>
<td>当前经度</td>
</tr>
<tr>
<td>altitude</td>
<td>当前海拔高度</td>
</tr>
<tr>
<td>accuracy</td>
<td>经纬度的精度（单位m）</td>
</tr>
<tr>
<td>altitudeAccuracy</td>
<td>海拔的精度（单位m）</td>
</tr>
<tr>
<td>heading</td>
<td>设备前进方向，正北顺时针旋转的角度</td>
</tr>
<tr>
<td>speed</td>
<td>设备前进速度（单位m/s）</td>
</tr>
<tr>
<td>timestamp</td>
<td>获得地理位置信息的时间戳</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">常用代码</span></div>
<div class="blog_h2"><span class="graybg">Viewport设置</span></div>
<p>所谓视口（Viewport）就是指浏览器/webview上能够显示网页内容的那部分区域。</p>
<p>视口的的尺寸不一定等于浏览器可视区域的尺寸。因为移动设备的（CSS逻辑）分辨率比桌面设备低，为了能够正常显示为桌面设备制作的传统网站，移动设备浏览器通常把自己的视口宽度设置为980px或者1024px——比浏览器可视区域宽度大，这就导致会出现横向滚动条。</p>
<p>移动设备中，CSS的一个像素，也不一定代表一个物理像素。以Iphone4为例，一个CSS像素对应了4个物理像素。<pre class="crayon-plain-tag">windiw.devicePixelRatio</pre> 表示单位长度内，物理像素数量  / CSS逻辑像素数量的比值，Iphone4中该比值为2。</p>
<p>可以使用meta标签对视口进行控制：</p>
<pre class="crayon-plain-tag">&lt;!--
width  视口的宽度，可以设置为width-device表示适应设备的宽度
initial-scale  页面的初始缩放比率
maximum-scale  页面的最大缩放比率
user-scalable  是否允许用户来缩放
--&gt;
&lt;meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=0"&gt;</pre>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/html5-study-note">HTML5学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/html5-study-note/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>HTML知识集锦</title>
		<link>https://blog.gmem.cc/html-faq</link>
		<comments>https://blog.gmem.cc/html-faq#comments</comments>
		<pubDate>Sat, 20 Dec 2003 03:38:27 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[HTML]]></category>
		<category><![CDATA[FAQ]]></category>

		<guid isPermaLink="false">http://blog.gmem.cc/?p=3552</guid>
		<description><![CDATA[<p>常用代码片段 HTML Head标签 [crayon-69e29a414812b487046656/] 嵌入Flash影片 [crayon-69e29a414812f277920730/] 内容安全策略 内容安全策略（CSP，Content Security Policy）是一个附加的安全层，用于帮助检测和缓解某些类型的攻击，包括跨站脚本（XSS）和数据注入等攻击。 使用标签[crayon-69e29a4148132664543563-i/] 可以设置内容安全策略，其中[crayon-69e29a4148134145021782-i/] 属性的格式为： [crayon-69e29a4148136330382827/] 策略指令 使用策略指令可以在多种策略范围配置安全策略： 策略指令 说明 default-src 定义了那些没有被更精确指令指定的安全策略，相当于默认值本表格的后续内容均为精确指令 child-src 定义嵌套浏览器上下文的安全策略，例如[crayon-69e29a4148138337584796-i/] 。应当使用该指令代替废弃的 frame-src connect-src <a class="read-more" href="https://blog.gmem.cc/html-faq">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/html-faq">HTML知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><div class="blog_h2"><span class="graybg">常用代码片段</span></div>
<div class="blog_h3"><span class="graybg">HTML Head标签</span></div>
<pre class="crayon-plain-tag">&lt;!-- 内容类型声明 --&gt;
&lt;meta http-equiv="Content-Type" content="text/html; charset=utf-8" /&gt;
&lt;!--样式表--&gt;
&lt;style type="text/css"&gt;
&lt;!--
body {
	margin: 0;
	padding: 0;
}
--&gt;
&lt;/style&gt;
&lt;!--外联样式表--&gt;
&lt;link rel="stylesheet" type="text/css" href="style.css" /&gt;</pre>
<div class="blog_h3"><span class="graybg">嵌入Flash影片</span></div>
<pre class="crayon-plain-tag">&lt;object id="FlashID" width="50" height="50" classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"&gt;
&lt;param name="movie" value="flash-file.swf" /&gt;
&lt;param name="quality" value="high" /&gt;
&lt;param name="wmode" value="transparent" /&gt;
&lt;!--[if !IE]&gt;--&gt;
&lt;object type="application/x-shockwave-flash" data="flash-file.swf" width="50" height="50"&gt;
    &lt;!--&lt;![endif]--&gt;
    &lt;param name="quality" value="high" /&gt;
    &lt;param name="wmode" value="transparent" /&gt;
    &lt;!--[if !IE]&gt;--&gt;
&lt;/object&gt;
&lt;!--&lt;![endif]--&gt;</pre>
<div class="blog_h2"><span class="graybg"><a id="csp"></a>内容安全策略</span></div>
<p>内容安全策略（CSP，Content Security Policy）是一个附加的安全层，用于帮助检测和缓解某些类型的攻击，包括跨站脚本（XSS）和数据注入等攻击。</p>
<p>使用标签<pre class="crayon-plain-tag">&lt;meta http-equiv="Content-Security-Policy" content=""&gt;</pre> 可以设置内容安全策略，其中<pre class="crayon-plain-tag">content</pre> 属性的格式为：</p>
<pre class="crayon-plain-tag">策略指令 内容源列表; 策略指令 内容源列表; ……</pre>
<div class="blog_h3"><span class="graybg">策略指令</span></div>
<p>使用策略指令可以在多种策略范围配置安全策略：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 25%; text-align: center;">策略指令</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>default-src</td>
<td>定义了那些没有被更精确指令指定的安全策略，相当于默认值<br />本表格的后续内容均为精确指令</td>
</tr>
<tr>
<td>child-src</td>
<td>定义嵌套浏览器上下文的安全策略，例如<pre class="crayon-plain-tag">&lt;iframe&gt;</pre> 。应当使用该指令代替废弃的 frame-src</td>
</tr>
<tr>
<td>connect-src</td>
<td>定义了请求、XMLHttpRequest、WebSocket 、EventSource的安全策略</td>
</tr>
<tr>
<td>font-src</td>
<td>定义了@font-face可以加载的字体的安全来源</td>
</tr>
<tr>
<td>img-src</td>
<td>定义了图片和favicons的安全来源</td>
</tr>
<tr>
<td>media-src</td>
<td>定义了<pre class="crayon-plain-tag">&lt;video&gt;</pre> 和<pre class="crayon-plain-tag">&lt;audio&gt;</pre> 的安全来源</td>
</tr>
<tr>
<td>object-src</td>
<td>定义了<pre class="crayon-plain-tag">&lt;object&gt;</pre> 、<pre class="crayon-plain-tag">&lt;embed&gt;</pre> 和<pre class="crayon-plain-tag">&lt;applet&gt;</pre> 的安全来源</td>
</tr>
<tr>
<td>script-src</td>
<td>定义了JavaScript脚本的安全来源。默认内联脚本和<pre class="crayon-plain-tag">eval()</pre> 是被禁止的。内联脚本包括<pre class="crayon-plain-tag">&lt;script&gt;</pre> 元素和<pre class="crayon-plain-tag">javascript:</pre> 格式的URL</td>
</tr>
<tr>
<td>style-src</td>
<td>定义了样式表的安全来源。默认内联样式表是被禁止的。内联样式表包括<pre class="crayon-plain-tag">&lt;style&gt;</pre> 元素和<pre class="crayon-plain-tag">style</pre> 属性</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">内容源列表</span></div>
<p>说明指令允许的源，<span style="background-color: #c0c0c0;">多个源用空格分开</span>。源可以分为三类：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 15%; text-align: center;"><span style="font-weight: normal; line-height: 19px;">源类型</span></td>
<td style="width: 25%; text-align: center;"><span style="font-weight: normal; line-height: 19px;">举例</span></td>
<td style="text-align: center;"><span style="font-weight: normal; line-height: 19px;">说明</span></td>
</tr>
</thead>
<tbody>
<tr>
<td rowspan="2"><span style="line-height: 19px;">URL源</span></td>
<td><span style="line-height: 19px;">https://*.gmem.cc</span></td>
<td> 基于HTTPS协议的gmem.cc所有子域名下全部网址<span style="line-height: 19px;"><br /></span></td>
</tr>
<tr>
<td><span style="line-height: 19px;">http://www.gmem.cc</span></td>
<td> 基于HTTP 协议的gmem.cc的www子域名下全部网址</td>
</tr>
<tr>
<td><span style="line-height: 19px;">通配符源</span></td>
<td><span style="line-height: 19px;">* </span></td>
<td><span style="line-height: 19px;">匹配所有源</span></td>
</tr>
<tr>
<td rowspan="7"><span style="line-height: 19px;">特殊源</span></td>
<td>'none'</td>
<td>代表空集，不匹配任何URL<span style="line-height: 19px;"><br /></span></td>
</tr>
<tr>
<td>'self'</td>
<td>代表与文档同源（同域名、同协议、同端口）的URL</td>
</tr>
<tr>
<td>'unsafe-inline'</td>
<td>允许内联资源，内联资源包括：<pre class="crayon-plain-tag">&lt;script&gt;</pre> 元素、<pre class="crayon-plain-tag">&lt;style&gt;</pre> 元素、<pre class="crayon-plain-tag">javascript:</pre> 格式的URL、内联的事件处理函数等</td>
</tr>
<tr>
<td>'unsafe-eval'</td>
<td>允许使用<pre class="crayon-plain-tag">eval()</pre> </td>
</tr>
<tr>
<td>data:</td>
<td>允许使用data:资源，data:资源格式：<pre class="crayon-plain-tag">data:[&lt;mediatype&gt;][;base64],&lt;data&gt;</pre></td>
</tr>
<tr>
<td>mediastream:</td>
<td>允许使用mediastream:资源</td>
</tr>
<tr>
<td>gap:</td>
<td>仅用于iOS的UIWebView，在JavaScript与Native通信时需要</td>
</tr>
</tbody>
</table>
<p>注意，启用unsafe-*可能导致XSS攻击。因为目标资源可能直接/间接的引用不安全内容。</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/html-faq">HTML知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/html-faq/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>
