<?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; Mobile</title>
	<atom:link href="https://blog.gmem.cc/category/work/mobile/feed" rel="self" type="application/rss+xml" />
	<link>https://blog.gmem.cc</link>
	<description></description>
	<lastBuildDate>Tue, 21 Apr 2026 10:40:56 +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>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>使用GapDebug调试Phonegap应用</title>
		<link>https://blog.gmem.cc/gapdebug</link>
		<comments>https://blog.gmem.cc/gapdebug#comments</comments>
		<pubDate>Fri, 19 Feb 2016 02:41:02 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Mobile]]></category>
		<category><![CDATA[远程调试]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=11043</guid>
		<description><![CDATA[<p>GapDebug简介 一个移动混合应用（hybrid mobile apps）调试工具，它是一个Eclipse RCP应用，集成了Chrome Dev Tools（用于Android调试）和Webkit Inspector（用于iOS调试）。GapDeug的主要界面在Chrome浏览器中运行。 GapDebug的价值在于，它整合了很多原先需要手工执行的繁琐步骤。GapDebug可以自动的识别设备、进行安装与配置、启动应用并执行调试，并且同时支持iOS、Android设备的远程调试。 联合使用GapDebug和Phonegap Developer App，可以进行无缝调试：包括DOM结构检查、断点、实时代码更新。 前置条件 要让GapDebug正确运行，必须满足以下条件：   Android iOS 移动设备  Android 4.4+或者更高版本 推荐使用 Genymotion模拟器 <a class="read-more" href="https://blog.gmem.cc/gapdebug">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/gapdebug">使用GapDebug调试Phonegap应用</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">GapDebug简介</span></div>
<p>一个移动混合应用（hybrid mobile apps）调试工具，它是一个Eclipse RCP应用，集成了Chrome Dev Tools（用于Android调试）和Webkit Inspector（用于iOS调试）。GapDeug的主要界面在Chrome浏览器中运行。</p>
<p>GapDebug的价值在于，它<span style="background-color: #c0c0c0;">整合了很多原先需要手工执行的繁琐步骤</span>。GapDebug可以自动的识别设备、进行安装与配置、启动应用并执行调试，并且同时支持iOS、Android设备的远程调试。</p>
<p>联合使用GapDebug和Phonegap Developer App，可以进行<span style="background-color: #c0c0c0;">无缝调试</span>：包括DOM结构检查、断点、实时代码更新。</p>
<div class="blog_h1"><span class="graybg">前置条件</span></div>
<p>要让GapDebug正确运行，必须满足以下条件：</p>
<table class=" full-width fixed-word-wrap">
<thead>
<tr>
<td style="width: 20%; text-align: center;"> </td>
<td style="text-align: center;">Android</td>
<td style="text-align: center;">iOS</td>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: center;"><strong>移动设备</strong></td>
<td>
<ol>
<li><span style="color: #333333; font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: 13px; line-height: 22px;"> Android 4.4+或者更高版本</span></li>
<li>推荐使用 Genymotion模拟器</li>
<li>启用USB调试</li>
</ol>
</td>
<td>
<ol>
<li><span style="color: #333333; font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: 13px; line-height: 22px;"> iOS 7+</span></li>
<li>启用Web Inspector</li>
</ol>
</td>
</tr>
<tr>
<td style="text-align: center;"><strong> 移动应用程序</strong></td>
<td> 启用调试</td>
<td> 应用程序必须使用iOS开发者证书和Provisioning Profile签名</td>
</tr>
<tr>
<td style="text-align: center;"><strong>工作站 </strong></td>
<td>
<ol>
<li><span style="color: #333333; font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: 13px; line-height: 22px;"> Android驱动（Windows）</span></li>
<li>Chrome/Chromium浏览器</li>
</ol>
</td>
<td>
<ol>
<li><span style="color: #333333; font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: 13px; line-height: 22px;"> 安装iTunes</span></li>
<li>Chrome/Chromium浏览器</li>
</ol>
</td>
</tr>
</tbody>
</table>
<div class="blog_h1">移动端配置 </div>
<div class="blog_h2"><span class="graybg">iOS</span></div>
<div class="blog_h3"><span class="graybg">启用Web Inspector</span></div>
<p>Settings ⇨ Safari ⇨ Advanced ⇨ Web Inspector，滑动打开</p>
<div class="blog_h2"><span class="graybg">Android</span></div>
<div class="blog_h3"><span class="graybg">启用USB调试</span></div>
<p>Settings ⇨ Developer Options ⇨ 勾线 USB Debugging</p>
<div class="blog_h3"><span class="graybg">允许应用调试</span></div>
<p>以Phonegap应用为例，编辑<pre class="crayon-plain-tag">platforms/android/AndroidManifest.xml</pre>，添加：</p>
<pre class="crayon-plain-tag">&lt;manifest&gt;
     ....
     &lt;application android:debuggable="true" /&gt;
     ....
&lt;/manifest&gt;</pre>
<p>如果使用Phonegap Developer App，则不需要上述设置。 </p>
<div class="blog_h1"><span class="graybg">工作站配置</span></div>
<ol>
<li>下载并安装GapDebug：<a href="https://www.genuitec.com/products/gapdebug/">https://www.genuitec.com/products/gapdebug</a></li>
<li>下载并安装Chromium，推荐绿色版：<a href="https://sourceforge.net/projects/crportable/">https://sourceforge.net/projects/crportable/</a></li>
</ol>
<div class="blog_h2"><span class="graybg">GapDebug配置</span></div>
<p>打开GapDebug，系统托盘中右键GapDebug图标 ⇨ Settings：</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>Chrome and Server</td>
<td>
<p>Server Setting：</p>
<ol>
<li>修改GapDebug的Web服务的端口，指定一个空闲端口</li>
</ol>
<p>Google Chrome Settings：</p>
<ol>
<li>定位到新下载的Chromium绿色版</li>
<li>取消勾选“Launch the debugger in Google Chrome application mode”</li>
</ol>
</td>
</tr>
<tr>
<td>General</td>
<td>
<p>Debug web browser pages：勾选后，可以调试手机上的Safari或者Chrome浏览器</p>
<p>Start app after installation：勾选后，新安装的应用程序会自动启动</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h1" style="text-align: center;"><span class="graybg">调试移动应用</span></div>
<div class="blog_h2"><span class="graybg">开始调试</span></div>
<ol>
<li>系统托盘中右键GapDebug图标 ⇨ Open Debug Tools，在Chromium浏览器中打开GapDebug主界面</li>
<li>打开模拟器，或者通过USB电缆连接设备。如果设备提示Allow USB debugging（Android）或者Trust This Computer（iOS），点击确定</li>
<li>最好关闭设备的自动锁定或者睡眠功能</li>
<li>在GapDebug左侧面板，会出现设备图标。并显示设备设备IP地址和ADB端口，以及设备名称和固件版本信息</li>
<li>点击左侧面板上“奔跑的小人”图标，会显示设备上可远程调试的应用程序列表。点击其中一个应用程序，即开始启动并开始调试。第一次调试某个新的设备时，可能需要到外网下载一些文件，应保证网络畅通</li>
<li>右侧面板出现基于Chrome Dev Tools或者Webkit Inspector的调试界面</li>
</ol>
<div class="blog_h2"><span class="graybg">用户界面</span></div>
<p>下面的是调试Android Phonegap应用时的截图：<a href="https://blog.gmem.cc/wp-content/uploads/2016/02/gapdebug-ui.jpg"><img class="aligncenter wp-image-11058" src="https://blog.gmem.cc/wp-content/uploads/2016/02/gapdebug-ui.jpg" alt="gapdebug-ui" width="700" height="428" /></a></p>
<p>左侧面板，设备上方有四个按钮，分别用于：</p>
<ol>
<li>启动某个设备应用程序以调试</li>
<li>截取设备屏幕</li>
<li>安装新的应用程序到设备。另外，直接拖拽APK/IPA文件到设备图标上，也可以进行安装</li>
<li>隐藏/显示应用程序列表</li>
</ol>
<p>左侧面板，应用程序列表，每个条目对应一个正在运行的移动Web应用程序。条目右侧有一个两个图标：</p>
<ol>
<li>小圆点，当前正被调试的应用程序的小圆点图标变亮</li>
<li><img class="aligncenter size-full wp-image-11062 inlineBlock" src="https://blog.gmem.cc/wp-content/uploads/2016/02/auto-active.png" alt="auto-active" width="16" height="16" />，点击后，可以启用即时调试（Instant Debugging）—— 应用程序启动后GapDebug会自动检测到，并立即启动一个Debugger附着到此应用程序上。</li>
</ol>
<p>可以看到，右侧第一个选项卡就是Chrome Dev Tools，相信Web开发者都很熟悉：</p>
<ol>
<li>点击菜单栏放大镜按钮，可以Inspect DOM元素，应用程序界面会和你的操作互动，就像调试普通网页时一样</li>
<li>点击放大镜旁边的手机按钮，可以进行屏幕转播（Screencast），即在Gapdebug界面中实时显示手机UI。此UI支持用户交互</li>
</ol>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/gapdebug">使用GapDebug调试Phonegap应用</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/gapdebug/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-69eeaec6dabf4809029837-i/] 时。因此，不要编译同一个模板多次。 语法 变量 [crayon-69eeaec6dabf8640935229-i/]  输出当前上下文中title变量的值 [crayon-69eeaec6dabfa899154787-i/] 输出父上下文中title变量的值 [crayon-69eeaec6dabfc585767482-i/] 输出父父上下文中title变量的值 [crayon-69eeaec6dabfe034398050-i/] 输出当前上下文对象 [crayon-69eeaec6dac01455572363-i/]  输出当前上下文中person变量name属性的值 [crayon-69eeaec6dac03854835116-i/] 输出父上下文中person变量name属性的值 [crayon-69eeaec6dac05556466638-i/] 访问额外数据变量，这些数据变量可以在助手中访问 块表达式 [crayon-69eeaec6dac07551828951-i/] 块表达式开始 [crayon-69eeaec6dac09299516246-i/] 块表达式结束 [crayon-69eeaec6dac0b258307613-i/] 反转块表达式开始 [crayon-69eeaec6dac0d160223506-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-69eeaec6dc905889055962/] “固定”布局 与静态布局类似，每个页面拥有自己的导航/工具栏，但是导航/工具栏不随页面滚动，它们分别固定在顶部/底部，始终可见。启用固定布局很简单，只需要在页面元素上添加样式类： [crayon-69eeaec6dc90a135084083/] “贯穿”布局 这是最常使用的一种方式，一个视图中的所有页面共享导航/工具栏。该布局支持动态导航栏（Dynamic Navbar）： [crayon-69eeaec6dc90d534915432/] 混合布局 F7支持在一个视图内混合使用多种布局方式。例如你可以使用贯穿的导航栏 + 固定的工具栏： [crayon-69eeaec6dc90f061306465/] 不使用导航/工具栏 你也可以不使用导航栏和（或）工具栏，只要不声明它们、同时不在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-69eeaec6e5b2e450705468-i/] 文件中。我们可以基于Less.js修改和编译样式。 F7还包括更多的特性： 原生的滚动效果（Native Scrolling） 不依赖任何第三方框架 高性能的，基于CSS的动画 支持缓存、历史、预加载（Preloading） 支持无限制的多视图，不需要任何JavaScript代码即可控制视图 简洁的JavaScript API DOM7：内置的DOM操控库，风格类似于jQuery 起步：第一个F7应用 基本布局：index.html 该页面声明了应用程序基本的布局： [crayon-69eeaec6e5b34687833508/] <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>PhoneGap学习笔记</title>
		<link>https://blog.gmem.cc/phonegap-study-note</link>
		<comments>https://blog.gmem.cc/phonegap-study-note#comments</comments>
		<pubDate>Tue, 01 Dec 2015 07:21:52 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Android]]></category>
		<category><![CDATA[IOS]]></category>
		<category><![CDATA[JavaScript]]></category>
		<category><![CDATA[Cordova]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=9504</guid>
		<description><![CDATA[<p>由于PhoneGap和Apache Cordova在底层上的一致性，本文主要讨论PhoneGap提供的额外工具，其他主题请参考： 基于Apache Cordova开发移动应用程序 PhoneGap简介 PhoneGap是什么 一个框架，支持通过标准的基于JavaScript的API，来开发跨平台的移动应用。PhoneGap同时支持基于Web、基于Native两种风格的混合应用，它通过联合使用Native元素和Web元素，来达到更加Native的look and feel。通过PhoneGap底层的Apache Cordova框架，我们可以使用很多设备的原生特性，例如GPS、罗盘、摄像头。 基于PhoneGap的混合应用有以下额外的特点： 不能完整调用原生API：只有受限的、通用的基础API被Cordova框架支持 平台差异化不足，应用界面长得差不多 PhoneGap与Apache Cordova的关系 后者是PhoneGap的底层框架，PhoneGap是后者的一个distribution。两者提供的命令行工具很类似，使用一套插件体系。PhoneGap提供了一些额外功能，包括： PhoneGap Developer App PhoneGap Desktop App <a class="read-more" href="https://blog.gmem.cc/phonegap-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/phonegap-study-note">PhoneGap学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></description>
				<content:encoded><![CDATA[<div class="wri_content_clear_both"><p>由于PhoneGap和Apache Cordova在底层上的一致性，本文主要讨论PhoneGap提供的额外工具，其他主题请参考：</p>
<ol>
<li><a href="/mobile-app-dev-with-apache-cordova">基于Apache Cordova开发移动应用程序</a></li>
</ol>
<div class="blog_h1"><span class="graybg">PhoneGap简介</span></div>
<div class="blog_h2"><span class="graybg">PhoneGap是什么</span></div>
<p>一个框架，支持通过标准的基于JavaScript的API，来开发跨平台的移动应用。PhoneGap同时支持<span style="background-color: #c0c0c0;">基于Web、基于Native</span>两种风格的混合应用，它通过联合使用Native元素和Web元素，来达到更加Native的look and feel。通过PhoneGap底层的Apache Cordova框架，我们可以使用很多设备的原生特性，例如GPS、罗盘、摄像头。</p>
<p>基于PhoneGap的混合应用有以下额外的特点：</p>
<ol>
<li>不能完整调用原生API：只有受限的、通用的基础API被Cordova框架支持</li>
<li>平台差异化不足，应用界面长得差不多</li>
</ol>
<div class="blog_h2"><span class="graybg">PhoneGap与Apache Cordova的关系</span></div>
<p>后者是PhoneGap的底层框架，PhoneGap是后者的一个distribution。两者提供的<span style="background-color: #c0c0c0;">命令行工具很类似，使用一套插件体系</span>。PhoneGap提供了一些额外功能，包括：</p>
<ol>
<li>PhoneGap Developer App</li>
<li>PhoneGap Desktop App</li>
<li>PhoneGap Enterprise</li>
<li>云端构建功能</li>
</ol>
<div class="blog_h1"><span class="graybg">安装PhoneGap和设备端工具</span></div>
<div class="blog_h2"><span class="graybg">命令行工具（CLI）</span></div>
<p>PhoneGap依赖于Node.js、NPM（Node.js目前自带）、git，准备好后，运行：</p>
<pre class="crayon-plain-tag">npm install -g phonegap@latest</pre>
<p>安装结束后，即可运行命令<pre class="crayon-plain-tag">phonegap </pre> ，来执行工程<span style="background-color: #c0c0c0;">创建、构建、安装、启动HTTP服务</span>等功能。除了使用命令行工具外，PhoneGap还提供了<a href="http://build.phonegap.com/">云端构建服务</a>，简化构建、App打包流程。</p>
<div class="blog_h2"><span class="graybg">桌面工具（PhoneGap Desktop App）</span></div>
<p>PhoneGap为Windows、Mac OS X提供了图形化界面，使用它可以方便的创建新的PhoneGap应用。可以到<a href="https://github.com/phonegap/phonegap-app-desktop/releases/download/0.2.0/PhoneGapSetup-win32.exe">这里</a>下载Windows的安装包。</p>
<p>该工具价值不大，使用PhoneGap CLI可以很容易的代替之。</p>
<div class="blog_h2"><span class="graybg"><a id="device-tool"></a>设备端工具</span></div>
<p>PhoneGap提供了一个移动APP——<a href="http://app.phonegap.com/">PhoneGap Developer App</a>（以下简称PGDA），可以方便的在设备上<span style="background-color: #c0c0c0;">预览、测试</span>PhoneGap应用。不需要<span style="background-color: #c0c0c0;">任何插件的支持或者编译步骤</span>，该APP就可以支持设备Native特性的访问。</p>
<p>该APP有安卓、IOS、WP三个版本，可以到对应的商店下载安装。</p>
<div class="blog_h3"><span class="graybg">定制PGDA</span></div>
<p>PGDA本质上就是一个Phonegap应用程序，只是它具有从远程服务器上同步最新Web代码的能力。PGDA默认自带了Cordova内置插件，如果你要使用第三方插件，必须自己构建PGDA。</p>
<p>PGDA的源码托管在：<a href="https://github.com/phonegap/phonegap-app-developer">https://github.com/phonegap/phonegap-app-developer</a></p>
<p>下载源码后，可以参考如下方式添加新插件支持，并构建（以Android为例）：</p>
<pre class="crayon-plain-tag">cd phonegap-app-developer
# 添加NFC插件的支持
cordova plugin add phonegap-nfc
#构建Android平台
cordova build android</pre>
<p>构建完毕后，文件platforms\android\build\outputs\apk\android-debug.apk即为最新的PGDA安装包，安装到设备即可。 </p>
<div class="blog_h1"><span class="graybg">Hello World!</span></div>
<p>打开PhoneGap桌面版本，点击左侧的+号，即可创建新的PhoneGap应用。输入路径、名称、ID后，即可创建工程。新创建的工程的结构如下：</p>
<table class="fixed-word-wrap" style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 25%; text-align: center;">目录/文件</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>.cordova</td>
<td>存放Apache Cordova相关的文件 </td>
</tr>
<tr>
<td>hooks</td>
<td rowspan="5">这些目录/文件与Apache Cordova工程同名目录/文件的含义相同</td>
</tr>
<tr>
<td>platforms</td>
</tr>
<tr>
<td>plugins</td>
</tr>
<tr>
<td>www</td>
</tr>
<tr>
<td>config.xml</td>
</tr>
</tbody>
</table>
<p>亦可导入既有的PhoneGap应用，Apache Cordova应用也是支持的，可以导入。</p>
<p>点击项目右侧的右侧的运行按钮，会启动一个HTTP服务，侦听3000端口。</p>
<p>打开移动设备，点击PGDA图标，输入上述HTTP服务的地址和端口，点击Connect，看到<span style="background-color: #c0c0c0;">DEVICE IS READY</span>字样，说明设备和HTTP服务器连接成功。在此设备界面上：</p>
<ol>
<li>四指轻触：刷新视图</li>
<li>三指轻触：回到PGDA主界面</li>
</ol>
<p>现在，可以打开<pre class="crayon-plain-tag">www/index.html</pre> 下的文件进行编辑，例如我们可以修改<pre class="crayon-plain-tag">&lt;h1&gt;PhoneGap&lt;/h1&gt;</pre>中的文本。其结果将很快的反映到移动设备上。</p>
<div class="blog_h1"><span class="graybg">PhoneGap CLI详解</span></div>
<p>命令格式：<pre class="crayon-plain-tag">phonegap [子命令] [选项]</pre> </p>
<div class="blog_h3">全局命令</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>create</td>
<td>
<pre class="crayon-plain-tag">phonegap create [options] &lt;path&gt; [id [name [config]]]</pre></p>
<p>创建新的PhoneGap工程：<br />path 在此目录创建工程<br />id 作为应用程序唯一标识的包名称<br />name 应用程序的名称<br />config JSON格式的配置信息，将被存放到.cordova/config.json目录<br />options<br />　--template &lt;name&gt; 从既有的应用模板创建，template命令可以管理模板<br />　--copy-from &lt;path&gt; 从既有的工程复制并创建<br />　--link-to &lt;path&gt; 链接到既有的www目录而不复制</p>
<p>举例：</p>
<p><pre class="crayon-plain-tag">phonegap create project-dir cc.gmem.phonegap.study PhonegpStudy</pre>
</td>
</tr>
<tr>
<td>help</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;"><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><span style="line-height: 19px;">run</span></td>
<td><span style="font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: small;"><span style="line-height: 19px;"><pre class="crayon-plain-tag">phonegap run [&lt;platforms&gt;] [options] [-- [platform options]]</pre> <br />准备、构建、安装应用程序到指定平台，如果不提供platforms参数，则对所有已添加的平台执行<br />options<br /></span></span>　--device 安装到设备<br />　--emulator 安装到模拟器<br />　--target &lt;id&gt; 安装到指定的目标<br />　--debug 以Debug模式构建（默认）<br />　--release 以Release模式构建<br />　--nobuild 跳过构建，直接安装</td>
</tr>
<tr>
<td><span style="line-height: 19px;">platform</span></td>
<td><span style="line-height: 19px;">管理平台，支持add、remove、update、check、list操作，与Cordova命令基本一致</span></td>
</tr>
<tr>
<td><span style="line-height: 19px;">plugin</span></td>
<td><span style="line-height: 19px;">管理插件，支持add、remove、list等操作，与Cordova命令基本一致</span></td>
</tr>
<tr>
<td><span style="line-height: 19px;">template</span></td>
<td>
<pre class="crayon-plain-tag"># 列出可用模板
phonegap template list</pre></p>
<p>从既有工程创建模板，或者列出可用模板</p>
</td>
</tr>
<tr>
<td><span style="line-height: 19px;">info</span></td>
<td><span style="line-height: 19px;">显示工程的相关信息 ，与Cordova命令基本一致</span></td>
</tr>
<tr>
<td><span style="line-height: 19px;">serve</span></td>
<td><span style="line-height: 19px;"><pre class="crayon-plain-tag">phonegap serve [options]</pre> <br />作为本地Web应用程序运行，PGDA可以连接到这样的Web服务：<br />options<br />　--port, -p &lt;n&gt; HTTP端口，默认3000<br />　--autoreload 当服务器端文件发生变化时，自动刷新PGDA，默认true<br />　--no-autoreload 禁止上述刷新<br />　--browser 启用桌面浏览器支持，默认false<br /></span></td>
</tr>
<tr>
<td><span style="line-height: 19px;">remote</span></td>
<td><pre class="crayon-plain-tag">phonegap remote [command]</pre> <br />在PhoneGap云端构建服务上执行命令：<br />command<br />　login　登录到云端构建<br />　logout 从云端构建退出登录<br />　build &lt;platform&gt; 构建指定的平台<br />　install &lt;platform&gt; 安装指定的平台<br />　run &lt;platform&gt; 构建并安装指定的平台</td>
</tr>
<tr>
<td><span style="line-height: 19px;">prepare</span></td>
<td rowspan="3"><span style="line-height: 19px;">与对应的Cordova命令基本一致</span></td>
</tr>
<tr>
<td><span style="line-height: 19px;">compile</span></td>
</tr>
<tr>
<td><span style="line-height: 19px;">emulate</span></td>
</tr>
<tr>
<td><span style="line-height: 19px;">cordova</span></td>
<td><span style="line-height: 19px;">执行指定的Cordova命令</span></td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">PhoneGap云构建服务</span></div>
<p>利用Adobe提供的PhoneGap云端服务，只需要上传PhoneGap应用程序的资产文件（HTML、CSS、JavaScript），后续的构建工作可以在云端完成。使用云构建服务，可以：</p>
<ol>
<li>避免本地维护各套平台SDK、甚至安装多套操作系统的麻烦</li>
<li>加快开发周期，在云端可以更快的构建，并且构建结果可以推送到测试人员预安装的App中</li>
</ol>
<p>云构建服务分为免费、收费版本，免费版本仅支持一个私有（闭源、商业）App。</p>
<div class="blog_h1"><span class="graybg">常见问题</span></div>
<div class="blog_h3"><span class="graybg">PhoneGap Developer App不自动重新加载</span></div>
<p>自动重新加载时，PhoneGap CLI会在控制台上打印信息：<pre class="crayon-plain-tag">[phonegap] 200 /__api__/autoreload</pre> </p>
<p>自动重新加载的前提条件是，页面引用cordova的JavaScript文件：</p>
<pre class="crayon-plain-tag">&lt;script type="text/javascript" src="cordova.js"&gt;&lt;/script&gt;</pre>
<p>此外，某些版本上的模拟器不能正常reload，可能是兼容性问题。</p>
<div class="blog_h3"><span class="graybg">iOS6上PhoneGap Developer App报错：404 /__api__/appzip</span></div>
<p>我的手机是越狱后的iOS 6.1.2，在AppStore下载的PGDA 1.5.12，无法连接到通过<pre class="crayon-plain-tag">phonegap serve</pre> 启动的服务器。 </p>
<p>到<a href="https://github.com/phonegap/phonegap-app-developer/tree/master/resources/release/ios">GitHub</a>上逐个下载预构建版本并安装测试，发现：</p>
<ol>
<li>1.5.x：全部不能使用</li>
<li>1.4.0：可以连接但无法自动重新加载</li>
<li>1.3.0：能够连接，也可以autoreload</li>
</ol>
<p>很无语……现在安装1.3.0正常使用。</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/phonegap-study-note">PhoneGap学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/phonegap-study-note/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>基于Apache Cordova开发移动应用程序</title>
		<link>https://blog.gmem.cc/mobile-app-dev-with-apache-cordova</link>
		<comments>https://blog.gmem.cc/mobile-app-dev-with-apache-cordova#comments</comments>
		<pubDate>Mon, 09 Nov 2015 09:20:41 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Android]]></category>
		<category><![CDATA[IOS]]></category>
		<category><![CDATA[Mobile]]></category>
		<category><![CDATA[Cordova]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=9730</guid>
		<description><![CDATA[<p>基础知识 名词术语 目标平台：本文中会反复出现该名词，目标平台是指移动操作系统+移动应用SDK构成的移动应用支撑环境。有时也称作移动平台，或简称平台 构建平台：在其上运行Cordova工具，生成目标平台应用程序的宿主操作系统 PhoneGap：目前作为Apache Cordova的一个发行版，它们的命令行非常相似。PhoneGap提供了一些更高级的组件，例如云端构建、设备端工具（PhoneGap Developer App） 移动应用开发技术的选型 可以选择三种方式来开发移动应用，它们各有特点： 原生（Native）应用 为特定的设备、操作系统构建。需要熟悉设备SDK，甚至特殊的开发语言：Objective-C/Swift (iOS)；Java (Android)；C# (Windows) 安装到设备，作为本地程序运行 使用原生的API和控件 通常必须经过应用市场的验证才能被客户使用 可以离线使用 性能优秀 纯Web应用 在任何移动浏览器中运行 <a class="read-more" href="https://blog.gmem.cc/mobile-app-dev-with-apache-cordova">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/mobile-app-dev-with-apache-cordova">基于Apache Cordova开发移动应用程序</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_h2"><span class="graybg">名词术语</span></div>
<ol>
<li>目标平台：本文中会反复出现该名词，目标平台是指<span style="background-color: #c0c0c0;">移动操作系统+移动应用SDK</span>构成的移动应用支撑环境。有时也称作移动平台，或简称平台</li>
<li>构建平台：在其上运行Cordova工具，生成目标平台应用程序的宿主操作系统</li>
<li>PhoneGap：目前作为Apache Cordova的一个发行版，它们的命令行非常相似。PhoneGap提供了一些更高级的组件，例如云端构建、设备端工具（PhoneGap Developer App）</li>
</ol>
<div class="blog_h2"><span class="graybg">移动应用开发技术的选型</span></div>
<p>可以选择三种方式来开发移动应用，它们各有特点：</p>
<div class="blog_h3"><span class="graybg">原生（Native）应用</span></div>
<ol>
<li>为特定的设备、操作系统构建。需要熟悉设备SDK，甚至特殊的开发语言：Objective-C/Swift (iOS)；Java (Android)；C# (Windows)</li>
<li>安装到设备，作为本地程序运行</li>
<li>使用原生的API和控件</li>
<li>通常必须经过应用市场的验证才能被客户使用</li>
<li>可以离线使用</li>
<li>性能优秀</li>
</ol>
<div class="blog_h3"><span class="graybg">纯Web应用</span></div>
<ol>
<li>在任何移动浏览器中运行</li>
<li>支持受限的使用设备特性</li>
<li>基于标准的Web技术构建，包括HTML、CSS、JavaScript</li>
<li>需要支持多种屏幕尺寸</li>
<li>需要Web服务器的支撑，不支持离线使用</li>
<li>性能较差</li>
</ol>
<div class="blog_h3"><span class="graybg">混合（Hybrid）应用</span></div>
<p>所谓混合应用，就是结合了前两者的特性和优势的应用，混合应用可以分为两种风格：</p>
<ol>
<li>基于Web的混合应用（Web hybrid mobile apps）：APP运行在一个很薄的<span style="background-color: #c0c0c0;">Native容器+WebView</span>中，该容器仅作为访问Native特性的桥梁，所有UI组件均基于Web技术构建</li>
<li>基于Native的混合应用（Native hybrid mobile apps）：包含很多的Native控件，以及1-N个WebView。常常<span style="background-color: #c0c0c0;">使用Native控件做导航、并在提供应用内容的WebView之间进行切换</span></li>
</ol>
<p>混合应用具有以下特性：</p>
<ol>
<li>安装到设备，作为本地程序运行</li>
<li>应用逻辑主要在WebView（嵌入的Web浏览器）中运行</li>
<li>大部分代码是跨平台的</li>
<li>支持访问原生特性</li>
<li>由于应用内容主要使用基于设备内置浏览器引擎，因此性能比起原生应用差</li>
</ol>
<div class="blog_h1"><span class="graybg">Apache Cordova简介</span></div>
<p>Apache Cordova是一个开源移动应用开发框架，它允许你使用标准的Web技术——HTML5、CSS3、JavaScript ——来进行跨平台开发，避免使用各移动平台Native的编程语言和SDK。基于Cordova的应用程序被包装在各平台的Wrapper中，使用标准兼容的API访问设备传感器、数据和其它Native特性。</p>
<div class="blog_h2"><span class="graybg">基本组件</span></div>
<p>Apache Cordova 工程依赖于一个命名为<span style="background-color: #c0c0c0;">config.xml</span>的配置文件，该配置文件影响应用程序的工作方式（例如是否对横屏做出响应）。</p>
<p>应用程序本身实现为<span style="background-color: #c0c0c0;">网页</span>，默认情况下是本地的一个index.html文件，该文件可能引用任何Web资源，例如JavaScript、CSS。应用程序被包装在Native的Wrapper中，作为WebView运行。该WebView作为更大的混合（Hybrid ）应用的一个组件，或者占据整个UI。</p>
<p><span style="background-color: #c0c0c0;">插件接口（plugin interface ）</span>允许Cordova和Native组件之间进行通信，插件接口以JavaScript的形式提供，从而允许在网页上调用Native代码。理想情况下，各平台的API应该一致，在3.0中，官方插件提供标准化的设备API，而<a href="http://plugins.cordova.io/">第三方插件</a>可能提供一些特定于特殊平台的API。Cordova官方提供了自定义插件的<a href="https://cordova.apache.org/docs/en/latest/guide/hybrid/plugins/index.html">开发文档</a>。</p>
<p>从3.0开始，新创建的Cordova工程默认不包含任何插件，所有插件都需要手工添加。</p>
<div id="cordova-workflow" class="blog_h2"><span class="graybg">开发工作流（Workflow）</span></div>
<p>Cordova 应用的开发工作流有两种：</p>
<ol>
<li>以Cordova CLI为中心跨平台工作流：如果需要应用在尽可能多的平台上运行，可以使用这种方式，该方式需要很少的平台特定开发工作量。CLI是3.0引入的，可以同时为多个平台构建工程的工具，它是Cordova跨平台工作流的核心。CLI会<span style="background-color: #c0c0c0;">创建Cordova工程的初始结构、把Web资产文件拷贝到各平台的子目录下、对各平台进行必要的配置，然后运行脚本生成二进制程序</span>。CLI也提供了为应用添加插件的公共接口</li>
<li>以平台为中心的工作流：如果需要构建特定单个平台的应用，以及需要在底层基于平台SDK对代码进行修改，可以使用该方式。典型的例子是开发Native-based混合应用，需要把WebView和其他Native组件集成时。使用这种方式开发时，可以通过<span style="background-color: #c0c0c0;">Plugman</span>来添加插件</li>
</ol>
<p>推荐以第1种方式开始，使用CLI创建Cordova工程，在<span style="background-color: #c0c0c0;">必要时切换</span>到第2种方式。针对平台的低级别Shell工具可以在工程的<pre class="crayon-plain-tag">platforms/*/cordova</pre> 目录下找到（其中*代表平台名称）。<span style="background-color: #c0c0c0;">一旦切换，就不能返回</span>，因为CLI管理的跨平台源代码集会在每次构建时被覆盖。为了保护你修改过的代码，必须使用平台特定的Shell工具，这些Shell工具忽略跨平台代码而依赖于平台特定的代码。</p>
<div class="blog_h1"><span class="graybg">基于CLI的跨平台工作流</span></div>
<p>使用Cordova的命令行接口（CLI），可以完成新工程的创建、针对各平台进行构建、并部署到设备（或模拟器）中运行。</p>
<p>在安装和使用CLI前，必须下载和安装相关目标平台的SDK和相关工具，如果不安装，在调用CLI进行应用构建时会收到错误提示，例如：</p>
<pre class="crayon-plain-tag">#没有安装完整Android SDK机器上的报错信息
Running command: cmd "/s /c "...\cordova-study\platforms\android\cordova\build.bat""
[Error: Please install Android target: "android-22".

Hint: Open the SDK manager by running: D:\Android\sdk\tools\android.BAT
You will require:
1. "SDK Platform" for android-22
2. "Android SDK Platform-tools (latest)
3. "Android SDK Build-tools" (latest)]
ERROR building one of the platforms: Error: cmd: Command failed with exit code 2
You may not have the required environment or OS to build this project</pre>
<p>CLI支持的<span style="background-color: #c0c0c0;">目标平台（构建平台）</span>包括：</p>
<ol>
<li>iOS（Mac）</li>
<li>Android (Mac, Linux, Windows)</li>
<li>BlackBerry 10 (Mac, Linux, Windows)</li>
<li>Windows Phone 8 (Windows)</li>
</ol>
<p>可以看到如果要支持全部主要的目标平台，你需要在不同操作系统上运行CLI命令，这意味着你应该使用代码库（SVN/Git）来管理工程。</p>
<div class="blog_h2"><span class="graybg">安装Cordova CLI</span></div>
<ol>
<li><a href="/node-js-faq">安装Node.js</a></li>
<li><a href="https://git-scm.com/downloads">安装Git客户端</a></li>
<li>通过npm命令安装cordova模块：<br />
<pre class="crayon-plain-tag"># 安装Cordova为全局模块
npm install -g cordova
#安装特定版本的Cordova
npm install -g cordova@3.1.0-0.2.0

#更新Cordova
npm update -g cordova

#显示版本信息
cordova -v</pre>
</li>
</ol>
<div class="blog_h2"><span class="graybg">创建Cordova工程</span></div>
<p>执行下面的命令创建工程：</p>
<pre class="crayon-plain-tag">pushd D:\JavaScript\projects\cordova\5.4.1
# cordova-study在当前目录下创建的子目录，作为工程文件的容器
# cc.gmem.study.cordova 域名反转的包名，可能被生成的外部文件引用，例如Android包名
# CordovaStudy 应用程序的标题
# 第2/3个参数可以在config.xml中手工修改
cordova create cordova-study cc.gmem.study.cordova CordovaStudy</pre>
<p>命令执行完毕后，生成如下目录结构： </p>
<table class="fixed-word-wrap" style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 25%; text-align: center;">目录/文件</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>hooks</td>
<td>Cordova钩子脚本的存放目录</td>
</tr>
<tr>
<td>platforms</td>
<td>生成的平台特定的文件，包括工程代码和元数据。基于CLI的跨平台工作流进行开发时，应该避免修改此目录下的任何文件</td>
</tr>
<tr>
<td>plugins</td>
<td>应用程序使用的插件目录</td>
</tr>
<tr>
<td>merges</td>
<td>用于合并到各平台assets/www中的文件，特定于平台的文件，按照www的结构，在此目录下存放，在构建时会自动合并</td>
</tr>
<tr>
<td>www</td>
<td>存放应用程序的主页面（index.html），以及css、js、img等Web资源。这些资产内容会<span style="background-color: #c0c0c0;">存放在设备的本地文件系统，而不是远程Web服务器</span>上。开发Cordova应用的主要工作在此目录下进行</td>
</tr>
<tr>
<td>config.xml</td>
<td>包含了用于生成和分发应用程序的重要元数据</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">添加目标平台支持</span></div>
<p>运行下面的命令添加对特定平台的支持：</p>
<pre class="crayon-plain-tag">#添加Android平台的支持
cordova platform add android
#输出如下：
Running command: cmd "/s /c "...\create.bat ...\cordova-study\platforms\android cc.gmem.study.cordova CordovaStudy --cli""
Creating Cordova project for the Android platform:
        #创建目标平台工程特定的文件
        Path: platforms\android
        Package: cc.gmem.study.cordova
        Name: CordovaStudy
        Activity: MainActivity
        Android target: android-22
Copying template files...
Android project created with cordova-android@4.1.1
#安装插件
Discovered plugin "cordova-plugin-whitelist" in config.xml. Installing to the project
Fetching plugin "cordova-plugin-whitelist@1" via npm
Installing "cordova-plugin-whitelist" for android


#添加iOS平台的支持，需要在安装了iOS SDK的Mac OS X上执行
cordova platform add ios
#添加Windows Phone 8平台的支持，需要在Windows上运行
cordova platform add wp8


#下面的命令示例了如何移除平台的支持
cordova platform remove blackberry10

#添加指定版本的cordova平台支持，并保存到config.xml中
cordova platform add android@3.7.0 --save</pre>
<p>运行上述命令后，Cordova工程的platforms、plugins子目录出现一些下载/生成的文件：</p>
<table class="fixed-word-wrap" style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 140px; text-align: center;">目录</td>
<td style="width: 100px; text-align: center;">子目录</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td rowspan="7">platforms/android      </td>
<td>.</td>
<td>当前目录包含了一个完整的Android工程，可以导入到Android Studio或者ADT中</td>
</tr>
<tr>
<td>cordova</td>
<td>特定于Android平台的Shell工具，可以完成Android平台应用的构建、清理、运行</td>
</tr>
<tr>
<td>CordovaLib</td>
<td>一个Android工程，包含了作为Android库的Cordova源代码</td>
</tr>
<tr>
<td>assets</td>
<td rowspan="4">
<p>遵循Android工程的默认用途</p>
<p>其中：</p>
<ol>
<li>assets/www包含了Cordova应用的所有Web资源文件，包括HTML、CSS、JS等。<span style="background-color: #c0c0c0;">Cordova工程中的www目录中的内容全部复制一份</span>覆盖到该目录。插件的Web资源也拷贝并覆盖到plugins子目录</li>
<li>src目录包含自动生成的一个Activity源码、已经拷贝过来的插件源代码</li>
</ol>
</td>
</tr>
<tr>
<td>src</td>
</tr>
<tr>
<td>libs</td>
</tr>
<tr>
<td>res</td>
</tr>
<tr>
<td>plugins</td>
<td colspan="2">存放下载的对应各目标平台的插件源代码、文档</td>
</tr>
</tbody>
</table>
<p><a href="#cordova-workflow">前面</a>我们说过，创建新工程可以从CLI命令行开始，在必要时切换到特定平台的开发工作中去。在此，就可以进行切换了：只需要把platforms子目录导入到Eclipse、XCode等IDE即可，然后以IDE为中心完成后续开发。</p>
<p>如果继续执行以CLI为中心的工作流，则需要继续了解下面的小节。</p>
<div class="blog_h2"><span class="graybg">构建应用</span></div>
<p>执行下面的命令可以构建应用程序的二进制文件并打包：</p>
<pre class="crayon-plain-tag"># 构建所有平台
cordova build
# 构建特定平台
cordova build ios</pre>
<p>上述build命令实际上包含了两个步骤： </p>
<pre class="crayon-plain-tag"># 准备阶段，会把Cordova源码拷贝、合并到平台的对应目录中
# 一旦执行完该命令，就可以用特定平台提供的SDK进行修改、编译
cordova prepare ios
# 编译
cordova compile ios</pre>
<div class="blog_h2"><span class="graybg">在设备上测试应用</span></div>
<p>运行下面的命令重新构建特定平台的应用，并部署到模拟器中：</p>
<pre class="crayon-plain-tag"># 运行下面的命令后，Cordova会自动启动模拟器，并把APK安装到上面
# 如果有模拟器正在运行，则使用既有的模拟器
cordova emulate android
# APK安装完毕后，将自动运行</pre>
<p> 或者，将启用了调试模式的手机连接到当前计算机，然后运行：</p>
<pre class="crayon-plain-tag">cordova run android</pre>
<div class="blog_h2"><span class="graybg">添加插件支持</span></div>
<p>你可以使用Web技术修改Cordova生成的应用骨架，来开发漂亮的页面。但是如果<span style="background-color: #c0c0c0;">想使用设备底层特性，则必须添加插件</span>，利用插件可以访问核心Cordova API。第三方/自己开发的插件可以用来访问特殊的设备特性，要访问通用设备特性使用Cordova提供的的插件就足够了。官方插件和第三方插件的列表位于<a href="http://cordova.apache.org/plugins/">http://cordova.apache.org/plugins/</a>。</p>
<p>下面的命令用来搜索插件：</p>
<pre class="crayon-plain-tag">#同时包含bar和code的插件
cordova plugin search bar code</pre>
<p>下面的命令用来列出当前工程已添加的插件：</p>
<pre class="crayon-plain-tag">cordova plugin ls</pre>
<p>下面的命令用来移除已经添加的插件：</p>
<pre class="crayon-plain-tag">cordova plugin rm cordova-plugin-console</pre>
<p>下面的命令用来给应用程序添加插件：</p>
<pre class="crayon-plain-tag">#依据插件ID添加
cordova plugin add plugin-id
#添加特定版本的插件
cordova plugin add plugin-id@latest
cordova plugin add plugin-id@1.2.0

# 设备基本特性
cordova plugin add cordova-plugin-device
# 网络连接信息
cordova plugin add cordova-plugin-network-information
# 电池状态
cordova plugin add cordova-plugin-battery-status
# 加速度计
cordova plugin add cordova-plugin-device-motion
# 横屏切换
cordova plugin add cordova-plugin-device-orientation
# 地理位置信息
cordova plugin add cordova-plugin-geolocation
# 摄像头
cordova plugin add cordova-plugin-camera
# 媒体截屏
cordova plugin add cordova-plugin-media-capture
# 媒体回放
cordova plugin add cordova-plugin-media
# 访问设备或网络上的文件
cordova plugin add cordova-plugin-file
cordova plugin add cordova-plugin-file-transfer
# 通过振动或者对话框通知用户
cordova plugin add cordova-plugin-dialogs
cordova plugin add cordova-plugin-vibration
# 访问通讯录
cordova plugin add cordova-plugin-contacts
# 国际化
cordova plugin add cordova-plugin-globalization
# 启动画面支持
cordova plugin add cordova-plugin-splashscreen
# 打开新的应用内浏览器窗口
cordova plugin add cordova-plugin-inappbrowser
# 调试控制台
cordova plugin add cordova-plugin-console</pre>
<p>CLI根据需要为各平台添加插件代码。Plugman可以为单个平台添加插件支持。 </p>
<p>如果插件没有在默认源（registry.cordova.io ）注册，通过下面的命令添加：</p>
<pre class="crayon-plain-tag"># 从Git的master分支下载插件
cordova plugin add https://github.com/someone/aplugin.git
# 从特定标记或者分支下载插件
cordova plugin add https://github.com/someone/aplugin.git#r0.2.0
# 如果插件（以及plugin.xml）在Git仓库的子目录下，可以
cordova plugin add https://github.com/someone/aplugin.git#:/sub/dir
# 也可以从本地文件系统添加插件
cordova plugin add ../local_plugin_dir</pre>
<div class="blog_h2"><span class="graybg">利用合并进行平台差异化处理</span></div>
<p>前面提到过，在以CLI为中心的跨平台开发工作流下，不应当修改platforms子目录。那么，如果如何实现平台差异化呢？例如为不同平台提供不同的样式表.css文件，这是很合理的需求。</p>
<p>Cordova提供了一种合并机制，在工程顶级目录下，可以添加一个merges目录，其子目录是各平台的名称，例如<pre class="crayon-plain-tag">merges/android</pre> 。这些子目录的内部结构和www目录一样，在构建时，会自动覆盖www中的文件，并最终拷贝到特定平台的资产目录下。举例来说：<pre class="crayon-plain-tag">merges/android/css/index.css</pre> 会覆盖Android平台下对应的<pre class="crayon-plain-tag">www/css/index.css</pre> 文件。</p>
<p>merges中的文件如果在www下没有对应物，会直接复制到特定平台的资产目录下。</p>
<div class="blog_h2"><span class="graybg">CLI命令说明</span></div>
<p>命令格式：<pre class="crayon-plain-tag">cordova 子命令 [选项]</pre> </p>
<div class="blog_h3"><span class="graybg">通用选项</span></div>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="text-align: center;">选项</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>-v, --version</td>
<td>打印CLI版本</td>
</tr>
<tr>
<td>-d, --verbose</td>
<td>显示CLI命令的活动的详细信息</td>
</tr>
<tr>
<td>--no-update-notifier</td>
<td>禁止检查CLI的版本更新</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: 25%; text-align: center;">子命令</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>create</td>
<td>创建一个新的Cordova工程</td>
</tr>
<tr>
<td>help</td>
<td>显示命令帮助：<br />
<pre class="crayon-plain-tag"># 显示run子命令的帮助信息
cordova run --help</pre>
</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: 25%; text-align: center;">子命令</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>info</td>
<td>显示当前工程的详细信息，例如哪些平台被支持，添加了哪些插件</td>
</tr>
<tr>
<td>requirements</td>
<td>检查并显示指定平台的要求，例如SDK版本</td>
</tr>
<tr>
<td>platform</td>
<td>
<p>管理工程支持的平台：<br />add 添加一个平台支持<br />remove 移除平台支持<br />list 列出已经安装和可用的平台<br />update 更新特定平台使用的Cordova版本<br />check 列出支持cordova platform update的平台</p>
</td>
</tr>
<tr>
<td>plugin</td>
<td>管理工程使用的插件：<br />add 添加一个插件<br />remove 移除一个插件<br />list 列出当前安装的插件列表<br />search 从插件仓库搜索插件</td>
</tr>
<tr>
<td>prepare</td>
<td>拷贝、合并工程文件到目标平台，以备构建</td>
</tr>
<tr>
<td>compile</td>
<td>编译目标平台，命令格式：<br /><pre class="crayon-plain-tag">cordova compile [PROD] [TARGET] [EXP] [PLATS] [-- POPTS]</pre> </p>
<p>PROD：编译的变体， --debug|--release<br />TARGET：目标设备， --device|--emulator|--target=FOO<br />PLATS：目标平台，可以指定多个平台<br />POPTS：平台特定的选项 </p>
</td>
</tr>
<tr>
<td>build</td>
<td>等价于<pre class="crayon-plain-tag">cordova prepare &amp;&amp; cordova compile</pre> </td>
</tr>
<tr>
<td>clean</td>
<td>清除已经构建出的构件（artifacts）</td>
</tr>
<tr>
<td>run</td>
<td>prepare、compile，并在设备或者模拟器上运行工程：<br /> --nobuild 不进行构建<br />--debug 部署Debug版本<br />--release  部署Release版本</td>
</tr>
<tr>
<td>emulate</td>
<td>等价于<pre class="crayon-plain-tag">cordova run --emulator</pre> </td>
</tr>
<tr>
<td>serve</td>
<td>
<p>启动一个本地HTTP服务器，把工程作为Web服务使用：<br /><pre class="crayon-plain-tag">cordova serve [PORT]</pre> </p>
<p>此命令启动的服务与PhoneGap Developer App无法搭配使用，可以改用：</p>
<pre class="crayon-plain-tag">phonegap serve</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">以Android平台为中心的工作流</span></div>
<p>首先，参考文章：<a href="/eclipse-adt-and-mvn-based-android-dev">基于Eclipse ADT和Maven的Android开发</a> 完整Android开发环境的搭建。
<p>由于Android平台官方的开发工具现在是Android Studio，并且基于Gradle构建，所以最好搭建好Gradle环境和Gradle的Eclipse插件。可参考文章：<a href="/gradle-study-note">Gradle学习笔记</a>。</p>
<div class="blog_h2"><span class="graybg">导入为Eclipse ADT工程</span></div>
<p>通过CLI创建Cordova工程后，一旦添加了Android平台的支持，就可以把platforms/android子目录作为工程导入到Eclipse ADT或者Android Studio。</p>
<p>以Eclipse ADT为例，操作步骤为：</p>
<ol>
<li>File ⇨ Import</li>
<li>在弹出的对话框中选择 Android ⇨ Existing Android Code Into Workspace，点击下一步</li>
<li>把Root Directory定位到Cordova工程的platforms/android目录，选择其中的两个工程，如下图：<br /><img class="aligncenter size-full wp-image-9857" src="https://blog.gmem.cc/wp-content/uploads/2015/11/import-cordova-project.jpg" alt="import-cordova-project" width="679" height="550" /></li>
<li>点击完成，工作区出现两个新的ADT工程，导入完毕</li>
</ol>
<p>这两个工程就是普通的ADT工程。其中cordovalib是cordova-android的依赖库。在cordova-android上点击右键 ⇨ Run as ⇨ Android Application，即可打包并部署到设备上运行。</p>
<p>上述方式导入的工程结构比较混乱，如果后续开发脱离Cordova进行，建议清理其中不需要的目录和文件。如果不习惯基于Gradle进行构建，可以创建或者转换为Maven工程。</p>
<div class="blog_h2"><span class="graybg">转换为Eclipse Gradle工程</span></div>
<p>安装了Gradle的Eclipse插件后，可以把platforms/android子目录转换为Gradle工程：</p>
<ol>
<li>File ⇨ Import</li>
<li>在弹出的对话框中选择 Gradle ⇨ Gradle Project，点击下一步</li>
<li>在弹出的对话框中，把Root folder定位到platforms/android子目录，然后点击右侧的Build Model按钮。Gradle从Maven仓库下载一些构件到Gradle缓存目录，完毕后显示如下画面：<img class="aligncenter size-full wp-image-10269" src="https://blog.gmem.cc/wp-content/uploads/2015/11/import-as-gradle-project.jpg" alt="import-as-gradle-project" width="653" height="716" /></li>
<li>点击完成。你可以自由的修改Eclipse工程的名称</li>
</ol>
<div class="blog_h2"><span class="graybg">转换为Eclipse Maven工程</span></div>
<p>如果习惯于使用Maven进行构建工作，可以参考下面的步骤，把platforms/android子目录转换为Maven工程：</p>
<ol>
<li>参考：<a href="/eclipse-adt-and-mvn-based-android-dev">基于Eclipse ADT和Maven的Android开发</a>创建Android的Maven工程，设工程根目录为<pre class="crayon-plain-tag">%MVN_PROJECT_ROOT%</pre> 。删除自动生成的Activity、res目录，并转换为android-maven-plugin 4.3.0标准的工程结构</li>
<li>如果使用SVN，在目录%MVN_PROJECT_ROOT% 上设置属性svn:ignore为：<br />
<pre class="crayon-plain-tag">target
bin
.*
cli</pre>
</li>
<li>通过CLI创建对应的（同名同包）Cordova工程，添加必要的插件，设工程根目录为<pre class="crayon-plain-tag">%CORDOVA_PROJECT_ROOT%</pre> </li>
<li>创建Maven工程指向Cordova工程的符号链接：<br />
<pre class="crayon-plain-tag">mklink /D %MVN_PROJECT_ROOT%\cli %CORDOVA_PROJECT_ROOT%</pre>
</li>
<li>另外创建一个Android的Maven工程“cordova-android”，project.properties中设置<pre class="crayon-plain-tag">android.library=true</pre> ，表示这是一个库工程。设工程根目录为<pre class="crayon-plain-tag">%MVN_LIB_ROOT%</pre> </li>
<li>修改<pre class="crayon-plain-tag">%MVN_PROJECT_ROOT%\pom.xml</pre> ，添加cordova-android的依赖，例如：<br />
<pre class="crayon-plain-tag">&lt;dependency&gt;
    &lt;groupId&gt;org.apache.cordova&lt;/groupId&gt;
    &lt;artifactId&gt;cordova-android&lt;/artifactId&gt;
    &lt;version&gt;4.4.1-SNAPSHOT&lt;/version&gt;
    &lt;type&gt;apklib&lt;/type&gt;
&lt;/dependency&gt;</pre>
</li>
<li>修改Cordova工程的<pre class="crayon-plain-tag">%CORDOVA_PROJECT_ROOT%\cli\platforms\android\build-extras.gradle</pre> 文件，添加以下Tasks：<br />
<pre class="crayon-plain-tag">task syncdown_src(type : Copy){
    from 'src'
    into '../../../src/main/java'
}
task syncdown_assets(type : Copy){
    from 'assets'
    into '../../../src/main/assets'
}
task syncdown_res(type : Copy){
    from 'res'
    into '../../../src/main/res'
}
task syncdown_libsrc(type : Copy){
    def dest = '../../../../cordova-android/src/main/java'
    if(file(dest).exists()){
        from 'CordovaLib/src'
        into dest
    }
}
task syncdown_manifest(type : Copy){
    from 'AndroidManifest.xml'
    into '../../../src/main'
}
task syncdown( dependsOn: [syncdown_src, syncdown_assets, syncdown_res, syncdown_libsrc, syncdown_manifest] )</pre>
</li>
<li>每当添加Cordova插件，或者修改Cordova工程中其它源码文件后，定位到<pre class="crayon-plain-tag">%MVN_PROJECT_ROOT%\cli</pre>目录，执行：<br />
<pre class="crayon-plain-tag">@echo off
call cordova prepare 
pushd platforms\android
call gradle syncdown
popd</pre></p>
<p>这会让所有修改从Cordova工程目录，同步到其Android平台子目录，然后再同步到Maven工程对应目录中。 </p>
</li>
</ol>
<div class="blog_h2"><span class="graybg">Android平台命令行工具</span></div>
<p>Android平台特定的命令行工具位于<pre class="crayon-plain-tag">platforms/android/cordova</pre> 子目录中，这些命令比Cordova CLI更有针对性，在以Android为中心的开发时，可以代替之：</p>
<pre class="crayon-plain-tag">###  构建工程  ###

#Apache Cordova CLI方式
cordova build android

#Android平台命令行工具
platforms/android/cordova/build --debug   #生成调试信息
platforms/android/cordova/build --release #对应用进行签名
#其它参数
# --ant 使用Ant进行构建
# --gradle 使用Gradle进行构建，Cordova 4.0以后的默认值
# --nobuild 不进行构建
# --prepenv 不进行构建，但是拷贝必要的构建脚本
# --versionCode=# 指定本次构建的版本号
# --minSdkVersion=# 指定本次构建要求的最低Android SDK版本代码
# --gradleArg=# 指定传递给Gradle的命令行参数
# --buildConfig 指定包含了构建配置的JSON文件


###  部署到模拟器  ###
# 需要首先配置并启动一台Android模拟器

#Apache Cordova CLI方式
cordova emulate android

#Android平台命令行工具
platforms/android/cordova/run --emulator
platforms/android/cordova/run --target=targetId #指定特定ID的模拟器
#当执行run时，默认会自动构建，可以通过参数-debug --release --nobuild控制此行为
platforms/android/cordova/run --emulator --nobuild

#通过Eclipse部署
#也可以在Eclipse中，Run As - Android Application方式部署应用



###  部署到真实设备  ###

#Apache Cordova CLI方式
cordova run android

#Android平台命令行工具
platforms/android/cordova/run --device



###  其他Android平台命令行工具  ###
platforms/android/cordova/log    #记录APP详细的运行日志
platforms/android/cordova/clean  #清理工程文件</pre>
<p>使用Android命令行build、run时，可以通过<pre class="crayon-plain-tag">--buildConfig</pre> 传入一个JSON，为构建提供配置信息。下面的JSON提供了APK签名所需的信息：</p>
<pre class="crayon-plain-tag">{
     "android": {
         "debug": {
             "keystore": "android.keystore",
             "storePassword": "android",
             "alias": "mykey1", 
             "password" : "password",
             "keystoreType": ""
         },
         "release": {
             "keystore": "android.keystore",
             "storePassword": "", //Release的密码可以不写，等待命令行提示
             "alias": "mykey2",
             "password" : "password",
             "keystoreType": ""
         }
     }
 }</pre>
<div class="blog_h2"><span class="graybg">通过Gradle执行构建</span></div>
<p>自4.0开始Gradle是Cordova默认的构建工具。</p>
<p>下表列出Cordova提供的<a href="/gradle-study-note#gradle-project-properties">Gradle工程属性</a>，可以用于定制构建： </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>cdvBuildMultipleApks</td>
<td>
<p>默认值：false，表示为所有平台生成一个通用的大的APK</p>
<p>如果设置为true，对library projects支持的每个平台（x86, ARM...）分别生成一个APK。如果工程使用大的Native库时很重要，因为Native库可能大大增加APK体积</p>
</td>
</tr>
<tr>
<td>cdvVersionCode</td>
<td>覆盖AndroidManifest.xml中的versionCode</td>
</tr>
<tr>
<td>cdvReleaseSigningPropertiesFile</td>
<td>
<p>默认值：release-signing.properties</p>
<p>包含Release版本APK签名需信息的属性文件的名称，属性文件的内容示例如下：</p>
<pre class="crayon-plain-tag">storeFile=relative/path/to/keystore.p12
storePassword=SECRET1
storeType=pkcs12
keyAlias=DebugSigningKey
keyPassword=SECRET2
#密码是可选的，如果不提供，构建时会进行交互式的提示</pre>
</td>
</tr>
<tr>
<td>cdvDebugSigningPropertiesFile</td>
<td>
<p>默认值：debug-signing.properties
<p>包含Debug版本APK签名需信息的属性文件的名称</p>
</td>
</tr>
<tr>
<td>cdvBuildToolsVersion</td>
<td>覆盖自动检测到的android.buildToolsVersion属性值。该属性由Android的Gradle插件引入</td>
</tr>
<tr>
<td>cdvCompileSdkVersion</td>
<td>覆盖自动检测到的android.compileSdkVersion属性值。该属性由Android的Gradle插件引入</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">扩展Gradle构建脚本</span></div>
<p>如果需要扩展Gradle构建逻辑，可以创建一个<pre class="crayon-plain-tag">build-extras.gradle</pre> 文件，并在其中编写脚本。该脚本会自动被包含在<pre class="crayon-plain-tag">build.gradle</pre> 的<span style="background-color: #c0c0c0;">首部</span>。注意避免修改build.gradle本身，该文件是自动生成的，可能被Cordova覆盖。下面是扩展脚本的一个例子：</p>
<pre class="crayon-plain-tag"># 此文件的内容被放在build.gradle的开始处

# 设置工程扩展属性
ext.cdvDebugSigningPropertiesFile = 'android-debug-keys.properties'

# 这里指定的闭包，会在build.gradle结尾处执行， 因为build.gradle的最后包含如下代码：
# if (hasProperty('postBuildExtras')) {
#     postBuildExtras()
# }
ext.postBuildExtras = {
    android.buildTypes.debug.applicationIdSuffix = '.debug'
}</pre>
<div class="blog_h2"><span class="graybg">打包和部署</span></div>
<p>有两种方式来打包Cordova应用，并部署到Android设备或者模拟器上测试：</p>
<ol>
<li>使用Maven：<pre class="crayon-plain-tag">package android:deploy android:run</pre> </li>
<li>使用ADT：在apk工程上面点击右键 Run As ⇨ Android Application</li>
</ol>
<p>注意，必须让你的设备或者模拟器链接到当前机器上。</p>
<p>这两种方式都比较慢，每次部署都需要数十秒，非常浪费时间。因此可以使用PhoneGap提供的<a href="/phonegap-study-note#device-tool">设备端工具</a>，该工具可以免部署的运行Cordova应用。</p>
<div class="blog_h1"><span class="graybg">理解Cordova HelloWorld</span></div>
<div class="blog_h3"><span class="graybg">启动</span></div>
<p>应用启动后，会自动启用一个WebView，加载config.xml中指定的起始页（默认index.html），让HTML页面占据整个屏幕空间。</p>
<p>例如，对Android平台，系统会执行MainActivity，该Activity是CordovaActivity的子类：</p>
<pre class="crayon-plain-tag">public class MainActivity extends CordovaActivity
{
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        // 加载HTML
        loadUrl(launchUrl);
    }
}</pre>
<div class="blog_h3"><span class="graybg">HTML和JavaScript脚本</span></div>
<pre class="crayon-plain-tag">&lt;!DOCTYPE html&gt;
&lt;html&gt;
    &lt;head&gt;
        &lt;!-- 内容安全策略 --&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;!-- 禁止IOS的电话号码识别 --&gt;
        &lt;meta name="format-detection" content="telephone=no"&gt;
        &lt;!-- 禁止Windows Phone 8+的触碰时高亮 --&gt;
        &lt;meta name="msapplication-tap-highlight" content="no"&gt;
        &lt;!-- WARNING: for iOS 7, remove the width=device-width and height=device-height attributes --&gt;
        &lt;!-- 
            视口元标签：指定应用程序占用屏幕的多大空间，以及应用程序如何缩放（zoom） 
            minimum-scale、maximum-scale 最小/最大缩放比率 initial-scale 为初始缩放比率
            user-scalable 是否允许用户缩放
            width、height 界面的宽高
        --&gt;
        &lt;meta name="viewport" content="user-scalable=no, initial-scale=1, maximum-scale=1, minimum-scale=1, width=device-width"&gt;
        &lt;link rel="stylesheet" type="text/css" href="css/index.css"&gt;
        &lt;title&gt;Hello World&lt;/title&gt;
    &lt;/head&gt;
    &lt;body&gt;
        &lt;div class="app"&gt;
            &lt;h1&gt;Apache Cordova&lt;/h1&gt;
            &lt;div id="deviceready" class="blink"&gt;
                &lt;p class="event listening"&gt;Connecting to Device&lt;/p&gt;
                &lt;p class="event received"&gt;Emulater is Ready&lt;/p&gt;
            &lt;/div&gt;
        &lt;/div&gt;
        &lt;!-- 
            该JS是 Apache Cordova 提供的，用于访问设备硬件（例如摄像头、GPS）的JavaScript API
            该JS只是一个占位符，没有对应的文件。该脚本在运行时由PhoneGap Developer App注入；或者在构建时由Cordova CLI注入
        --&gt;
        &lt;script type="text/javascript" src="cordova.js"&gt;&lt;/script&gt;
        &lt;script type="text/javascript" src="js/index.js"&gt;&lt;/script&gt;
    &lt;/body&gt;
&lt;/html&gt;</pre><br />
<pre class="crayon-plain-tag">var app = {
    // 程序入口点
    initialize: function() {
        this.bindEvents();
    },
    // 绑定事件监听器，例如'load', 'deviceready', 'offline',  'online'
    bindEvents: function() {
        document.addEventListener('deviceready', this.onDeviceReady, false);
    },
    //事件deviceready是Cordova专有的，该事件发生后，Cordova提供的设备API加载完毕并可用
    onDeviceReady: function() {
        app.receivedEvent('deviceready');
    },
    // 修改DOM内容
    receivedEvent: function(id) {
        var parentElement = document.getElementById(id);
        var listeningElement = parentElement.querySelector('.listening');
        var receivedElement = parentElement.querySelector('.received');

        listeningElement.setAttribute('style', 'display:none;');
        receivedElement.setAttribute('style', 'display:block;');

        console.log('Received Event: ' + id);
    }
};

app.initialize();</pre>
<div class="blog_h1">开发与调试指南</div>
<div class="blog_h2"><span class="graybg">开发最佳实践</span></div>
<div class="blog_h3"><span class="graybg">单页面应用（SPA）</span></div>
<p>Cordova应该是基于单页面应用设计的。所谓单页面应用，是指不存在URL导航的Web应用。单页面引用仅在最初加载一次页面，后续的更新通过JavaScript、Ajax完成。</p>
<p>单页面应用有利于增强移动应用的性能，因为避免了<span style="background-color: #c0c0c0;">重复加载</span>各种资产文件（脚本、图片）。单页面应用对Cordova有额外的好处，因为Cordova应用必须<span style="background-color: #c0c0c0;">等待deviceready事件</span>，URL导航导致重复的事件等待。</p>
<div class="blog_h3"><span class="graybg">性能方面的考虑</span></div>
<p>Cordova开发者往往错误的假设，移动设备能够达到和桌面应用一样性能。实际上，尽管移动设备已经长足发展了近十年，在电池、性能方面，仍然无法和桌面应用比较。注意以下几个方面：</p>
<ol>
<li>click vs touch：click事件被大量误用。大部分移动设备添加300ms的延迟，用以区分touch和touch hold。使用touchstart、touchend可能有很大的性能改善——避免频繁的UI更新和其它行为</li>
<li>CSS 变换 vs DOM操控：硬件加速的CSS变换比起基于JavaScript的动画，性能有极大的优势</li>
<li>网络延迟：尽量使用Cordova支持的本地存储机制，避免反复通过网络获取数据、资源</li>
</ol>
<div class="blog_h3"><span class="graybg">识别并处理离线状态</span></div>
<p>使用Cordova提供的offline、online事件的支持，你可以识别出离线状态，并正确的响应用户。注意，这两种事件并不是非常可靠，有时候你应当使用Ajax来判断设备是否真正的断开网络了。</p>
<p>注意：苹果的商店拒绝不能正确处理离线状态的APP上架。</p>
<div class="blog_h3"><span class="graybg">数据存储方案</span></div>
<p>有几种存储方案可以供Apache Cordova应用使用：</p>
<ol>
<li>LocalStorage：同步的基于键值对的存储</li>
<li><a href="http://dev.w3.org/html5/webdatabase/">WebSQL</a>：基于SQL语句的API，支持Android、iOS</li>
<li>IndexedDB：支持Windows 8、Windows Phone 8</li>
<li>cordova-plugin-file插件：缓存数据到本地文件系统</li>
</ol>
<div class="blog_h2"><span class="graybg">测试Cordova应用</span></div>
<p>测试Cordova应用时，你可以使用真实设备、模拟器，甚至是桌面浏览器。但是，在多种真实设备上测试是很有必要的： </p>
<ol>
<li>模拟器（Simulator）不能模拟一些真实存在的情况：例如低电池时，应用会出现某个问题，这个问题在模拟器上难以体现</li>
<li>模拟器（Emulator）和真实设备的行为存在不一致：某些模拟器会导致页面显示混乱，但是在真实设备上没有这种问题。4.1.2的AVD，运行5.4版本Cordova的HelloWorld应用，就存在页面显示混乱的情况。你可能需要禁用Host GPU加速</li>
<li>Simulator往往比真实设备快很多，而Emulator则慢很多</li>
<li>在不同OS的设备上测试是有必要的，应用的行为可能不符合预期</li>
</ol>
<div class="blog_h3"><span class="graybg">PhoneGap Developer App</span></div>
<p>使用PGDA可以避免编译、打包、部署到设备的繁冗步骤，在开发阶段能够节约很多时间。</p>
<div class="blog_h2"><span class="graybg">调试Cordova应用</span></div>
<div class="blog_h3"><span class="graybg">iOS Debugging</span></div>
<p>使用XCode可以调试Cordova应用Native的那一部分。</p>
<div class="blog_h3"><span class="graybg">Safari Remote Debugging with Web Inspector</span></div>
<p>使用Safari的web inspector，你可以Debug Webview和Cordova应用的JavaScript代码，仅支持OS X和iOS6+。通过OS X中的Safari连接到设备（或模拟器），可以使用Safari的dev tools调试Cordova应用。</p>
<div class="blog_h3"><span class="graybg">Chrome Remote Debugging</span></div>
<p>与Safari类似，Chrome Dev Tools也可以用来<a href="https://developers.google.com/chrome/mobile/docs/debugging">调试Cordova应用</a>。需要<span style="background-color: #c0c0c0;">Android 4.4、API 19+、Chrome 30+</span>才可以进行调试。使用<a href="https://github.com/google/ios-webkit-debug-proxy/">WebKit proxy</a>，Chrome Dev Tools也可以调试iOS应用。</p>
<div class="blog_h3"><span class="graybg">Ripple</span></div>
<p><a href="http://ripple.incubator.apache.org/">Ripple</a>是一个基于Web的移动设备环境模拟器，用于快速开发基于Apache Cordova、Blackberry WebWorks等框架的移动Web应用。</p>
<div class="blog_h3"><span class="graybg">GapDebug</span></div>
<p>综合性的移动应用调试工具，支持在现代移动设备（iOS、Android）上调试混合应用，对PhoneGap、Cordova提供非常好的支持。</p>
<p>该工具实质上是对Chrome Dev Tools的包装和增强。</p>
<p>具体参见：<a href="/gapdebug">使用GapDebug调试Phonegap应用</a></p>
<div class="blog_h1"><span class="graybg">Cordova生命周期事件</span></div>
<p>除非特别说明所有事件均在<pre class="crayon-plain-tag">document</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>deviceready</td>
<td>对任何Cordova应用程序都非常关键，该事件的发生意味着Cordova设备API已经加载完毕、并可以使用。用法示例：<br />
<pre class="crayon-plain-tag">document.addEventListener("deviceready", onDeviceReady, false);
function onDeviceReady() {
    // 现在可以安全的使用Cordova设备API了
}</pre>
</td>
</tr>
<tr>
<td>pause</td>
<td>当应用程序进入后台时，发布该事件<br />注意：在iOS中，此事件的监听器对Cordova API的调用、对alert、console.log的调用都被阻塞，直到resume发生时才执行。</td>
</tr>
<tr>
<td>resume</td>
<td>当应用程序从后台返回前台执行时，发布此事件<br />注意：在iOS中，resume的监听器中，对交互式函数（例如alert）的调用，必须异步执行，否则应用程序会挂起。下面是正确的代码示例：<br />
<pre class="crayon-plain-tag">document.addEventListener("resume", onResume, false);
function onResume() {
   setTimeout(function() {
       alert(1);
   }, 0);
}</pre>
</td>
</tr>
<tr>
<td>backbutton</td>
<td>当用户按下后退按钮时，发布此事件，可以覆盖后台按钮的默认行为<br />不支持iOS系统</td>
</tr>
<tr>
<td>menubutton</td>
<td>当用户按下菜单按钮时，发布此事件，可以覆盖菜单按钮的默认行为<br />不支持iOS系统</td>
</tr>
<tr>
<td>searchbutton</td>
<td>当用户按下搜索按钮时，发布此事件，可以覆盖搜索按钮的默认行为<br />仅支持Android系统 </td>
</tr>
<tr>
<td>volumedownbutton</td>
<td>当用户按下减小音量按钮时，发布此事件，仅支持Android、BlackBerry 10</td>
</tr>
<tr>
<td>volumeupbutton</td>
<td>当用户按下增大音量按钮时，发布此事件，仅支持Android、BlackBerry 10</td>
</tr>
<tr>
<td style="text-align: center;" colspan="2"><strong>插件cordova-plugin-battery-status引入的事件</strong></td>
</tr>
<tr>
<td>batterycritical</td>
<td rowspan="3">
<p>这些事件都在window对象上发布。分别在关键电量、低电量、电池状态变化时（增减超过1%、或者插上电源、拔下电源）时发布。可以访问事件对象的如下属性：</p>
<ol>
<li>level，电量百分比数值，0-100</li>
<li>isPlugged，布尔型，判断是否插入电源</li>
</ol>
</td>
</tr>
<tr>
<td>batterylow</td>
</tr>
<tr>
<td>batterystatus</td>
</tr>
<tr>
<td style="text-align: center;" colspan="2"><strong>插件cordova-plugin-network-information引入的事件</strong></td>
</tr>
<tr>
<td>online</td>
<td rowspan="2"> 当设备上线（连接到互联网）时，以及设备下线时，发布对应的事件</td>
</tr>
<tr>
<td>offline</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">Cordova插件API</span></div>
<p>Cordova自带了一个最小化的API集合，工程可以通过插件添加额外的API。本节介绍Cordova核心插件的API。</p>
<p>本节中，使用样式区分不同的API类型：</p>
<ol>
<li>斜体：表示静态（static）成员</li>
<li>下划线标注：类型定义（typedef）</li>
<li>括号后缀：表示方法/函数</li>
</ol>
<div class="blog_h3"><span class="graybg">cordova-plugin-battery-status</span></div>
<p>提供三个电池相关的事件，如上节所述</p>
<div class="blog_h3"><span class="graybg">cordova-plugin-camera </span></div>
<p>该插件定义了一个全局的<pre class="crayon-plain-tag">navigator.camera</pre> 对象，用于拍摄照片、从系统的图片库选择照片。该插件常用API如下表：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 15%; text-align: center;">所有者</td>
<td style="width: 20%; text-align: center;">方法/属性</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td rowspan="5">navigator.<br />camera</td>
<td><em>getPicture()</em></td>
<td>
<pre class="crayon-plain-tag">camera.getPicture(
    onSuccess successCallback, 
    onError errorCallback, 
    CameraOptions options
)</pre></p>
<p>使用摄像头拍照，或者从设备的图片库中获取照片。如果<br /><pre class="crayon-plain-tag">options.sourceType == Camera.PictureSourceType.</pre> ：</p>
<ol>
<li><span style="color: #333333; font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: small;"><span style="line-height: 22px;">CAMERA（默认）：打开摄像头并拍照，一旦拍照完毕，则关闭默认照相程序，并返回到当前应用</span></span></li>
<li>PHOTOLIBRARY、SAVEDPHOTOALBUM：显示一个对话框，让用户选择一个既有的照片。此时当前函数返回一个CameraPopoverHandle，用于重新定位图片选择对话框（例如设备的方向改变时）</li>
</ol>
<p>图片通过Base64编码或者URI的方式（默认）传递给<pre class="crayon-plain-tag">successCallback</pre> 。如果<br /><pre class="crayon-plain-tag">options.destinationType == Camera.DestinationType.</pre> ：</p>
<ol>
<li>FILE_URI（默认）：返回文件URI，对于Android返回<pre class="crayon-plain-tag">content://</pre> 形式的URI。这是最好的方式，因为现代设备的拍的照片、图片库中的照片的质量可能很高，而此插件没有对其进行缩小、压缩处理，因此使用DATA_URL可能导致内存方面的问题</li>
<li>NATIVE_URI：返回Native的URI</li>
<li>DATA_URL：返回Base64编码的字符串</li>
</ol>
<p>得到图片URI或者Base64编码后，你可以：</p>
<ol>
<li>使用<pre class="crayon-plain-tag">&lt;img&gt;</pre> 渲染到当前页面</li>
<li>存储到本地</li>
<li>发送到远程服务器</li>
</ol>
</td>
</tr>
<tr>
<td><em>cleanup()</em></td>
<td>清理图片临时文件，仅当options.sourceType为CAMERA、options.destinationType为FILE_URI时需要调用<br />仅支持iOS</td>
</tr>
<tr>
<td><span style="text-decoration: underline;"><em>onError()</em></span></td>
<td>获取照片的错误回调，入参：
<ol>
<li><span style="color: #333333; font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: 13px; line-height: 22px;">sring </span><span style="color: #333333; font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: 13px; line-height: 22px;">message：来自设备Native代码的错误信息</span></li>
</ol>
</td>
</tr>
<tr>
<td><em><span style="text-decoration: underline;">onSuccess()</span></em></td>
<td>获取照片的成功回调，入参：
<ol>
<li><span style="color: #333333; font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: 13px; line-height: 22px;">sring imageData：Base64图片数据或者URI</span></li>
</ol>
</td>
</tr>
<tr>
<td><em><span style="text-decoration: underline;">CameraOptions</span></em></td>
<td>
<p>用于指定摄像头参数的对象，包含以下属性：</p>
<ol>
<li>quality：图片质量，0-100，100表示全分辨率+无损压缩。注意摄像头的分辨率无法得知</li>
<li>destinationType、sourceType：参考getPicture()方法的介绍</li>
<li>allowEdit：在选择前，允许简单的编辑照片</li>
<li>encodingType：图像编码方式，默认Camera.EncodingType.JPEG</li>
<li>targetWidth、targetHeight：目标图片的尺寸，纵横比保持不变</li>
<li>mediaType：当sourceType为PHOTOLIBRARY 或SAVEDPHOTOALBUM时，指定媒体类型，默认Camera.MediaType.PICTURE</li>
<li>correctOrientation：修正照片的方向</li>
<li>saveToPhotoAlbum：在拍照后，保存照片到设备相册</li>
<li>popoverOptions：仅用于iOS的iPad，用来指定popover的位置</li>
<li>cameraDirection：使用前摄像头还是后摄像头，默认Camera.Direction.BACK</li>
</ol>
</td>
</tr>
<tr>
<td rowspan="5">Camera   </td>
<td>enum<br />DestinationType</td>
<td>图片目标类型，可选值：<br />DATA_URL<br />FILE_URI<br />NATIVE_URI</td>
</tr>
<tr>
<td>enum<br />EncodingType</td>
<td>图像编码方式，可选值：<br />JPEG<br />PNG </td>
</tr>
<tr>
<td>enum<br />MediaType</td>
<td>媒体类型，可选值： <br />PICTURE：仅选择图片<br />VIDEO：仅选择视频，对于视频，DestinationType只能时URI<br />ALLMEDIA：同时选择图片和视频</td>
</tr>
<tr>
<td>enum<br />PictureSourceType</td>
<td>图片来源类型，可选值：<br />PHOTOLIBRARY：图片库，对于Android和SAVEDPHOTOALBUM一样<br />CAMERA：即时拍照<br />SAVEDPHOTOALBUM：图片库，对于Android和SAVEDPHOTOALBUM一样</td>
</tr>
<tr>
<td>enum<br />Direction</td>
<td>使用哪个摄像头，可选值：BACK、FRONT</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">cordova-plugin-console</span></div>
<p>该插件确保<pre class="crayon-plain-tag">console.log()</pre> 能够正常工作，为非Android平台提供额外的函数支持。该插件定义了一个全局的<pre class="crayon-plain-tag">console</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>log</td>
<td style="vertical-align: top;" rowspan="12">这些方法支持C语言printf风格的字符串格式化：
<ol>
<li><span style="color: #333333; font-family: Georgia, 'Times New Roman', 'Bitstream Charter', Times, serif; font-size: small;"><span style="line-height: 22px;">%j 格式化为JSON</span></span></li>
<li>%o 格式化为JSON</li>
<li>%c 格式化为''</li>
<li>%% 转义%</li>
<li>%* 其他均使用目标参数的<pre class="crayon-plain-tag">toString()</pre> 方法进行打印</li>
</ol>
</td>
</tr>
<tr>
<td>error</td>
</tr>
<tr>
<td>exception</td>
</tr>
<tr>
<td>warn</td>
</tr>
<tr>
<td>info</td>
</tr>
<tr>
<td>debug</td>
</tr>
<tr>
<td>assert</td>
</tr>
<tr>
<td>dir</td>
</tr>
<tr>
<td>dirxml</td>
</tr>
<tr>
<td>time</td>
</tr>
<tr>
<td>timeEnd</td>
</tr>
<tr>
<td>table</td>
</tr>
</tbody>
</table>
<div class="blog_h1">Cordova配置文件：config.xml</div>
<p>位于Cordova工程根目录的配置文件config.xml，可以在全局上控制Cordova应用程序的行为：</p>
<ol>
<li>指定核心Cordova API特性</li>
<li>启用Cordova插件</li>
<li>进行平台相关的设置</li>
</ol>
<p>config.xml指定的配置会影响所有应用程序和CordovaWebView实例。</p>
<p>当使用Cordova的命令行接口（CLI）构建工程时，该文件会被修改并拷贝到各平台的对应目录，例如<pre class="crayon-plain-tag">platforms/ios/AppName/config.xml</pre> </p>
<div class="blog_h2"><span class="graybg">全局与跨平台配置选项</span></div>
<pre class="crayon-plain-tag">&lt;?xml version='1.0' encoding='utf-8'?&gt;
&lt;!-- widget元素： id 当前应用程序的唯一标识，通常是域名倒写，在Android中对应了包名 version 主版本.次版本.补丁版本 --&gt;
&lt;widget id="cc.gmem.study.cordova" version="0.0.1" xmlns="http://www.w3.org/ns/widgets" xmlns:cdv="http://cordova.apache.org/ns/1.0"&gt;
    &lt;!-- 应用程序的正式名称，该名称会出现在设备的主屏幕，以及应用商店 --&gt;
    &lt;name&gt;CordovaStudy&lt;/name&gt;
    &lt;!-- 应用程序描述和作者信息，会显示在应用商店 --&gt;
    &lt;description&gt;
        A sample Apache Cordova application that responds to the deviceready event.
    &lt;/description&gt;
    &lt;author email="dev@cordova.apache.org" href="http://cordova.io"&gt;
        Apache Cordova Team
    &lt;/author&gt;
    &lt;!-- 指定应用的起始网页 --&gt;
    &lt;content src="index.html" /&gt;
    &lt;!-- 指定应用使用的Cordova插件 --&gt;
    &lt;plugin name="cordova-plugin-whitelist" spec="1" /&gt;
    &lt;!-- 指定应用可以和哪些外部域名进行交互，默认值允许与任何外部服务交互 --&gt;
    &lt;access origin="*" /&gt;
    &lt;!-- Intent白名单 --&gt;
    &lt;allow-intent href="http://*/*" /&gt;
    &lt;allow-intent href="https://*/*" /&gt;
    &lt;allow-intent href="tel:*" /&gt;
    &lt;allow-intent href="sms:*" /&gt;
    &lt;allow-intent href="mailto:*" /&gt;
    &lt;allow-intent href="geo:*" /&gt;
    &lt;!-- platform元素只会拷贝到对应平台下的config.xml中 --&gt;
    &lt;platform name="android"&gt;
        &lt;allow-intent href="market:*" /&gt;
        &lt;!-- 平台特定的方向设置 --&gt;
        &lt;preference name="Orientation" value="sensorLandscape" /&gt;
    &lt;/platform&gt;
    &lt;platform name="ios"&gt;
        &lt;allow-intent href="itms:*" /&gt;
        &lt;allow-intent href="itms-apps:*" /&gt;
        &lt;!-- IOS：同时设置portrait、landscape模式 --&gt;
        &lt;preference name="Orientation" value="all" /&gt;
    &lt;/platform&gt;
    &lt;!-- 以键值对的形式定义若干选项，键大小写不敏感 --&gt;
    &lt;preference&gt;
        &lt;!-- 是否隐藏设备顶部的状态栏 --&gt;
        &lt;preference name="Fullscreen" value="false" /&gt;
        &lt;!-- 支持Android、IOS。是否禁止拖拽滚动超过顶部或者底部，如果不需要“下拉以刷新”之类的功能，可以禁止 --&gt;
        &lt;preference name="DisallowOverscroll" value="false" /&gt;
        &lt;!-- 支持Android、Blackberry。应用的背景色 --&gt;
        &lt;preference name="BackgroundColor" value="0xff0000ff" /&gt;
        &lt;!-- 支持Blackberry。隐藏键盘上方额外的工具栏 --&gt;
        &lt;preference name="HideKeyboardFormAccessoryBar" value="false" /&gt;
        &lt;!-- 锁定方向，禁止响应设备的转动。可选值landscape、portrait、default --&gt;
        &lt;preference name="Orientation" value="default" /&gt;
    &lt;/preference&gt;
    &lt;!-- 
        如果使用CLI，添加的插件不会反应到feature元素，但是如果使用针对特定平台的config.xml，
        可以设置feature元素，以启用设备级别的API或者外部插件
    --&gt;
    &lt;!-- 在Android平台上启用Device API --&gt;
    &lt;feature name="Device"&gt;
        &lt;param name="android-package" value="org.apache.cordova.device.Device" /&gt;
    &lt;/feature&gt;
    &lt;!-- 在IOS平台上启用Device API --&gt;
    &lt;feature name="Device"&gt;
        &lt;param name="ios-package" value="CDVDevice" /&gt;
    &lt;/feature&gt;
    &lt;!-- 添加钩子，以扩展Cordova的功能 --&gt;
    &lt;hook type="after_plugin_install" src="scripts/afterPluginInstall.js" /&gt;
    
    &lt;!-- 指定应用程序图标，platform/width/height/density都是可选的，density为Android专有 --&gt;
    &lt;icon src="res/ios/icon.png" platform="ios" width="57" height="57" density="mdpi" /&gt;
    &lt;!-- 可以采用平台专有的方式指定图标 --&gt;
    &lt;platform name="android"&gt;
        &lt;icon src="res/android/ldpi.png" density="ldpi" /&gt;
        &lt;icon src="res/android/mdpi.png" density="mdpi" /&gt;
        &lt;icon src="res/android/hdpi.png" density="hdpi" /&gt;
        &lt;icon src="res/android/xhdpi.png" density="xhdpi" /&gt;
     &lt;/platform&gt;
     &lt;platform name="ios"&gt;
        &lt;!-- iOS 8.0+ --&gt;
        &lt;!-- iPhone 6 Plus  --&gt;
        &lt;icon src="res/ios/icon-60@3x.png" width="180" height="180" /&gt;
        &lt;!-- iOS 7.0+ --&gt;
        &lt;!-- iPhone / iPod Touch  --&gt;
        &lt;icon src="res/ios/icon-60.png" width="60" height="60" /&gt;
        &lt;icon src="res/ios/icon-60@2x.png" width="120" height="120" /&gt;
        &lt;!-- iPad --&gt;
        &lt;icon src="res/ios/icon-76.png" width="76" height="76" /&gt;
        &lt;icon src="res/ios/icon-76@2x.png" width="152" height="152" /&gt;
        &lt;!-- iOS 6.1 --&gt;
        &lt;!-- Spotlight Icon --&gt;
        &lt;icon src="res/ios/icon-40.png" width="40" height="40" /&gt;
        &lt;icon src="res/ios/icon-40@2x.png" width="80" height="80" /&gt;
        &lt;!-- iPhone / iPod Touch --&gt;
        &lt;icon src="res/ios/icon.png" width="57" height="57" /&gt;
        &lt;icon src="res/ios/icon@2x.png" width="114" height="114" /&gt;
        &lt;!-- iPad --&gt;
        &lt;icon src="res/ios/icon-72.png" width="72" height="72" /&gt;
        &lt;icon src="res/ios/icon-72@2x.png" width="144" height="144" /&gt;
        &lt;!-- iPhone Spotlight and Settings Icon --&gt;
        &lt;icon src="res/ios/icon-small.png" width="29" height="29" /&gt;
        &lt;icon src="res/ios/icon-small@2x.png" width="58" height="58" /&gt;
        &lt;!-- iPad Spotlight and Settings Icon --&gt;
        &lt;icon src="res/ios/icon-50.png" width="50" height="50" /&gt;
        &lt;icon src="res/ios/icon-50@2x.png" width="100" height="100" /&gt;
     &lt;/platform&gt;
     
     &lt;!-- 指定启动画面，也可用使用Splashscreen插件，动态编程控制 --&gt;
     &lt;platform name="android"&gt;
        &lt;!-- you can use any density that exists in the Android project --&gt;
        &lt;splash src="res/screen/android/splash-land-hdpi.png" density="land-hdpi"/&gt;
        &lt;splash src="res/screen/android/splash-land-ldpi.png" density="land-ldpi"/&gt;
        &lt;splash src="res/screen/android/splash-land-mdpi.png" density="land-mdpi"/&gt;
        &lt;splash src="res/screen/android/splash-land-xhdpi.png" density="land-xhdpi"/&gt;

        &lt;splash src="res/screen/android/splash-port-hdpi.png" density="port-hdpi"/&gt;
        &lt;splash src="res/screen/android/splash-port-ldpi.png" density="port-ldpi"/&gt;
        &lt;splash src="res/screen/android/splash-port-mdpi.png" density="port-mdpi"/&gt;
        &lt;splash src="res/screen/android/splash-port-xhdpi.png" density="port-xhdpi"/&gt;
    &lt;/platform&gt;

    &lt;platform name="ios"&gt;
        &lt;!-- images are determined by width and height. The following are supported --&gt;
        &lt;splash src="res/screen/ios/Default~iphone.png" width="320" height="480"/&gt;
        &lt;splash src="res/screen/ios/Default@2x~iphone.png" width="640" height="960"/&gt;
        &lt;splash src="res/screen/ios/Default-Portrait~ipad.png" width="768" height="1024"/&gt;
        &lt;splash src="res/screen/ios/Default-Portrait@2x~ipad.png" width="1536" height="2048"/&gt;
        &lt;splash src="res/screen/ios/Default-Landscape~ipad.png" width="1024" height="768"/&gt;
        &lt;splash src="res/screen/ios/Default-Landscape@2x~ipad.png" width="2048" height="1536"/&gt;
        &lt;splash src="res/screen/ios/Default-568h@2x~iphone.png" width="640" height="1136"/&gt;
        &lt;splash src="res/screen/ios/Default-667h.png" width="750" height="1334"/&gt;
        &lt;splash src="res/screen/ios/Default-736h.png" width="1242" height="2208"/&gt;
        &lt;splash src="res/screen/ios/Default-Landscape-736h.png" width="2208" height="1242"/&gt;
    &lt;/platform&gt;
&lt;/widget&gt;</pre>
<div class="blog_h2"><span class="graybg">安卓配置选项</span></div>
<pre class="crayon-plain-tag">&lt;!-- 
    应用程序是否保持后台运行，甚至在pause事件发生之后 
    设置为fasle不会在pause事件后终止应用，而是仅仅当应用位于后台时，暂停CordovaWebView中代码的执行
--&gt;
&lt;preference name="KeepRunning" value="true"/&gt;
&lt;!-- 加载页面超时的毫秒数 --&gt;
&lt;preference name="LoadUrlTimeoutValue" value="20000"/&gt;
&lt;!-- 启动屏幕，位于res/drawable-*中的资源名称，不带扩展名 --&gt;
&lt;preference name="SplashScreen" value="splash"/&gt;
&lt;!-- 启动屏幕图片显示的毫秒数 --&gt;
&lt;preference name="SplashScreenDelay" value="3000"/&gt;
&lt;!-- 控制通过InAppBrowser打开的页面，是否可以和通过默认浏览器打开的页面访问同一localStorage和WebSQL --&gt;
&lt;preference name="InAppBrowserStorageEnabled" value="true"/&gt;
&lt;!-- 默认为null，如果设置，那么在加载第一个页面时，显示指定的标题、消息以及一个spinner。标题和消息使用逗号分隔 --&gt;
&lt;preference name="LoadingDialog" value="My Title,My Message"/&gt;
&lt;!-- 默认为null，与上一个配置类似，但是用于除了第一个页面的所有其它页面 --&gt;
&lt;preference name="LoadingPageDialog" value="My Title,My Message"/&gt;
&lt;!-- 默认为null，出错时显示的页面 --&gt;
&lt;preference name="ErrorUrl" value="myErrorPage.html"/&gt;
&lt;!-- 是否在屏幕顶部显示标题 --&gt;
&lt;preference name="ShowTitle" value="false"/&gt;
&lt;!-- 设置应用的日志级别：ERROR, WARN, INFO, DEBUG, VERBOSE --&gt;
&lt;preference name="LogLevel" value="ERROR"/&gt;
&lt;!-- 设置Activity的android:launchMode属性 --&gt;
&lt;preference name="AndroidLaunchMode" value="singleTop"/&gt;
&lt;!-- 设置User Agent，默认不设置 --&gt;
&lt;preference name="OverrideUserAgent" value="Mozilla/5.0 My Browser" /&gt;</pre>
<div class="blog_h1">指定平台或插件的版本</div>
<p>从Cordova4.3开始，支持保存（save ）、恢复（restore ）平台支持版本、插件版本信息。</p>
<p>当使用<pre class="crayon-plain-tag">--save</pre> 选项调用<pre class="crayon-plain-tag">cordova platform</pre> 、<pre class="crayon-plain-tag">cordova plugin</pre> 命令时，会自动把版本信息存放到config.xml中。执行<pre class="crayon-plain-tag">cordova prepare</pre> 时，会从config.xml中读取版本信息，并同步相应文件到platforms子目录中。</p>
<div class="blog_h3"><span class="graybg">保存平台版本</span></div>
<p>执行下面的命令，即可保存特定版本的平台：</p>
<pre class="crayon-plain-tag"># 命令格式
cordova platform add &lt;platform[@&lt;version&gt;] | directory | git_url&gt; --save
# 命令举例
cordova platform add android --save
cordova platform add android@3.7.0 --save    #后续不带@version添加平台，自动使用 3.7.0版本
cordova platform add android@https://github.com/apache/cordova-android.git​ --save
cordova platform add C:/path/to/android/platform --save

# 保存所有平台的版本
cordova platform save</pre>
<p>运行此命令后，config.xml中出现类似下面的条目：</p>
<pre class="crayon-plain-tag">&lt;engine name="android" spec="~4.0.0" /&gt;</pre>
<p>执行下面的命令，可以更新特定平台的版本： </p>
<pre class="crayon-plain-tag"># 命令格式
cordova platform update &lt;platform[@&lt;version&gt;] | directory | git_url&gt; --save

# 命令举例
cordova platform update android --save
cordova platform update android@3.8.0 --save
cordova platform update /path/to/android/platform --save</pre>
<div class="blog_h3"><span class="graybg">保存插件版本</span></div>
<p>执行下面的命令，即可保存插件版本到config.xml：</p>
<pre class="crayon-plain-tag"># 命令格式
cordova plugin add &lt;plugin[@&lt;version&gt;] | directory | git_url&gt; --save

# 命令举例
cordova plugin add cordova-plugin-console --save
cordova plugin add cordova-plugin-console@0.2.13 --save     #后续不带@version添加此插件，自动使用0.2.13版本
cordova plugin add https://github.com/apache/cordova-plugin-console.git --save
cordova plugin add C:/path/to/console/plugin --save

# 保存所有插件的版本
cordova plugin save</pre>
<p>运行此命令后，config.xml中出现类似下面的条目：</p>
<pre class="crayon-plain-tag">&lt;plugin name="cordova-plugin-console" spec="~1.0.0" /&gt;</pre>
<p>执行下面的命令，可以更新特定插件的版本： </p>
<pre class="crayon-plain-tag"># 命令格式
cordova plugin update &lt;plugin[@&lt;version&gt;] | directory | git_url&gt; --save

# 命令举例
cordova plugin update cordova-plugin-console --save
cordova plugin update cordova-plugin-console@0.2.13 --save
cordova plugin update /path/to/console/plugin --save</pre>
<div class="blog_h1"><span class="graybg"> 隐私与安全</span></div>
<div class="blog_h2"><span class="graybg">隐私指南</span></div>
<p>移动应用的隐私问题是每个移动App都需要关注的内容，用户需要它们的私人信息被合理使用，并不泄漏。下面是一些被广泛接受的建议：</p>
<ol>
<li>隐私策略（Privacy Policy）：使用平白的语言声明你的App的隐私策略</li>
<li>敏感信息的收集：在获取敏感信息，例如通讯录、地理位置、摄像机信息前，获得用户的许可</li>
<li>与第三方共享敏感信息：如果你的App获取的信息需要与第三方网站/应用共享，应当获得用户许可</li>
<li>仅在必要的时候才收集隐私信息</li>
</ol>
<div class="blog_h2"><span class="graybg">安全指南</span></div>
<p>本节包含一些开发Cordova应用时，处理安全问题的最佳实践。</p>
<ol>
<li>不要使用自签名的网站证书：如果你的服务器使用SSL，务必使用知名CA签发的数字证书</li>
<li>避免使用IFrame引用外部网页（不受你控制的第三方网站）：位于白名单中的网页，如果运行在IFrame中，它是可以访问Native Cordova Bridge的。引用外部链接，最好使用<span style="background-color: #c0c0c0;">InAppBrowser</span>，外部链接不会有机会执行Cordova API</li>
<li>关于域名白名单：<span style="background-color: #c0c0c0;">Android API 10以下</span>、<span style="background-color: #c0c0c0;">WP8中的IFrame和XMLHttpRequest</span>，都不受域名白名单控制，这意味着任何网页都可以调用Cordova接口</li>
<li>不要缓存敏感信息，例如用户名、密码</li>
<li>避免使用<pre class="crayon-plain-tag">eval()</pre> ，除非你确切知道脚本的内容</li>
</ol>
<div class="blog_h3"><span class="graybg">域名白名单配置</span></div>
<p>域名白名单（Domain whitelisting）机制用来控制Cordova应用对外部域名的访问。默认的，新创建的App<span style="background-color: #c0c0c0;">被允许访问所有外部域名</span>，在产品发布时，你必须确定需要访问哪些域名，并进行白名单配置。</p>
<p>对于Android和iOS，上述安全策略通过插件cordova-plugin-whitelist实现，具有更好的安全性和可配置性，推荐使用：</p>
<pre class="crayon-plain-tag">&lt;!-- 
    导航白名单（Navigation Whitelist）
    控制WebView可以被导航到那些URL，仅应用到顶级导航 
--&gt;
&lt;!-- 允许连接到google.com--&gt;
&lt;allow-navigation href="http://google.com/*" /&gt;
&lt;!-- 允许使用HTTP/HTTPS或者其它任何协议连接到google.com的所有子域 --&gt;
&lt;allow-navigation href="*://*.google.com/*" /&gt;
&lt;!-- 允许导航到任意网站--&gt;
&lt;allow-navigation href="*" /&gt;
&lt;!-- 上面一条等价于这三条：--&gt;
&lt;allow-navigation href="http://*/*" /&gt;
&lt;allow-navigation href="https://*/*" /&gt;
&lt;allow-navigation href="data:*" /&gt;


&lt;!-- 
    Intent白名单（Intent Whitelist）
    控制App可以要求系统打开哪些URL，默认任何URL都不允许 
--&gt;
&lt;!-- 允许在一个浏览器窗口中打开网页 --&gt;
&lt;allow-intent href="http://*/*" /&gt;
&lt;allow-intent href="https://*/*" /&gt;
&lt;allow-intent href="http://google.com/*" /&gt;
&lt;allow-intent href="*://*.example.com/*" /&gt;
&lt;!-- 允许在短信应用中打开SMS链接 --&gt;
&lt;allow-intent href="sms:*" /&gt;
&lt;!-- 允许拨打电话 --&gt;
&lt;allow-intent href="tel:*" /&gt;
&lt;!-- 允许打开地图 --&gt;
&lt;allow-intent href="geo:*" /&gt;
&lt;!-- 允许使用对应的App打开所有其它类型的URL --&gt;
&lt;allow-intent href="*" /&gt;


&lt;!-- 
    网络请求白名单（Network Request Whitelist）
    控制哪些网络请求被允许：Ajax、images等
    注意，最好使用Content Security Policy（CSP），CSP更安全。这节的配置主要用于不支持CSP的老旧Webview
    默认Android允许访问https://ssl.gstatic.com/accessibility/javascript/android/，这确保TalkBack正常工作
--&gt;
&lt;!-- 允许对google.com执行Ajax、图片请求 --&gt;
&lt;access origin="http://*.google.com" /&gt;
&lt;!-- 允许针对content URL的请求 --&gt;
&lt;access origin="content:///*" /&gt;
&lt;!-- 允许任何请求 --&gt;
&lt;access origin="*" /&gt;</pre>
<p>对于其它平台，Cordova应用依赖于config.xml中的<pre class="crayon-plain-tag">&lt;access&gt;</pre> 元素进行白名单设置：</p>
<pre class="crayon-plain-tag">&lt;!-- 允许访问google.com --&gt;
&lt;access origin="http://google.com" /&gt;
&lt;!-- 允许访问HTTPS版本的google.com --&gt;
&lt;access origin="https://google.com" /&gt;
&lt;!-- 允许访问maps.google.com --&gt;
&lt;access origin="http://maps.google.com" /&gt;
&lt;!-- 允许访问google.com的所有子域名 --&gt;
&lt;access origin="http://*.google.com" /&gt;
&lt;!-- 允许访问所有网站 --&gt;
&lt;access origin="*" /&gt;</pre>
<div class="blog_h3"><span class="graybg">内容安全策略（Content Security Policy）</span></div>
<p>参见：<a href="/html-faq#csp">HTML知识集锦</a></p>
<p>CSP用于控制图片、Ajax、<pre class="crayon-plain-tag">&lt;video&gt;</pre> 、WebSocket等所有请求。上面的whitelist插件的network request whitelist，<span style="background-color: #c0c0c0;">只能控制</span>图片、Ajax，因此有必要在HTML的<pre class="crayon-plain-tag">&lt;meta&gt;</pre> 配置CSP：</p>
<pre class="crayon-plain-tag">&lt;!-- 较为安全的默认值 --&gt;
&lt;meta http-equiv="Content-Security-Policy"
    content="default-src 'self' data: gap: https://ssl.gstatic.com; style-src 'self' 'unsafe-inline'; media-src *"&gt;

&lt;!-- 允许同源和google.com的所有内容--&gt;
&lt;meta http-equiv="Content-Security-Policy" content="default-src 'self' google.com"&gt;

&lt;!-- 
    允许所有内容，除了：
    对于CSS，仅允许同源的CSS文件，以及内联的样式
    对于脚本，仅允许同源的JS文件，内联的JavaScript脚本，以及eval()
--&gt;
&lt;meta http-equiv="Content-Security-Policy"
    content="default-src *; style-src 'self' 'unsafe-inline'; script-src 'self' 'unsafe-inline' 'unsafe-eval'"&gt;

&lt;!-- 仅允许同源的、通过HTTPS的Ajax请求 --&gt;
&lt;meta http-equiv="Content-Security-Policy" content="default-src 'self' https:"&gt;

&lt;!-- 允许来自google.com的IFrame --&gt;
&lt;meta http-equiv="Content-Security-Policy" content="default-src 'self'; frame-src 'self' https://google.com"&gt;</pre>
<div class="blog_h1"><span class="graybg">Cordova Hooks</span></div>
<p>钩子是Cordova Application、Plugin添加的特殊脚本，用于<span style="background-color: #c0c0c0;">定制化Cordova的命令</span>。可以在hooks目录添加钩子，也可以在config.xml、plugin.xml中声明钩子。钩子按照下面的<span style="background-color: #c0c0c0;">顺序串行执行</span>：</p>
<ol>
<li>来自/hooks目录的Application钩子</li>
<li>来自config.xml的Application钩子</li>
<li>来自plugins/.../plugin.xml的插件钩子</li>
</ol>
<p>目前支持的钩子类型有：</p>
<ol>
<li>after_build</li>
<li>after_compile</li>
<li>after_clean</li>
<li>after_docs</li>
<li>after_emulate</li>
<li>after_platform_add</li>
<li>after_platform_rm</li>
<li>after_platform_ls</li>
<li>after_plugin_add</li>
<li>after_plugin_ls</li>
<li>after_plugin_rm</li>
<li>after_plugin_search</li>
<li>after_plugin_install  仅执行被安装插件声明在plugin.xml中的钩子</li>
<li>after_prepare</li>
<li>after_run</li>
<li>after_serve</li>
<li>before_build</li>
<li>before_clean</li>
<li>before_compile</li>
<li>before_docs</li>
<li>before_emulate</li>
<li>before_platform_add</li>
<li>before_platform_rm</li>
<li>before_platform_ls</li>
<li>before_plugin_add</li>
<li>before_plugin_ls</li>
<li>before_plugin_rm</li>
<li>before_plugin_search</li>
<li>before_plugin_install  仅执行被安装插件声明在plugin.xml中的钩子</li>
<li>before_plugin_uninstall  仅执行被卸载插件声明在plugin.xml中的钩子</li>
<li>before_prepare</li>
<li>before_run</li>
<li>before_serve</li>
<li>pre_package 仅支持Windows、Windows Phone平台</li>
</ol>
<div class="blog_h3"><span class="graybg">在何处存放钩子</span></div>
<p>如果把钩子存放在hooks目录，必须遵从严格的命名规定：<pre class="crayon-plain-tag">hooks/钩子类型/脚本文件.js</pre> 。例如，如果你需要在prepare命令执行完毕后，附加一些行为，可以编写脚本：<pre class="crayon-plain-tag">hooks/after_prepare/cordova-syncdown.js</pre> 。应避免使用hooks目录，未来的Cordova版本可能废弃这种方式。</p>
<p>在config.xml、plugin.xml中声明钩子的方式如下：</p>
<pre class="crayon-plain-tag">&lt;hook type="before_build" src="scripts/appBeforeBuild.bat" /&gt;
&lt;hook type="before_build" src="scripts/appBeforeBuild.js" /&gt;
&lt;hook type="before_plugin_install" src="scripts/appBeforePluginInstall.js" /&gt;</pre>
<div class="blog_h3"><span class="graybg">钩子编程接口</span></div>
<p>一般使用NodeJS来编写钩子，Node模块定义的方式如下：</p>
<pre class="crayon-plain-tag">module.exports = function( ctx )
{
}</pre>
<p>其中ctx包含很多上下文信息： </p>
<pre class="crayon-plain-tag">{
  "hook": "after_prepare",
  "scriptLocation": "after_prepare/script.js",
  "cmdLine": "/path/to/cordova arguments",
  "opts": {
    "projectRoot":"/path/to/project",
    "cordova": {
      "platforms": ["android"],
      "plugins": ["com.plugin.withhooks"],
      "version": "0.21.7-dev"
    },
    "plugin": {
      "id": "com.plugin.withhooks",
      "pluginInfo": {
        ...
      },
      "platform": "android",
      "dir": "/path/to/project/plugins/com.plugin.withhooks"
    }
  },
  "cordova": {} //cordova object
}</pre>
<div class="blog_h1"><span class="graybg">常见问题</span></div>
<div class="blog_h2"><span class="graybg">避免Cordova在构建时自动下载gradle</span></div>
<p>修改$CORDOVA_PROJECT_ROOT/platforms/android/cordova/lib/build.js 中如下变量的值：</p>
<pre class="crayon-plain-tag">var distributionUrl = 'distributionUrl=../../../gradle-2.2.1-all.zip';</pre>
<p>然后，把本地磁盘的 gradle-2.2.1-all.zip拷贝或者链接到$CORDOVA_PROJECT_ROOT/platforms 即可。执行<pre class="crayon-plain-tag">cordova prepare</pre>  时，会从config.xml中读取版本信息，并同步到已经添加的各平台的对应目录中。</p>
<p>让Gradle使用本地Maven仓库中的构件，避免不必要的下载动作</p>
<p>修改$CORDOVA_PROJECT_ROOT/platforms/android/build.gradle中的内容：</p>
<pre class="crayon-plain-tag">repositories {
    mavenLocal()
    //添加上面一行的内容
    mavenCentral()
}</pre>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/mobile-app-dev-with-apache-cordova">基于Apache Cordova开发移动应用程序</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/mobile-app-dev-with-apache-cordova/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Android Studio知识集锦</title>
		<link>https://blog.gmem.cc/android-studio-faq</link>
		<comments>https://blog.gmem.cc/android-studio-faq#comments</comments>
		<pubDate>Tue, 24 Mar 2015 09:30:12 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Android]]></category>
		<category><![CDATA[AS]]></category>
		<category><![CDATA[Ubuntu]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=11187</guid>
		<description><![CDATA[<p>Ubuntu相关的AS知识 环境变量设置 [crayon-69eeaec6e9210952800192/] 安装KVM以加速AVD  [crayon-69eeaec6e9214418190015/] 字体渲染优化 Android Studio（包括Jetbrains、WebStorms等IDE），在Ubuntu下字体渲染过于单薄，可以使用修改过的Open JDK： [crayon-69eeaec6e9216235794665/] 修改Jetbrains的启动脚本，设置上面安装的Open JDK为JAVA_HOME，并设置以下JVM参数： [crayon-69eeaec6e9218872957506/] 下面是.desktop文件的示例： [crayon-69eeaec6e921a476203291/] &#160; &#160;</p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/android-studio-faq">Android Studio知识集锦</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">Ubuntu相关的AS知识</span></div>
<div class="blog_h3"><span class="graybg">环境变量设置</span></div>
<pre class="crayon-plain-tag">ANDROID_HOME=/home/alex/Android/Sdk
PATH=$ANDROID_HOME/tools:$ANDROID_HOME/platform-tools:$PATH</pre>
<div class="blog_h3"><span class="graybg">安装KVM以加速AVD </span></div>
<pre class="crayon-plain-tag"># 安装必要的软件
sudo apt-get install qemu-kvm libvirt-bin ubuntu-vm-builder bridge-utils
# 添加当前用户到组
sudo adduser alex kvm
sudo adduser alex libvirtd

# 启动AVD管理器，新建模拟器，注意一定要选择Atom的镜像
android avd</pre>
<div class="blog_h3"><span class="graybg">字体渲染优化</span></div>
<p>Android Studio（包括Jetbrains、WebStorms等IDE），在Ubuntu下字体渲染过于单薄，可以使用修改过的Open JDK：</p>
<pre class="crayon-plain-tag"># 可选启用HUD
sudo add-apt-repository ppa:danjaredg/jayatana
sudo apt-get update
sudo apt-get install jayatana
# 安装带Font fix的JDK版本
sudo add-apt-repository ppa:no1wantdthisname/openjdk-fontfix
sudo apt-get update
sudo apt-get install openjdk-7-jdk</pre>
<p>修改Jetbrains的启动脚本，设置上面安装的Open JDK为JAVA_HOME，并设置以下JVM参数：</p>
<pre class="crayon-plain-tag">-Dawt.useSystemAAFontSettings=lcd
-Dswing.aatext=true
-Dsun.java2d.xrender=true
-Dsun.java2d.pmoffscreen=false</pre>
<p>下面是.desktop文件的示例：</p>
<pre class="crayon-plain-tag">[Desktop Entry]
Encoding=UTF-8
Version=1.0
Type=Application
Name=Android Studio
Icon=jetbrains-studio.png
Path=/home/alex
Exec=/usr/lib/jvm/java-1.7.0-openjdk-amd64/bin/java -Xbootclasspath/a:/home/alex/Android/ide/as/1.5/bin/../lib/boot.jar -classpath /home/alex/Android/ide/as/1.5/bin/../lib/bootstrap.jar:/home/alex/Android/ide/as/1.5/bin/../lib/extensions.jar:/home/alex/Android/ide/as/1.5/bin/../lib/util.jar:/home/alex/Android/ide/as/1.5/bin/../lib/jdom.jar:/home/alex/Android/ide/as/1.5/bin/../lib/log4j.jar:/home/alex/Android/ide/as/1.5/bin/../lib/trove4j.jar:/home/alex/Android/ide/as/1.5/bin/../lib/jna.jar:/usr/lib/jvm/java-1.7.0-openjdk-amd64/lib/tools.jar -Xms256m -Xmx1280m -XX:MaxPermSize=350m -XX:ReservedCodeCacheSize=225m -XX:+UseConcMarkSweepGC -XX:SoftRefLRUPolicyMSPerMB=50 -da -Djna.nosys=true -Djna.boot.library.path= -Djna.debug_load=true -Djna.debug_load.jna=true -Dsun.io.useCanonCaches=false -Djava.net.preferIPv4Stack=true  -Dawt.useSystemAAFontSettings=lcd -Dswing.aatext=true -Dsun.java2d.xrender=true -Dsun.java2d.pmoffscreen=false -Djb.vmOptionsFile=/home/alex/Android/ide/as/1.5/bin/studio64.vmoptions -XX:ErrorFile=/home/alex/java_error_in_STUDIO_%p.log -Djb.restart.code=88 -Didea.paths.selector=AndroidStudio1.5 -Didea.platform.prefix=AndroidStudio com.intellij.idea.Main
StartupNotify=false
StartupWMClass=jetbrains-studio
OnlyShowIn=Unity;
X-UnityGenerated=true</pre>
<p>&nbsp;</p>
<p>&nbsp;</p>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/android-studio-faq">Android Studio知识集锦</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/android-studio-faq/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Gradle学习笔记</title>
		<link>https://blog.gmem.cc/gradle-study-note</link>
		<comments>https://blog.gmem.cc/gradle-study-note#comments</comments>
		<pubDate>Fri, 16 Jan 2015 08:45:14 +0000</pubDate>
		<dc:creator><![CDATA[Alex]]></dc:creator>
				<category><![CDATA[Android]]></category>
		<category><![CDATA[Java]]></category>
		<category><![CDATA[Gradle]]></category>
		<category><![CDATA[Groovy]]></category>
		<category><![CDATA[学习笔记]]></category>

		<guid isPermaLink="false">https://blog.gmem.cc/?p=10070</guid>
		<description><![CDATA[<p>基础知识 Gradle简介 Gradle是近年来流行起来的自动化构建工具，它具有以下特性： 灵活、通用的构建功能，类似Ant 基于约定的构建框架，类似Maven 强大的多工程构建支持 支持基于Apache Ivy的依赖管理，现已使用Gradle自己的依赖处理引擎（dependency resolution engine） 完整的支持既有的Maven或者Ivy构件仓库，且不需要pom.xml或者ivy.xml就可以进行依赖管理 整合型构建工具： 支持Ant任务，Gradle可以直接引入Ant工程，并在运行时将Ant targets转换为Gradle tasks 可以自动的把Maven的pom.xml转换为Gradle脚本 基于Groovy的构建脚本。构建脚本可以很容易的维护和重用 用于描述构建的丰富的领域模型 Gradle Wrapper：运行在没有安装Gradle的机器上运行Gradle构建 Gradle的核心是一个基于Groovy的、扩展的DSL（Domain Specific <a class="read-more" href="https://blog.gmem.cc/gradle-study-note">[...]</a></p>
<p>The post <a rel="nofollow" href="https://blog.gmem.cc/gradle-study-note">Gradle学习笔记</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_h2"><span class="graybg">Gradle简介</span></div>
<p>Gradle是近年来流行起来的自动化构建工具，它具有以下特性：</p>
<ol>
<li>灵活、通用的构建功能，类似Ant</li>
<li>基于约定的构建框架，类似Maven</li>
<li>强大的多工程构建支持</li>
<li>支持基于Apache Ivy的依赖管理，现已使用Gradle自己的依赖处理引擎（dependency resolution engine）</li>
<li>完整的支持既有的Maven或者Ivy构件仓库，且不需要pom.xml或者ivy.xml就可以进行依赖管理</li>
<li>整合型构建工具：
<ol>
<li>支持Ant任务，Gradle可以直接引入Ant工程，并在运行时将Ant targets转换为Gradle tasks</li>
<li>可以自动的把Maven的pom.xml转换为Gradle脚本</li>
</ol>
</li>
<li>基于Groovy的构建脚本。构建脚本可以很容易的维护和重用</li>
<li>用于描述构建的丰富的领域模型</li>
<li>Gradle Wrapper：运行在没有安装Gradle的机器上运行Gradle构建</li>
</ol>
<p>Gradle的核心是一个基于Groovy的、扩展的DSL（Domain Specific Language），而不是向Maven、Ant那样，通过XML去描述构建步骤，具有非常好的灵活性。</p>
<div class="blog_h2"><span class="graybg">Gradle重要概念</span></div>
<table class="fixed-word-wrap" style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 25%; text-align: center;"> 名词</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>工程（Project）</td>
<td>每个构建都是由<span style="background-color: #c0c0c0;">一个或多个</span>projects 构成，project是一个抽象概念，既可以和通常的IDE中的工程对应，也可以仅仅代表某项需要完成的工作</td>
</tr>
<tr>
<td>任务（Task）</td>
<td>
<p>每个project可以包含一个或者多个task，task代表细化的构建步骤</p>
<p>各Task之间可以形成依赖关系，被依赖的Task总是先执行。Gradle能够依据Task形成的无回路有向图（Directed Acyclic Graph），依次执行Task，保证每个任务至多被执行一次</p>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">新特性</span></div>
<div class="blog_h3"><span class="graybg">3.x</span></div>
<ol>
<li>Gradle Daemon性能增强，让构建速度最快有75%的提升，默认启用</li>
<li>更好的IDE支持</li>
<li>改进并行任务执行</li>
<li>初步的Java 9支持</li>
<li>新的plugins DSL，用于启用插件，代替原先的<pre class="crayon-plain-tag">apply plugin</pre>：<br />
<pre class="crayon-plain-tag">plugins {
    id 'my.special.plugin' version '1.0' apply false
} </pre>
</li>
<li>改进增量构建</li>
<li>更快的依赖解析</li>
<li>改进eclipse-wtp插件</li>
<li>插件库升级</li>
</ol>
<div class="blog_h3"><span class="graybg">4.x</span></div>
<ol>
<li>支持依赖的并行下载</li>
<li>改进构建缓存</li>
<li>添加Google的Maven仓库的快捷方式：<br />
<pre class="crayon-plain-tag">repositories {
    google()
}</pre>
</li>
<li>支持使用Gradle属性来配置日志级别，属性名org.gradle.logging.level，支持的值：quiet, warn, lifecycle, info, debug，默认lifecycle</li>
<li>
<p>对删除文件的Task进行更好的建模。一个Task可以使用 @Destroys 注解，明确声明自己会删除文件：</p>
<pre class="crayon-plain-tag">class RemoveTempDirs extends DefaultTask {
    @Destroys
    FileCollection tempDirs

    @TaskAction
    void removeDirs() {
        project.delete(tempDirs)
    }
} </pre>
</li>
</ol>
<div class="blog_h3"><span class="graybg">5.x</span></div>
<ol>
<li>更快的构建速度</li>
<li>更细粒度的传递性依赖管理</li>
<li>内存使用更高效</li>
<li>新的Gradle调用选项</li>
<li>新的Task/Plugin API</li>
</ol>
<div class="blog_h1"><span class="graybg">gradlew</span></div>
<p>即Gradle Wrapper，是一段脚本，此脚本<span style="background-color: #c0c0c0;">调用你声明的版本的Gradle</span>，如果目标版本不存在，则从网络上下载。
<p>使用gradlew可以保证构建过程的一致性，但是网络差的话你就得静静的等待下载完成。</p>
<div class="blog_h2"><span class="graybg">生成gradlew</span></div>
<p>要生成gradlew相关的文件，需要本机已经安装任意版本的Gradle。执行命令<pre class="crayon-plain-tag">gradle wrapper</pre>即可。</p>
<p>生成的gradlew文件包括：</p>
<ol>
<li>gradle/wrapper/gradle-wrapper.properties 配置文件</li>
<li>gradle/wrapper/gradle-wrapper.jar 包含用于下载Gradle发行版的代码</li>
<li>gradlew, gradlew.bat Shell脚本和Windows批处理脚本</li>
</ol>
<pre class="crayon-plain-tag">#Tue Jun 25 10:21:26 CST 2019
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
# 从何处下载
# 后缀 -all 表示包含源码，-bin 表示不包含源码
distributionUrl=https\://services.gradle.org/distributions/gradle-4.10-all.zip</pre>
<p>如果下载缓慢，可以手工拷贝，存放到<pre class="crayon-plain-tag">~/.gradle/wrapper/dists</pre>对应的子目录下。</p>
<div class="blog_h2"><span class="graybg">升级gradlew</span></div>
<p>执行下面的命令升级到指定版本：</p>
<pre class="crayon-plain-tag">./gradlew wrapper --gradle-version 5.4.1</pre>
<div class="blog_h2"><span class="graybg">使用gradlew</span></div>
<p>gradlew的命令行调用格式和gradle完全一致。 </p>
<div class="blog_h1"><span class="graybg">安装与配置</span></div>
<ol>
<li>安装Gradle需要JDK/JRE，版本在6.0+。Gradle内置了Groovy库，因此后者不需要</li>
<li>到<a href="http://gradle.org/gradle-download/">官网</a>下载Gradle，并解压到某个目录，该目录设置为环境变量<pre class="crayon-plain-tag">GRADLE_HOME</pre> </li>
<li>将<pre class="crayon-plain-tag">$GRADLE_HOME/bin</pre> 添加到环境变量PATH中</li>
<li>打开终端，运行<pre class="crayon-plain-tag">gradle -v</pre> 验证安装是否成功</li>
</ol>
<div class="blog_h1"><span class="graybg">与Eclipse的集成</span></div>
<div class="blog_h2"><span class="graybg">插件安装</span></div>
<p>可以从Nodeclipse提供的Update Site：http://www.nodeclipse.org/updates/gradle-ide-pack/ 安装Gradle插件。</p>
<p>此站点中杂项内容非常多，可以根据需要，选择性的安装：</p>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="text-align: center;">插件</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>Gradle IDE</td>
<td>Pivotal提供 的Gradle与Eclipse的集成</td>
</tr>
<tr>
<td>Minimalist Gradle Editor</td>
<td>.gradle文件的编辑器，提供语法高亮等简单功能</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">Eclipse配置</span></div>
<div class="blog_h3"><span class="graybg">Pivotal Gradle IDE</span></div>
<p>打开Window ⇨ Preferences ⇨ Gradle：</p>
<ol>
<li>Gradle Distribution 点选 Folder，填写<pre class="crayon-plain-tag">%GRADLE_HOME%</pre> 对应的目录</li>
<li>Gradle User Home 点选 Directory，填写<pre class="crayon-plain-tag">%USERPROFILE%\.gradle</pre> </li>
</ol>
<p>打开Window ⇨ Preferences ⇨ Gradle ⇨ Arguments：</p>
<ol>
<li>Java Home 点选Workspace JRE，选择一个已安装的JRE</li>
<li>默认JVM参数可以根据需要调整</li>
</ol>
<div class="blog_h3"><span class="graybg">Gradle for Eclipse</span></div>
<p>打开Window ⇨ Preferences ⇨ Gradle EnIDE：</p>
<ol>
<li>Gradle Home to use：填写<pre class="crayon-plain-tag">%GRADLE_HOME%</pre> 对应的目录</li>
</ol>
<div class="blog_h3"><span class="graybg">Start Explorer</span></div>
<p>打开Window ⇨ Preferences ⇨ Desktop Environment：</p>
<ol>
<li>点选 Custom Desktop Environment</li>
<li>Start Shell设置为Console2，例如<pre class="crayon-plain-tag">D:\Programs\Console2\Console.exe -d ${resource_path}</pre> </li>
</ol>
<div class="blog_h2"><span class="graybg">创建Gradle工程</span></div>
<p>打开File ⇨ New ⇨ Other，选择Gradle/Gradle Project，即可创建Gradle工程，Sample project选择Java Quickstart，点击完成。</p>
<p>初次创建Gradle IDE会去下载一些东西，完毕后，形成类似下面的工程结构：</p>
<p><img class="aligncenter size-full wp-image-10089" src="https://blog.gmem.cc/wp-content/uploads/2015/01/gradle-project.jpg" alt="gradle-project" width="335" height="213" /></p>
<p>可以看到，默认配置下Gradle沿用了Maven的工程目录习惯。除了把pom.xml换成build.gradle，Maven工程和Gradle工程的结构完全相同。</p>
<p>Eclipse工程结构和元数据完全根据build.gradle导出，这个和Maven也是类似的。工程根目录右键 ⇨ Gradle ⇨ Refresh All，可以根据当前的build.gradle更新Eclipse工程结构。</p>
<p>要构建Gradle工程，可以工程根目录右键 ⇨ Run As ⇨ Gradle Build。</p>
<div class="blog_h1"><span class="graybg">Gradle命令行</span></div>
<p>命令格式：</p>
<pre class="crayon-plain-tag">//可以同时指定多个Task
gradle [option...] [task...]</pre>
<p>用法示例：</p>
<pre class="crayon-plain-tag"># 依次调用 compile 和 test 任务
# 它们所依赖的任务会根据依赖图的顺序被依次调用，每个任务只会调用一次
# 只需要声明任务之间的依赖，不需要人工控制任务执行顺序，是相对于Maven的优势
gradle compile test

# 使用-x 排除某个任务
gradle dist -x test

# 默认情况下，一旦有一个任务失败，Gradle就会终止执行，下面的选项禁止此行为
gradle run --continue

# 简化任务名：不需要输入完整，只需要足够避免歧义的前缀
gradle ru
# 简化任务名：驼峰式首字母，下面的命令调用gradle compTest
gradle cT

# Gradle默认依据当前目录下的build.gradle，使用-b可以选择构建其它脚本
gradle -q -b subdir/myproject.gradle hello

# 使用-p，可以选择构建的目录
gradle -q -p subdir hello

# 获取子工程列表
gradle -q projects

# 列出工程中所有任务
gradle tasks
gradle -q tasks --all   #同时显示任务依赖关系

# 显示任务的详细信息
gradle help --task taskName

# 显示工程中所有依赖库的列表
gradle dependencies  #输出依据依赖配置（Dependencies Configuration）分组
# 显示api子工程的所有依赖列表
gradle -q dependencies api:dependencie
# 显示api子工程的、testCompile配置的所有依赖列表
gradle -q api:dependencies --configuration testCompile
# 显示某个特定依赖库的情况，显示groovy依赖库的情况
gradle -q api:dependencyInsight --dependency groovy --configuration compile

# 显示工程属性列表
gradle properties
gradle -q api:propertie #子工程属性列表

# 记录构建日志
gradle run --profile   # 日志存放到build/reports/profile 

# 以特定的工程属性来构建
gradle hello -PskipHello

# 设置JVM属性
gradle hello -Djava.io.tempdir=/tmp

# 丢弃已经编译的脚本缓存
gradle  --recompile--scripts 

# 显示图形界面
gradle --gui </pre>
<div class="blog_h1"><span class="graybg"><a id="gradle-project-properties"></a>Gradle工程属性</span></div>
<p>除了通过gradle命令行参数<pre class="crayon-plain-tag">-P</pre> 直接为Gradle工程添加属性之外，还可以使用<pre class="crayon-plain-tag">gradle.properties</pre> 文件。如果有多个子工程，则每个子工程的目录都可以提供此文件：</p>
<pre class="crayon-plain-tag">gradlePropertiesProp=gradlePropertiesValue
# 对于根工程，可以设置JVM系统属性，这些属性以systemProp.开头
systemProp.system=systemValue</pre>
<div class="blog_h1"><span class="graybg">编写构建脚本</span></div>
<p><pre class="crayon-plain-tag">build.gradle</pre> 本质上是Groovy脚本，任何合法的Groovy语句都可以添加到其中。</p>
<div class="blog_h3"><span class="graybg">Hello World</span></div>
<p>在当前目录下创建一个UTF-8编码的build.gradle文件，或者通过Eclipse创建一个Gradle工程。打开build.gradle文件，输入下面的内容：</p>
<pre class="crayon-plain-tag">//可以声明工程属性
description = 'Hello project'

//声明一个名为hello的task
//新任务默认是org.gradle.api.DefaultTask的实例
task hello {
    //闭包指定了hello的action
    doLast {
        println 'Hello world!'  //任何Groovy脚本都支持
    }
}

//上述任务也可以用下面的快捷声明方式
task hello &lt;&lt; {
    println 'Hello world!'
}</pre>
<p>然后，打开终端，pushd到当前目录，运行命令：<pre class="crayon-plain-tag">gradle -q hello</pre>  ，可以看到输出：<pre class="crayon-plain-tag">Hello world!</pre> 。该命令就是调用hello这个Task，而这个Task仅仅是打印一条语句到控制台。</p>
<div class="blog_h3"><span class="graybg">默认任务</span></div>
<p>使用下面的脚本可以指定默认任务，这样，如果<pre class="crayon-plain-tag">gradle</pre> 命令不指定task参数，则自动运行这些任务：</p>
<pre class="crayon-plain-tag">defaultTasks 'hello', 'run'</pre>
<div class="blog_h3"><span class="graybg">Project API</span></div>
<p>对于构建中的每个工程，Gradle都创建了一个<pre class="crayon-plain-tag">org.gradle.api.Project</pre> 类型的对象，工程脚本<pre class="crayon-plain-tag">build.gradle</pre> 的顶级作用域就是该对象（this指向当前工程）：</p>
<pre class="crayon-plain-tag">apply plugin: 'java'

println this.name     //当前对象——当前工程——的name属性
println name          //自动委托给当前对象
println project.name  //this.project == this，预定义属性
//上面三个语句打印一样的结果</pre>
<p>除了上面的name以外，你还可以访问很多Project的属性，例如： </p>
<table class="fixed-word-wrap" style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">属性</td>
<td style="text-align: center;">类型</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>project</td>
<td>Project</td>
<td>当前Project 实例对象</td>
</tr>
<tr>
<td>path</td>
<td>String</td>
<td>工程的绝对路径</td>
</tr>
<tr>
<td>description</td>
<td>String</td>
<td>工程的描述</td>
</tr>
<tr>
<td>projectDir</td>
<td>File</td>
<td>包含了构建脚本的目录</td>
</tr>
<tr>
<td>build</td>
<td>File</td>
<td>projectDir/build</td>
</tr>
<tr>
<td>group</td>
<td>Object</td>
<td>组标识</td>
</tr>
<tr>
<td>name</td>
<td>String</td>
<td>工程名称</td>
</tr>
<tr>
<td>version</td>
<td>Object</td>
<td>版本标识</td>
</tr>
<tr>
<td>ant</td>
<td>org.gradle.api.AntBuilder</td>
<td>Ant实例对象</td>
</tr>
</tbody>
</table>
<p>Project包含很多常用的方法，可以在构建脚本中使用：</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>buildscript</td>
<td>配置驱动<span style="background-color: #c0c0c0;">构建过程本身的依赖</span>，常常在此脚本块中声明依赖和仓库</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">Script API</span></div>
<p>每个构建脚本都自动实现<pre class="crayon-plain-tag">org.gradle.api.Script</pre> 接口，因此<a href="https://docs.gradle.org/current/dsl/org.gradle.api.Script.html">该接口</a>提供的属性、方法你可以自由的使用。</p>
<div class="blog_h3"><span class="graybg">变量声明</span></div>
<p>在构建脚本中，你可以声明两种变量：</p>
<ol>
<li>局部变量 ( local ) ：只在局部作用域可见，使用关键字<pre class="crayon-plain-tag">def</pre> 声明，例如：<br />
<pre class="crayon-plain-tag">def dest = "dest"
task copy(type: Copy) {
    form "source"
    into dest
}</pre>
</li>
<li>扩展属性 ( extra )：Gradle领域模型中所有增强对象（包括但不限于：projects, tasks, source sets）都可以添加扩展属性，扩展属性在任何地方可见。使用ext脚本块可以同时声明多个属性。下面是一个示例：<br />
<pre class="crayon-plain-tag">ext {
    //为Project添加属性
    springVersion = "3.1.0.RELEASE"
    emailNotification = "build@master.org"
}
//也可以这样写
ext.springVersion = "3.1.0.RELEASE"
springVersion = "3.1.2.RELEASE"  //声明以后再修改，不需要ext关键字

//这样写会报错，自定义的外部属性，必须首先使用ext声明
hibernateVersion = "3.6.10.Final"

//设置所有源码集的purpose属性
sourceSets.all { ext.purpose = null }

sourceSets {
    //分别设置每个源码集的属性
    main {
        purpose = "production"
    }
    test {
        purpose = "test"
    }
    plugin {
        purpose = "production"
    }
}

task printProperties &lt;&lt; {
    println springVersion
    println emailNotification
    sourceSets.matching { it.purpose == "production" }.each { println it.name }
}</pre>
</li>
</ol>
<div class="blog_h3"><span class="graybg">Groovy语言特性与DSL</span></div>
<p>Gradle大量使用了Groovy提供的灵活语法特性和DSL支持。整个构建脚本都是合法的Groovy代码，虽然有时看起来更像是XML那样的配置文件：</p>
<pre class="crayon-plain-tag">//下面的脚本，更像是配置文件
dependencies {
    compile group: 'commons-collections', name: 'commons-collections', version: '3.2'
    testCompile project(':shared')
}</pre>
<p>实际上dependencies是Project对象的一个方法，它的签名如下：</p>
<pre class="crayon-plain-tag">public interface Project{
    void dependencies(Closure configureClosure);
}</pre>
<p>可以看到，该方法的入参是一个闭包，我们可以把上面的脚本改写的更像C-Style的代码：</p>
<pre class="crayon-plain-tag">dependencies({
    //这里是闭包体，其包含了两个方法调用：
    compile( [ group: 'commons-collections', name: 'commons-collections', version: '3.2' ] )
    testCompile( project(':shared') )
    //那么，闭包里面调用的方法，是哪个对象的呢？是this也就是闭包本身吗？不是……
    //根据Groovy的规则，闭包里面的变量，其Scope由闭包的delegate属性指定，因此上面的调用实际上是：
    delegate.testCompile( project(':shared') )
    //delegate已经由Gradle自动设置为dependencies
})</pre>
<p>我们还可以把上述脚本改写为等价的Java语句： </p>
<pre class="crayon-plain-tag">Closure&lt;Object&gt; c = new Closure&lt;Object&gt;(){

    public Object call( Object... args ){
    
        assert delegate == project.dependencies
        
        Map&lt;String,String&gt; map = new HashMap&lt;String,String&gt;();
        map.put( 'group',   'commons-collections' );
        map.put( 'name',    'commons-collections' );
        map.put( 'version', '3.2' );
        delegate.compile( dep );
        
        delegate.testCompile( project(':shared') )
        
    }
}
c.delegate = project.dependencies
project.dependencies( c );</pre>
<div class="blog_h3"><span class="graybg">脚本包含</span></div>
<p>可以把其它脚本的内容包含到当前构建脚本中：</p>
<pre class="crayon-plain-tag">println "configuring $project"
task hello &lt;&lt; {
    println' 'hello form other srcipt'
}</pre><br />
<pre class="crayon-plain-tag">apply from: 'other.gradle'  //包含其它脚本</pre>
<p> 其它脚本中定义的任务，可以直接使用：<pre class="crayon-plain-tag">gradle -q hello</pre> </p>
<div class="blog_h1"><span class="graybg">依赖管理</span></div>
<p>依赖管理是任何一种构建工具的关键特性，Gradle提供了易于理解的、和Maven/Ivy兼容的依赖管理机制。Gradle的依赖管理具有以下特点：</p>
<ol>
<li>传递性依赖管理</li>
<li>支持非受管的（non-managed）依赖：依赖可以仅仅是位于版本控制工具或者共享磁盘上的文件</li>
<li>支持定制化的依赖定义：可以在构建脚本中描述依赖的层次</li>
<li>完全可定制的依赖解析机制（Dependency Resolution）</li>
<li>与Maven和Ivy完全兼容</li>
<li>与既有的依赖管理工具，例如Maven和Ivy集成</li>
</ol>
<p>依赖管理分为两部分的内容：</p>
<ol>
<li>依赖项（dependencies）：当前工程在构建或者运行时需要的东西</li>
<li>发布项（publications）：当前工程的构建的产出，这些产出可能需要上传到仓库</li>
</ol>
<p>对于依赖项，Gradle采用了类似Maven的方式进行声明，而不像Ant那样指定绝对路径。依赖项的所在位置（远程仓库或者本地目录）另外声明。对于发布项，行为依赖于配置，可能是发布到本地目录，或者远程仓库。</p>
<div class="blog_h3"><span class="graybg">依赖配置</span></div>
<p>Gradle把依赖按照<span style="background-color: #c0c0c0;">依赖配置（dependency configuration）分组</span>。每个分组具有一个名称和若干其它属性，分组可以继承其它分组。某些插件会自动引入一些依赖配置，例如Java插件添加了：</p>
<table class="fixed-word-wrap full-width" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 25%; text-align: center;">依赖配置</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>compile</td>
<td>用来编译项目源代码的依赖</td>
</tr>
<tr>
<td>runtime</td>
<td>在运行时被生成的类使用的依赖，默认也作为编译时依赖</td>
</tr>
<tr>
<td>testCompile</td>
<td>编译测试代码的依赖</td>
</tr>
<tr>
<td>testRuntime</td>
<td>运行测试所需要的依赖</td>
</tr>
</tbody>
</table>
<p>除了插件自动引入的依赖配置以外，你还可以在构建脚本中自行添加。自定义依赖配置在很多情况下有用，例如添加一个既不在编译，也不在测试时需要的“运行时”依赖（典型的例子是JDBC驱动程序）到归档文件中。</p>
<p>下面示例了如何声明依赖配置：</p>
<pre class="crayon-plain-tag">configurations {
    compile {
        description = 'compile classpath'
        transitive = true //是否启用传递性依赖
    }
    runtime {
        extendsFrom compile
    }
}</pre>
<div class="blog_h3"><span class="graybg">依赖项声明</span></div>
<p>每一个依赖项声明的格式为：</p>
<pre class="crayon-plain-tag">dependencies{
    // 依赖配置名称 依赖项规格说明
}</pre>
<p>依赖项规格是什么形式取决于依赖的类型：</p>
<table class="fixed-word-wrap" style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 18%; text-align: center;">依赖类型</td>
<td style="text-align: center;">依赖项规格说明</td>
</tr>
</thead>
<tbody>
<tr>
<td>
<p>外部模块依赖</p>
</td>
<td>
<p>即对当前构建以外的模块（Module）的依赖，模块可以存放在Maven、Ivy远程仓库或者本地仓库中，甚至位于本地目录中。一个外部依赖使用group、name、version三个属性限定。下面是一些例子：</p>
<pre class="crayon-plain-tag">dependencies {
    //可以使用group name version字段来指定
    runtime group: 'hibernate', name: 'hibernate-core', version: '3.6.7'
    //也可以使用分号分隔的group:name:version指定
    runtime 'hibernate:hibernate-core:3.6.7','springframework:spring-aop:2.5'
    //可以控制传递性依赖
    runtime('hibernate:hibernate:3.0.5') {
        transitive = true
    }
    //仅构件标记（Artifact only notation），不去下载模块描述符（例如pom.xml），仅仅下载构件本身
    runtime "org.groovy:groovy:2.2.0@jar"
    //使用分类器字段
    otherConf group: 'test', name: 'service', version: '1.0', classifier: 'jdk14'
}</pre>
</td>
</tr>
<tr>
<td>
<p>工程依赖
</td>
<td>在多工程构建时，声明各子工程之间的依赖，例如：<br />
<pre class="crayon-plain-tag">project(':shared')</pre>
</td>
</tr>
<tr>
<td>
<p>文件依赖</p>
</td>
<td>即对位于本地文件系统上一系列文件的依赖，例如：<br />
<pre class="crayon-plain-tag">dependencies {
    runtime files('libs/a.jar', 'libs/b.jar')
    runtime fileTree(dir: 'libs', include: '*.jar')
}</pre>
</td>
</tr>
<tr>
<td>
<p>客户端模块依赖</p>
</td>
<td>类似于外部模块依赖，目标构件位于某个仓库中，但是模块的元数据文件则由当前构建指定。如果需要覆盖外部模块的元数据，可以使用这种依赖。示例：<br />
<pre class="crayon-plain-tag">runtime module(group: 'org.apache.ant', name: 'ant', version: '1.9.4') {
    dependencies "org.apache.ant:ant-launcher:1.9.4@jar",
                     "org.apache.ant:ant-junit:1.9.4"
}</pre>
</td>
</tr>
<tr>
<td>
<p>Gradle API依赖</p>
</td>
<td>针对当前Gradle版本的API的依赖，开发Gradle插件和Task类型时使用：<br />
<pre class="crayon-plain-tag">dependencies {
    compile gradleApi()
}</pre>
</td>
</tr>
<tr>
<td>
<p>本地Groovy依赖</p>
</td>
<td>针对当前Gradle使用的Groovy版本的依赖，开发Gradle插件和Task类型时使用：<br />
<pre class="crayon-plain-tag">dependencies {
    compile localGroovy()
}</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h3"><span class="graybg">排除传递性依赖</span></div>
<pre class="crayon-plain-tag">configurations {
    //为指定的依赖配置指定传递性依赖的排除规则
    compile.exclude module: 'commons'
    //为所有的依赖配置指定传递性依赖的排除规则
    all*.exclude group: 'org.gradle.test.excludes', module: 'reports'
}

dependencies {
    compile("org.gradle.test.excludes:api:1.0") {
        exclude module: 'shared' //为单个依赖项指定传递性依赖的排除规则
    }
}</pre>
<div class="blog_h3"><span class="graybg">仓库</span></div>
<p>Gradle必须知道从哪里下载外部依赖，这是由仓库配置来指定的：</p>
<pre class="crayon-plain-tag">repositories {
    //Maven本地仓库，寻找本地仓库的逻辑与Maven相同
    mavenLocal()
    //Maven中心仓库
    mavenCentral()
    //JCenter仓库
    jcenter()
    //其它Maven远程仓库
    maven {
        //可以指定身份验证信息
        credentials {
            username 'user'
            password 'password'
        }
        url "http://repo.mycompany.com/maven2"
        //如果上面的URL找不到构件，则在下面找
        artifactUrls "http://repo.mycompany.com/jars"
    }
    //Ivy远程仓库
    ivy {
        url "http://repo.mycompany.com/repo"
    }
    //Ivy本地仓库
    ivy {
        url "../local-repo"
    }
    //扁平布局的文件系统仓库
    flatDir {
        dirs 'lib'
    }
    flatDir {
        dirs 'lib1', 'lib2'
    }
}</pre>
<div class="blog_h3"><span class="graybg">发布构件</span></div>
<p>除了可以发布到本地目录外，Gradle还支持发布构建的产出到Ivy、Maven仓库：</p>
<pre class="crayon-plain-tag">uploadArchives {
    repositories {
        //发布到扁平结构的目录
        flatDir {
            dirs 'repos'
        }
        //发布到Ivy仓库
		ivy {
		    credentials {
		        username "username"
		        password "pw"
		    }
		    url "http://repo.mycompany.com"
		}
		//发布到Maven仓库
		mavenDeployer {
            repository(url: "file://localhost/tmp/myRepo/")
        }
    }
}</pre>
<div class="blog_h1"><span class="graybg">任务详解</span></div>
<div class="blog_h3"><span class="graybg">多种定义Task的方式</span></div>
<p> task关键字本质上是Project对象的一组方法：</p>
<pre class="crayon-plain-tag">Task task(String name) throws InvalidUserDataException
Task task(String name, Closure configureClosure)
Task task(Map&lt;String,?&gt; args, String name) throws InvalidUserDataException
Task task(Map&lt;String,?&gt; args, String name, Closure configureClosure)</pre>
<p> 下面列举了不同风格的Task定义：</p>
<pre class="crayon-plain-tag">// 操作符“&lt;&lt;”被Task重载过，它接受一个闭包，该闭包可以对当前Task进行任意操作
// 直接使用不带引号的hello0，会被解释为this.hello0，Gradle可能进行了Groovy元编程，让不存在的变量指定作为当前project的属性看待

// 不带括号
task hello0 &lt;&lt; {
    description 'Hello task'   // 可以给Task添加描述
    println "hello"
}
// 带括号
task(hello1) &lt;&lt; {
    println "hello1"
}
// 任务名带引号
task('hello2') &lt;&lt;
{
    println "hello2"
}
//指定任务类型
task('copy', type: Copy) {
    //闭包的delegate是task对象，注意Gradle的这种风格，与上面章节dependencies中的闭包delegate指向是一个风格
    from(file('srcDir'))
    into(buildDir)
}
//这样也可以
tasks.create(name: 'hello') &lt;&lt; {
    println "hello"
}</pre>
<div class="blog_h3"><span class="graybg">获得Task的引用</span></div>
<p>首先，任何Task都作为Project的属性看待：</p>
<pre class="crayon-plain-tag">task hello
//作为当前工程的属性来获得Task的引用
println hello.name
println project.hello.name</pre>
<p>也可以通过<pre class="crayon-plain-tag">org.gradle.api.tasks.TaskContainer</pre> 来访问Task：</p>
<pre class="crayon-plain-tag">task hello
//作为TaskContainer的属性访问
println tasks.hello.name
println tasks['hello'].name

project(':projectA') {
    task hello
}

//也可以调用getByPath()方法
//路径根部的:可以省略
println tasks.getByPath('hello').path  //:hello
println tasks.getByPath(':hello').path //:hello
println tasks.getByPath('projectA:hello').path  //:projectA:hello
println tasks.getByPath(':projectA:hello').path //:projectA:hello</pre>
<div class="blog_h3"><span class="graybg">定制Task的类型和属性</span></div>
<p> 下面实例了指定Task的类型为<pre class="crayon-plain-tag">org.gradle.api.tasks.Copy</pre> 并且设置其属性的几种方式：</p>
<pre class="crayon-plain-tag">//Copy任务用来拷贝文件到目标目录中，支持过滤和重命名

//指定任务类型
task myCopy(type: Copy)

//同时设置属性
Copy myCopy = task(myCopy, type: Copy)
myCopy.from 'resources'
myCopy.into 'target'
myCopy.include('**/*.txt', '**/*.xml', '**/*.properties')

//闭包方式，再次注意闭包的delegate指向是当前task，delegate在Minimalist Gradle Editor中常常显示为关键字
task copy(type: Copy) {
   from 'resources'
   into 'target'
   include('**/*.txt', '**/*.xml', '**/*.properties')
}</pre>
<div class="blog_h3"><span class="graybg">为Task添加依赖 </span></div>
<pre class="crayon-plain-tag">//声明任务intro依赖于hello
task intro(dependsOn: 'hello') &lt;&lt; { //如果被依赖任务在后面定义，则这里引用的任务名必须加引号
    println "I'm Gradle"
}
//被依赖的任务可以在后面定义
task hello &lt;&lt; {
    println 'Hello world!'
}
//访问Task的方法也可以添加依赖
intro.dependsOn hello
//也可以通过闭包添加依赖
intro.dependsOn {
    tasks.findAll { task -&gt; task.name.startsWith('lib') }
}

project('projectA') {
    //要依赖其它项目中的任务，必须指定任务的路径
    task taskX(dependsOn: ':projectB:taskY') &lt;&lt; {
        println 'taskX'
    }
}
project('projectB') {
    task taskY &lt;&lt; {
        println 'taskY'
    }
}</pre>
<div class="blog_h3"><span class="graybg">替换一个已经存在的任务</span></div>
<pre class="crayon-plain-tag">task copy(type: Copy)
task copy(overwrite: true) &lt;&lt; { //必须设置overwrite为true
    println('I am the new one.')
}</pre>
<div class="blog_h3"><span class="graybg">跳过任务执行</span></div>
<p>Gradle提供几种方式，来绕过某个任务的执行： </p>
<pre class="crayon-plain-tag">task hello &lt;&lt; {
    println 'hello world'
//只有当工程没有属性“skipHello”的时候，才会执行hello
hello.onlyIf { !project.hasProperty('skipHello') }
//运行gradle hello -q -PskipHello 不会打印任何信息

//也可以使用StopExecutionException异常跳过任务
hello.doFirst{ //添加如下逻辑到Task最前面
     if (true) { throw new StopExecutionException() }
}

//还可以设置enabled属性，为false则不会执行
hello.enabled = false</pre>
<div class="blog_h3"><span class="graybg">跳过up-to-date的任务</span></div>
<p>Gradle判断任务是否已经是最新状态的依据是：如果任务的输入没有变化，那么当前任务不需要执行（也就是up-to-date）。更进一步说，Gradle的判断规则如下：</p>
<ol>
<li>Task执行前，获得输入文件的散列值</li>
<li>Task执行完毕后，存储输出文件的散列值</li>
<li>Task执行前，如果输入、输出的散列值和上一次执行相比，没有变化，那么任务是up-to-date </li>
</ol>
<p>可以看到，如果任务没有输出，那么它永远不会被看做是up-to-date的。如何声明任务的输入输出呢？</p>
<pre class="crayon-plain-tag">task transform {
    ext.srcFile = file('mountains.xml')
    ext.destDir = new File(buildDir, 'generated')
    
    //下面两行定义了当前任务的输入和输出
    inputs.file srcFile
    outputs.dir destDir
    
    doLast {
       //Task的逻辑
    }
}</pre>
<p>上述脚本设置了Task的inputs、outputs属性，如果没必要执行，那么Gradle会自动跳过此任务。</p>
<p>对于复杂的场景，可以调用<pre class="crayon-plain-tag">TaskOutputs.upToDateWhen()</pre> 来判断任务是否up-to-date。</p>
<div class="blog_h3"><span class="graybg">动态创建Task</span></div>
<pre class="crayon-plain-tag">//动态创建4个任务task1 ... task3
4.times { counter -&gt;
    task "task$counter" &lt;&lt; {
        println "I'm task number $counter"
    }
}

//基于规则的动态任务
//对于任何pingXxx，都作为任务看待
tasks.addRule("Pattern: ping&lt;ID&gt;") { String taskName -&gt;
    if (taskName.startsWith("ping")) {
        task(taskName) &lt;&lt; {
            println "Pinging: " + (taskName - 'ping')
        }
    }
}
//执行命令 gradle -q pingServer1
//输出 Pinging: Server1

//这些动态任务甚至还可以用做任务依赖
task groupPing {
    dependsOn pingServer1, pingServer2
}</pre>
<div class="blog_h3"><span class="graybg">添加finally任务</span></div>
<p>类似于Java的<pre class="crayon-plain-tag">finally</pre> 代码块，这种任务用于在目标任务结束后执行，不论目标任务是否执行成功：</p>
<pre class="crayon-plain-tag">task taskX &lt;&lt; {
    throw new RuntimeException()
}
task taskY &lt;&lt; {
    println 'taskY'
}

taskX.finalizedBy taskY
//运行 gradle -q taskX
//输出 taskY</pre>
<div class="blog_h1"><span class="graybg"> 文件操作</span></div>
<p>由于大多数构建都需要操作文件，因此Gradle定义了一些额外的文件操作API。</p>
<pre class="crayon-plain-tag">/************************ 文件定位 ***************************/
// 使用一个相对路径
File configFile = project.file('src/config.xml')
// 使用一个绝对路径
configFile = file('file:/some/path.xml')
configFile = file('F:/config.xml')



/************************ 文件集合 ***************************/
FileCollection collection = files(
    'src/file1.txt',
    new File('src/file2.txt'),
    ['src/file3.txt', 'src/file4.txt'] //会被自动展开
)
//迭代集合
collection.each {File file -&gt;
    println file.name
}
//类型转换
List list = collection as List
String path = collection.asPath
File file = collection.singleFile
//添加、去除文件元素
def union = collection + files('src/file3.txt')
def different = collection - files('src/file3.txt')


/************************ 文件树 ***************************/
//从相对目录创建
FileTree tree = fileTree(dir: 'src/main')
tree.include '**/*.java'
tree.exclude '**/Abstract*'
//从归档文件创建
FileTree zip = zipTree('someFile.zip')
FileTree tar = tarTree('someFile.tar')
//遍历
tree.each {File file -&gt;
    println file
}
//过滤
FileTree filtered = tree.matching {
    include 'org/gradle/api/**'
}
//合并
FileTree sum = tree + fileTree(dir: 'src/test')


/************************ 复制文件 ***************************/
//各种文件来源
task copyTask(type: Copy) {
    //复制目录下所有文件
    from 'src/main/webapp'
    //复制单个单独文件
    from 'src/staging/index.html'
    //从一个任务的输出复制
    from copyTask
    //从一个任务的outputs属性复制
    from copyTaskWithPatterns.outputs
    //复制归档文件中的内容
    from zipTree('src/main/assets.zip')
    //最终指定目标目录
    into { getDestDir() }
}
//文件过滤
task copyTaskWithPatterns(type: Copy) {
    from 'src/main/webapp'
    into 'build/explodedWar'
    include '**/*.html'
    include '**/*.jsp'
    exclude { details -&gt; details.file.name.endsWith('.html') }
}
//复制的过程中重命名
task rename(type: Copy) {
    //闭包指定重命名规则
    rename { String fileName -&gt; fileName.replace('-staging-', '') } 
    //使用正则表达式指定映射规则
    rename(/(.+)-staging-(.+)/, '$1$2')
}
//处理文件内容
task filter(type: Copy) {
    //替换文件中的标记，标记必须是@tokenName@的形式
    expand(copyright: '2009', version: '2.3.1')
    expand(project.properties)
    //使用闭包处理每一行
    filter { String line -&gt; "[$line]" }
    //使用闭包删除行
    filter { String line -&gt;  line.startsWith('-') ? null : line  }
}


/************************ 同步两个目录 ***************************/
task libs(type: Sync) {
    from configurations.runtime
    into "$buildDir/libs"
}


/************************ 创建归档文件 ***************************/
task zip(type: Zip) {
    from 'src/dist'
    into('libs') {
        from configurations.runtime //指定依赖配置类型
    }
    baseName = 'customName' //指定归档文件名
}</pre>
<div class="blog_h1"><span class="graybg">日志记录</span></div>
<div class="blog_h3"><span class="graybg">日志记录级别</span></div>
<p>Gradle支持以下日志记录级别：</p>
<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>ERROR</td>
<td> </td>
<td>打印错误消息</td>
</tr>
<tr>
<td>QUIET</td>
<td>-q  --quiet</td>
<td>打印重要的、你应当知道的消息 </td>
</tr>
<tr>
<td>WARNING </td>
<td> </td>
<td>打印警告信息</td>
</tr>
<tr>
<td>LIFECYCLE</td>
<td>默认</td>
<td>这是Gradle的默认级别，打印处理进度信息</td>
</tr>
<tr>
<td>INFO </td>
<td>-i  --info</td>
<td>打印一般性的消息 </td>
</tr>
<tr>
<td>DEBUG</td>
<td>-d --debug</td>
<td>打印调试信息</td>
</tr>
</tbody>
</table>
<p>在打印日志的同时，还可以指定命令行参数：</p>
<ol>
<li>-s  --stacktrace：打印调用栈</li>
<li>-S  --full-stacktrace：打印完整调用栈</li>
</ol>
<div class="blog_h3"><span class="graybg">打印自己的日志</span></div>
<p>可以向标准输出打印日志：</p>
<pre class="crayon-plain-tag">println 'A message which is logged at QUIET level'</pre>
<p>或者通过<pre class="crayon-plain-tag">logger</pre> 属性打印日志： </p>
<pre class="crayon-plain-tag">logger.quiet('An info log message which is always logged.')
logger.error('An error log message.')
logger.warn('A warning log message.')
logger.lifecycle('A lifecycle info log message.')
logger.info('An info log message.')
logger.debug('A debug log message.')
logger.trace('A trace log message.')</pre>
<p>还可以使用外部日志工具：</p>
<pre class="crayon-plain-tag">import org.slf4j.Logger
import org.slf4j.LoggerFactory

Logger slf4jLogger = LoggerFactory.getLogger('some-logger')
slf4jLogger.info('An info log message logged using SLF4j')</pre>
<div class="blog_h1"><span class="graybg"> Gradle的插件机制</span></div>
<p>就像Maven一样，Gradle核心本身也不能做多少有意义的事情，实际的构建工作都是由插件完成的。Gradle允许插件：</p>
<ol>
<li>扩展Gradle的DSL模型，例如可以配置新的DSL元素</li>
<li>应用“约定由于配置”原则，为工程提供缺省配置</li>
<li>引入新的默认任务集</li>
</ol>
<p>Gradle插件分为两类：</p>
<ol>
<li>脚本插件：本质上是额外的构建脚本，可以进一步的配置构建逻辑</li>
<li>二进制插件：是接口<pre class="crayon-plain-tag">org.gradle.api.Plugin</pre> 的实现，以编程的的方式操控构建</li>
</ol>
<div class="blog_h3"><span class="graybg">应用插件到工程</span></div>
<p>使用方法<pre class="crayon-plain-tag">Project.apply()</pre> 可以应用插件到工程，应用同一插件多次是安全的：</p>
<pre class="crayon-plain-tag">/** 应用一个脚本插件 **/
// 实际上就是include一段脚本
apply from: 'other.gradle'

/** 应用二进制插件 **/
apply plugin: 'java'  //基于短名
apply plugin: JavaPlugin //基于插件类，org.gradle.api.plugins包内的插件不需要全名
//通过构建脚本块可以引入插件依赖，并使用插件
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath "com.jfrog.bintray.gradle:gradle-bintray-plugin:0.4.1"
    }
}
apply plugin: "com.jfrog.bintray"</pre>
<div class="blog_h3"><span class="graybg">Gradle现有插件列表</span></div>
<table class=" fixed-word-wrap full-width">
<thead>
<tr>
<td style="width: 15%; text-align: center;">分类</td>
<td style="width: 25%; text-align: center;">插件标识</td>
<td style="text-align: center;">说明</td>
</tr>
</thead>
<tbody>
<tr>
<td rowspan="5">语言插件</td>
<td>java</td>
<td>为工程添加Java编译、测试功能，是需要其它插件的基础</td>
</tr>
<tr>
<td>groovy</td>
<td>支持Groovy代码的编译</td>
</tr>
<tr>
<td>scala</td>
<td>支持Scala代码的编译</td>
</tr>
<tr>
<td>antlr</td>
<td>增加对ANTLR的生成解析器的支持</td>
</tr>
<tr>
<td colspan="2">此外还有尚在孵化中的：assembler、c、cpp、objective-c、windows-resources</td>
</tr>
<tr>
<td rowspan="6">集成插件</td>
<td>application</td>
<td>支持把Java工程通过命令行运行</td>
</tr>
<tr>
<td>ear</td>
<td>增加对构建J2EE应用程序的支持</td>
</tr>
<tr>
<td>war</td>
<td>
<p>支持构建War包</p>
</td>
</tr>
<tr>
<td>maven</td>
<td>
<p>支持发布构件到Maven仓库</p>
</td>
</tr>
<tr>
<td>sogi</td>
<td>支持构建OSGI工程</td>
</tr>
<tr>
<td>jetty</td>
<td>支持在嵌入式的Jetty容器中部署War包或者工程构建结果（解包）</td>
</tr>
<tr>
<td rowspan="7">开发插件</td>
<td>checkstyle</td>
<td>使用Checksytle对工程的Java代码质量进行检查</td>
</tr>
<tr>
<td>findbugs</td>
<td>使用Findbugs对工程的Java代码质量进行检查</td>
</tr>
<tr>
<td>codenarc</td>
<td>使用Codenarc对工程的Groovy代码质量进行检查</td>
</tr>
<tr>
<td>eclipse</td>
<td>生成Eclipse IDE的元数据，以便导入到Eclipse工作区</td>
</tr>
<tr>
<td>eclipse-wtp</td>
<td>与eclipse类似，支持生成Eclipse WTP工程结构</td>
</tr>
<tr>
<td>idea</td>
<td>生成Intellij IDEA IDE的元数据，以便可以导入</td>
</tr>
<tr>
<td>signing</td>
<td>对生成的归档文件进行签名</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">Gradle构建生命周期</span></div>
<div class="blog_h3"><span class="graybg">构建的三个阶段 </span></div>
<p>Gradle构建分为三个独立的阶段：</p>
<ol>
<li>Initialization：Gradle支持多工程构建。在初始化阶段，Gradle会确定有哪些工程参与构建，并为它们创建Project实例</li>
<li>Configuration：在此阶段，所有的Project对象被合理配置，每个Project的构建脚本（build script，即build.gradle）被执行</li>
<li>Execution：Gradle依据命令行参数、当前所在目录决定需要执行的Task的子集。Task的全集在Configuration阶段已经生成</li>
</ol>
<p>下面示意构建脚本的不同部分在哪个阶段执行：</p>
<pre class="crayon-plain-tag">println '该代码在配置阶段执行'
task configured {
	println '该代码在配置阶段执行'
}
task test &lt;&lt; {
	println '该代码在执行阶段执行'
}
task testBoth {
	doFirst {
	  println '该代码在执行阶段执行，最先执行'
	}
	doLast {
	  println '该代码在执行阶段执行，最后执行'
	}
	println '该代码在配置阶段执行'
}</pre>
<div class="blog_h3"><span class="graybg">初始化阶段</span></div>
<p>在初始化阶段，Gradle必须确定进行的单工程还是多工程构建（参见下一章）。多工程的设置总是存放在settings.gradle，Gradle自<span style="background-color: #c0c0c0;">动的寻找该文件的</span>顺序如下：</p>
<ol>
<li>在当前目录下寻找</li>
<li>在同级的、名为master的目录中寻找</li>
<li>到父目录中寻找</li>
</ol>
<p>如果找到settings.gradle，Gradle寻找被构建工程是否存在于settings.gradle所声明的工程树中，如果是，则执行多工程构建。否则，执行单工程构建。找不到settings.gradle则一定执行单工程构建。</p>
<p>注意，上述自动寻找settings.gradle的逻辑，仅在多工程满足下面条件之一时发生：</p>
<ol>
<li>工程物理位置与工程树结构对应</li>
<li>或者，使用扁平目录结构，并且用master目录表示主工程</li>
</ol>
<p>否则，你必须在settings.gradle所在目录执行Gradle命令以触发多工程构建。</p>
<div class="blog_h3"><span class="graybg">单工程构建的配置与执行阶段</span></div>
<p>在初始化结束之后，如果时单工程构建，其过程很简单。Gradle会寻找命令行传入的任务名，并<span style="background-color: #c0c0c0;">把每个任务作为单独的构建执行</span>。</p>
<div class="blog_h3"><span class="graybg">生命周期事件</span></div>
<p>在生命周期各关键点<span style="background-color: #c0c0c0;">前后</span>，Gradle会发布多种事件（通知），你可以在构建脚本中对这些事件进行处理：</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>Project evaluation</td>
<td>在工程被evaluate前后，可以得到一个通知：<br />
<pre class="crayon-plain-tag">allprojects {
	afterEvaluate { project -&gt;
		println '$it evaluated'
	}
}</pre>
</td>
</tr>
<tr>
<td>Task creation</td>
<td>在Task被加入到工程前后，可以得到通知：<br />
<pre class="crayon-plain-tag">tasks.whenTaskAdded { task -&gt;
    task.ext.srcDir = 'src/main/java'
}</pre>
</td>
</tr>
<tr>
<td>Task execution</td>
<td>在任务执行前后，可以得到通知：<br />
<pre class="crayon-plain-tag">gradle.taskGraph.beforeTask { Task task -&gt;
	println "executing $task ..."
}

gradle.taskGraph.afterTask { Task task, TaskState state -&gt;
	if (state.failure) {
		println "FAILED"
	}
}</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h1"><span class="graybg">多工程（multi-project）构建</span></div>
<p>所谓多工程构建，是指在执行单次Gradle命令时，同时完成多个工程（一个root工程，外加若干个子工程，每个子工程也可以具有自己的子工程）的构建。必须通过<pre class="crayon-plain-tag">settings.gradle</pre> 文件来指定参与到构建中的工程，该脚本中的this指向<pre class="crayon-plain-tag">org.gradle.api.initialization.Settings</pre> 。Gradle依据该脚本构建工程的树状结构，此树具有<span style="background-color: #c0c0c0;">唯一的根目录</span>，每个子工程都具有一个“路径”用于表示其在树中的位置，通常情况下<span style="background-color: #c0c0c0;">路径与子工程在文件系统中的位置一致</span>。默认的，Gradle认为<span style="background-color: #c0c0c0;">settings脚本所在目录就是根工程</span>，该行为也是可配置的。</p>
<div class="blog_h3"><span class="graybg">简单的例子</span></div>
<p>假设有一个包含四个子工程的工程，结构如下：</p>
<pre class="crayon-plain-tag">multiproject/
  api/                      #生成一个JAR包，提供WebService客户端的功能
  services/webservice/      #WebService服务端
  shared/                   #其它三个工程依赖该工程
  services/shared/</pre>
<p>要启用多工程的支持，必须在工程集的根目录添加<pre class="crayon-plain-tag">settings.gradle</pre> 文件，指定哪些工程需要被构建：</p>
<pre class="crayon-plain-tag">include "shared", "api", "services:webservice", "services:shared"</pre>
<p> 根目录通常作为工程的容器，其中可以通过<pre class="crayon-plain-tag">subprojects</pre> 来设置所有子工程共享的配置信息：</p>
<pre class="crayon-plain-tag">//所有子目录下的工程都获得下列配置信息：
subprojects {
    apply plugin: 'java'
    apply plugin: 'eclipse-wtp'

    repositories {
       mavenCentral()
    }

    dependencies {
        testCompile 'junit:junit:4.12'
    }

    version = '1.0'

    jar {
        manifest.attributes provider: 'gradle'
    }
}
//注意，由于是在subprojects段设置，因此根工程不会获得这些配置</pre>
<p>可以声明子工程之间的依赖：</p>
<pre class="crayon-plain-tag">dependencies {
    compile project(':shared')
}</pre>
<p>上面的设置可以确保shared总是在api前面构建，因为后者依赖于前者。 </p>
<div class="blog_h2"><span class="graybg">跨工程的配置</span></div>
<div class="blog_h3"><span class="graybg" style="font-weight: bold;">按需配置</span></div>
<p>默认的，Gradle使用<span style="background-color: #c0c0c0;">全量配置</span>方式：在任何Task执行之前，所有工程都被配置完毕。如果工程数量达到上百个，这会导致配置阶段耗费的时间过于漫长。对不参与本次构建的工程进行配置是不必要的，因此，Gradle 1.4引入了按需配置（Configuration on demand）模式，按需配置<span style="background-color: #c0c0c0;">仅仅去配置那些与被请求执行任务相关的工程</span>。在未来，按需配置可能变为默认或者唯一的配置模式。</p>
<p>在按需配置模式下，工程按照下面的规则配置：</p>
<ol>
<li>根工程总是被配置，这保证典型的公共配置脚本被支持（allprojects和subprojects脚本块）</li>
<li>如果Gradle命令没有指定目标，则当前目录对应的工程被配置。这保证工程默认任务（Default tasks）可以正常工作</li>
<li>依赖工程被配置，如果projectA在编译时依赖于projectB，那么构建projectA会导致projectB也被配置</li>
<li>依赖任务所在工程被配置，例如<pre class="crayon-plain-tag">taskA.dependsOn(":projectB:taskB1")</pre> 导致构建taskA时projectB被配置</li>
<li>通过命令行指定Task的路径，那么路径中牵涉到的工程都会被配置，例如<pre class="crayon-plain-tag">gradle projectA:projectB:someTask</pre> 导致projectB被配置</li>
</ol>
<p>在配置文件<pre class="crayon-plain-tag">gradle.properties</pre> 中指定<pre class="crayon-plain-tag">org.gradle.configureondemand</pre> ，可以启用按需配置模式。</p>
<div class="blog_h3"><span class="graybg">定义公共行为</span></div>
<p>以一个简单的多工程为例，根工程root包含一个子工程sub0：</p>
<pre class="crayon-plain-tag">root/
  build.gradle
  settings.gradle
  sub0/</pre>
<p>settings.gradle的内容如下：<pre class="crayon-plain-tag">include 'sub0'</pre>  。sub0下面的构建脚本文件不是必须的，其构建脚本可以<span style="background-color: #c0c0c0;">直接放在根工程的build.gradle</span>里面：</p>
<pre class="crayon-plain-tag">Closure cl = { task -&gt; println "I'm $task.project.name" }
task hello &lt;&lt; cl
project(':sub0') {
    task hello &lt;&lt; cl
}
# 在根工程目录运行gradle -q hello输出如下：
# I'm root
# I'm sub0
# 注意，所有子工程同名Task会自动执行</pre>
<p>其实，Gradle允许在任何一个工程的构建脚本中访问所有工程，只要调用<pre class="crayon-plain-tag">project(projectName)</pre> 方法即可。</p>
<p>下面的脚本块可以为所有工程添加公共行为：</p>
<pre class="crayon-plain-tag">allprojects {
    task hello &lt;&lt; { task -&gt; println "I'm $task.project.name" }
}</pre>
<p>下面的脚本则为所有子工程添加公共行为：</p>
<pre class="crayon-plain-tag">subprojects {
    hello &lt;&lt; { println "I'm $it.project.name" }
}</pre>
<p>还可以依据各种条件进行过滤，针对符合条件的工程添加行为：</p>
<pre class="crayon-plain-tag">//根据子工程的名称进行过滤
configure(subprojects.findAll {it.name != 'sub1'}) {
    hello &lt;&lt; { println '$it.project.name' }
}</pre>
<div class="blog_h2"><span class="graybg">多工程构建的Task执行规则 </span></div>
<p>当<span style="background-color: #c0c0c0;">在根工程</span>目录执行<pre class="crayon-plain-tag">gradle taskA</pre>  时，根工程的taskA以及所有子工程的taskA任务<span style="background-color: #c0c0c0;">都会被执行</span>。</p>
<p>Gradle支持指定Task的绝对路径：</p>
<pre class="crayon-plain-tag">gradle -q :hello   //根工程的hello任务
:sub0:hello        //sub0工程的hello任务
hello              //当前工程的hello任务</pre>
<p>如果路径以分号开始，则表示是绝对路径（相当于根）。根工程是唯一不需要以名称指代的工程。 </p>
<div class="blog_h3"><span class="graybg">执行依赖</span></div>
<p>要确定Task的执行顺序，可以使用<pre class="crayon-plain-tag">dependsOn</pre> ，例如：</p>
<pre class="crayon-plain-tag">//跨工程的任务依赖
task consume(dependsOn: ':producer:produce') &lt;&lt; {
    println("Consuming message: ${rootProject.producerMessage}")
}</pre>
<p>被依赖的Task总是先被执行。</p>
<div class="blog_h3 blog_h2"><span class="graybg">库（Lib）工程依赖</span></div>
<p>库依赖（lib  dependency）是一种特殊的执行依赖。以Java构建为例，工程构建时，要求作为库的Jar已经生成好（此Jar可能传递性的依赖于其它Jar）并且添加到当前工程的Classpath中。如果这些库作为多工程构建的子工程存在，这就意味着工程之间存在依赖，这种依赖使用<pre class="crayon-plain-tag">dependencies</pre> 块声明：</p>
<pre class="crayon-plain-tag">subprojects {
    // 子工程公共属性
    apply plugin: 'java'
    group = 'cc.gmem.study.gradle'
    version = '1.0'
}

project(':api') {
    configurations {
        spi  //声明一个依赖配置（dependency configurations）
    }
    dependencies {
        compile project(':shared') //声明库依赖：编译时依赖于shared工程
    }
    task spiJar(type: Jar) {
        baseName = 'api-spi'
        dependsOn classes
        from sourceSets.main.output
        include('org/gradle/sample/api/**')
    }
    //声明产出构件
    artifacts {
        spi spiJar
    }
}

project(':services:personService') {
    dependencies {
        compile project(':shared') //声明库依赖
        compile project(path: ':api', configuration: 'spi') //声明库依赖，并指定配置
        testCompile "junit:junit:4.12", project(':api')
    }
}</pre>
<p>如果要禁止依赖工程的构建，可以使用gradle命令的<pre class="crayon-plain-tag">-a</pre> 选项。</p>
<div class="blog_h1"><span class="graybg">构建Java应用</span></div>
<div class="blog_h2"><span class="graybg">添加Java插件</span></div>
<p>Gradle自带了很多插件，这些插件可以完成不同领域的构建任务。Gradle从一开始就很好的支持Java的构建，支持编译、打包、单元测试都构建动作。 </p>
<p>类似与Maven，Gradle的Java插件也是遵从“约定优于配置”的理念，因而Java插件生成了类似Maven的工程结构。</p>
<p>要添加Java插件的支持，在build.gradle中需要声明：<pre class="crayon-plain-tag">apply plugin: 'java'</pre> 。添加该插件后：</p>
<ol>
<li>自动设置src/main/java为Java源码目录，src的其它子目录布局和Maven一致</li>
<li>自动添加若干可用的Task，运行<pre class="crayon-plain-tag">gradle tasks</pre> 可以看到这些Task的详细说明</li>
</ol>
<p>下表列出Java插件提供的常用任务：</p>
<table class="fixed-word-wrap" style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 15%; text-align: center;">分类</td>
<td style="width: 22%; text-align: center;">任务</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td rowspan="5"> 生命周期任务</td>
<td>build</td>
<td>对工程进行完整构建</td>
</tr>
<tr>
<td>buildNeeded</td>
<td>对工程进行完整构建，同时构建<span style="background-color: #c0c0c0;">该工程依赖的</span>所有其它工程</td>
</tr>
<tr>
<td>buildDependents</td>
<td>对工程进行完整构建，同时构建所有<span style="background-color: #c0c0c0;">依赖于该工程</span>的其它工程</td>
</tr>
<tr>
<td>assemble</td>
<td>打印工程中的所有归档文件</td>
</tr>
<tr>
<td>check</td>
<td>执行所有验证任务。其他的插件会加入更多的检查步骤。其它插件会扩展这个任务的行为，例如Checkstyle插件会引发静态代码检查</td>
</tr>
<tr>
<td rowspan="3">源码集任务  </td>
<td>compileSourceSetJava</td>
<td>使用java编译指定源码集中的Java代码</td>
</tr>
<tr>
<td>processSourceSetResources</td>
<td>拷贝指定源码集中的资源文件到资源目录</td>
</tr>
<tr>
<td>sourceSetClasses</td>
<td>Assembles给定源码集中的类和资源文件</td>
</tr>
<tr>
<td rowspan="8">其它</td>
<td>clean</td>
<td>清空构建结果</td>
</tr>
<tr>
<td>compileJava</td>
<td>编译产品（不是测试）Java代码</td>
</tr>
<tr>
<td>processResources</td>
<td>处理产品（不是测试）资源文件</td>
</tr>
<tr>
<td>classes</td>
<td>Assembles产品类和资源文件</td>
</tr>
<tr>
<td>jar</td>
<td>打Jar包</td>
</tr>
<tr>
<td>javadoc</td>
<td>生成Javadoc</td>
</tr>
<tr>
<td>test</td>
<td>通过TestNG或者JUnit运行测试</td>
</tr>
<tr>
<td>uploadArchives</td>
<td>上传构件</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">工程布局</span></div>
<p>与Maven类似，默认有src/main|test/java|resourcs目录，其它源码集存放在：<pre class="crayon-plain-tag">src/sourceSetName/java</pre> 和<pre class="crayon-plain-tag">src/sourceSetName/resources</pre> 中。要改变此默认布局，可以：</p>
<pre class="crayon-plain-tag">sourceSets {
    main {
        java {
            srcDir 'src/java'
        }
        resources {
            srcDir 'src/resources'
        }
    }
}</pre>
<div class="blog_h2"><span class="graybg">添加外部依赖</span></div>
<p>Gradle维护一个和Maven Repository类似的依赖库，默认位于<pre class="crayon-plain-tag">%USERPROFILE%/.gradle/caches/</pre> 。我们可以指定外部仓库的位置，以便从中下载依赖库：</p>
<pre class="crayon-plain-tag">repositories {
    mavenCentral()
}</pre>
<p>使用下面的方式声明当前工程的依赖库：</p>
<pre class="crayon-plain-tag">dependencies {
    compile group: 'commons-collections', name: 'commons-collections', version: '3.2'
    testCompile group: 'junit', name: 'junit', version: '4.+'
}</pre>
<div class="blog_h2"><span class="graybg">设置工程属性</span></div>
<p>Java插件默认给工程添加了很多属性，通过命令<pre class="crayon-plain-tag">gradle properties</pre> 可以查看这些属性的值。你可以在build.gradle中修改属性：</p>
<pre class="crayon-plain-tag">sourceCompatibility = 1.5
version = '1.0'</pre>
<p>也可以在jar的manifest中添加属性：</p>
<pre class="crayon-plain-tag">jar {
    manifest {
        attributes 'Implementation-Title': 'Gradle Quickstart',
                   'Implementation-Version': version
    }
}</pre>
<p>或者在测试阶段添加一个系统属性：</p>
<pre class="crayon-plain-tag">test {
    systemProperties 'property': 'value'
}</pre>
<div class="blog_h2"><span class="graybg">发布构建结果</span></div>
<p>可以声明把构建结果发布到仓库中，下面示例了如何发布到本地目录：</p>
<pre class="crayon-plain-tag">uploadArchives {
    repositories {
       flatDir {
           dirs 'repos'
       }
    }
}</pre>
<div class="blog_h2"><span class="graybg">转换为Eclipse工程</span></div>
<p>添加插件：<pre class="crayon-plain-tag">apply plugin: 'eclipse'</pre> ，然后运行命令<pre class="crayon-plain-tag">gradle eclipse</pre> ，即可自动依据build.gradle生成对应的Eclipse工程元数据，可以导入到Eclipse工作区中。</p>
<div class="blog_h2"><span class="graybg">支持注解处理器</span></div>
<p>Annotation processing是Java 1.5引入的功能，允许在编译期进行代码检查、代码生成等操作。</p>
<p>要让Gradle能自动调用注解处理器，可以使用net.ltgt.apt插件：</p>
<pre class="crayon-plain-tag">buildscript {
  repositories {
    maven {
      url "https://plugins.gradle.org/m2/"
    }
  }
  dependencies {
    classpath "net.ltgt.gradle:gradle-apt-plugin:0.21"
  }
}

apply plugin: "net.ltgt.apt"</pre>
<p>然后，执行gradle build会调用注解处理器。</p>
<p>如果使用Intellij IDEA作为开发工具，可以将插件更换为：</p>
<pre class="crayon-plain-tag">apply plugin: "net.ltgt.apt-idea"</pre>
<p>这样，如果注解处理器生成了源代码，这些源代码的目录自动变为source root。</p>
<div class="blog_h2"><span class="graybg">可执行自包含JAR</span></div>
<pre class="crayon-plain-tag">task fatJar( type: Jar ) {
    manifest {
        attributes 'Implementation-Title': project.name,
                   'Implementation-Version': version,
                   'Main-Class': 'cc.gmem.yun.alcm.repository.source.GrpcServer'
    }
    baseName = project.name + '-all'
    from {
        configurations.compile.collect {
            it.isDirectory() ? it : zipTree( it ).matching {
                it.exclude(
                    'META-INF/*.SF', 'META-INF/*.DSA', 'META-INF/*.RSA', 'META-INF/DEPENDENCIES', 'META-INF/INDEX.LIST',
                    'META-INF/LICENSE*', 'META-INF/NOTICE*', 'META-INF/README*'
                )
            }
        }
    }
    with jar
} </pre>
<div class="blog_h1"><span class="graybg">Maven支持</span></div>
<div class="blog_h2"><span class="graybg">本地仓库</span></div>
<p>如果你希望把构件安装到Maven本地仓库，则需要此插件：</p>
<pre class="crayon-plain-tag">apply plugin: 'maven'</pre>
<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>install</td>
<td>
<p>安装构建的产出为Maven构件，并保存到本地仓库</p>
<p>可以使用系统属性指定本地仓库的位置：</p>
<pre class="crayon-plain-tag">-Dmaven.repo.local=the/path/of/the/folder</pre>
</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">远程仓库</span></div>
<p>需要使用到插件：
<pre class="crayon-plain-tag">apply plugin: 'maven-publish'</pre><br />
<pre class="crayon-plain-tag">publishing {
    publications {
        maven( MavenPublication ) {
            // 源码在哪
            from components.java
            // 发布JavaDocs和源码
            artifact javadocJar
            artifact sourcesJar

            // 编辑POM内容
            pom {
                name = project.group + ":" + project.name
                url = 'https://github.com/grpc/grpc-java'
                afterEvaluate {
                    description = project.description
                }

                scm {
                    connection = 'scm:git:https://github.com/grpc/grpc-java.git'
                    developerConnection = 'scm:git:git@github.com:grpc/grpc-java.git'
                    url = 'https://github.com/grpc/grpc-java'
                }

                licenses {
                    license {
                        name = 'Apache 2.0'
                        url = 'https://opensource.org/licenses/Apache-2.0'
                    }
                }

                developers {
                    developer {
                        id = "grpc.io"
                        name = "gRPC Contributors"
                        email = "grpc-io@googlegroups.com"
                        url = "https://grpc.io/"
                        organization = "gRPC Authors"
                        organizationUrl = "https://www.google.com"
                    }
                }

                withXml {
                    if ( !( project.name in
                        [
                            "grpc-stub",
                            "grpc-protobuf",
                            "grpc-protobuf-lite",
                        ] ) ) {
                        asNode().dependencies.'*'.findAll() { dep -&gt;
                            dep.artifactId.text() in [ 'grpc-api', 'grpc-core' ]
                        }.each() { core -&gt;
                            core.version*.value = "[" + core.version.text() + "]"
                        }
                    }
                }
            }
        }
    }
    // 远程仓库配置
    repositories {
        maven {
                credentials {
                    if ( rootProject.hasProperty( 'pacloudUsername' ) &amp;&amp; rootProject.hasProperty( 'pacloudPassword' ) ) {
                        username = rootProject.pacloudUsername
                        password = rootProject.pacloudPassword
                    }
                }
                def releaseUrl = 'https://nexus.pacloud.io/repository/maven-releases/'
                def snapshotUrl = 'https://nexus.pacloud.io/repository/maven-snapshots/'
                url = version.endsWith( 'SNAPSHOT' ) ? snapshotUrl : releaseUrl
            }
        }
    }
}</pre>
<p>执行下面的命令，完成打包和发布：</p>
<pre class="crayon-plain-tag">gradle sourceJar
gradle publishToMavenLocal</pre>
<div class="blog_h1"><span class="graybg">构建Groovy应用</span></div>
<p>Gradle提供了Groovy插件：</p>
<pre class="crayon-plain-tag">apply plugin: 'eclipse'
apply plugin: 'groovy'

repositories {
    mavenCentral()
}

dependencies {
    compile 'org.codehaus.groovy:groovy-all:2.3.7'
    testCompile 'junit:junit:4.11'
}</pre>
<p>该插件引入了以下任务： </p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">任务</td>
<td style="text-align: center;">依赖于</td>
<td style="text-align: center;">被依赖</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>compileGroovy</td>
<td>compileJava</td>
<td>classes</td>
<td>编译产品Groovy代码</td>
</tr>
<tr>
<td>compileTestGroovy</td>
<td>compileTestJava</td>
<td>testClasses</td>
<td>编译测试Groovy代码</td>
</tr>
<tr>
<td>compileSourceSetGroovy</td>
<td>compileSourceSetJava</td>
<td>sourceSetClasses</td>
<td>编译指定的Groovy源码集</td>
</tr>
<tr>
<td>groovydoc</td>
<td> </td>
<td> </td>
<td>生成Groovy文档</td>
</tr>
</tbody>
</table>
<p>Groovy工程的目录布局与Java工程类似，新引入src/main/groovy、src/test/groovy目录存放Groovy源码。</p>
<p>如果要改变默认工程目录布局，可以：</p>
<pre class="crayon-plain-tag">sourceSets {
    main {
        groovy {
            srcDirs = ['src/groovy']
        }
    }

    test {
        groovy {
            srcDirs = ['test/groovy']
        }
    }
}</pre>
<div class="blog_h1"><span class="graybg">构建Web应用</span></div>
<p>Gradle提供了两个和Web应用相关的插件：</p>
<ol>
<li>War插件：在Java插件的基础上进行扩展，支持构建war包 </li>
<li>Jetty插件：允许发布War到Jetty容器</li>
</ol>
<div class="blog_h2"><span class="graybg">使用War插件</span></div>
<p>要添加War插件，可以：</p>
<pre class="crayon-plain-tag">apply plugin: 'war'
//注意，该插件会自动引入Java插件</pre>
<p>运行<pre class="crayon-plain-tag">gradle build</pre>  可以编译、测试并打war包。 </p>
<p>War插件引入以下任务：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">任务</td>
<td style="text-align: center;">依赖于</td>
<td style="text-align: center;">被依赖</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>war</td>
<td>compile</td>
<td>assemble</td>
<td>打包Java的Web归档（war）</td>
</tr>
</tbody>
</table>
<p>Web资源，例如JavaScript、CSS、HTML、JSP等，默认存放到src/main/webapp目录中。</p>
<p>War插件引入了两个新的依赖配置：</p>
<table class="fixed-word-wrap" style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="width: 25%; text-align: center;">依赖配置</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>providedCompile</td>
<td rowspan="2">与compile、runtime类似，但是它们不会被打包到war中</td>
</tr>
<tr>
<td>providedRuntime</td>
</tr>
</tbody>
</table>
<p>下面的脚本示例了如何定制War插件的属性：</p>
<pre class="crayon-plain-tag">configurations {
   moreLibs
}

repositories {
   flatDir { dirs "lib" } //把lib目录作为依赖仓库
   mavenCentral()
}
dependencies {
    providedCompile "javax.servlet:servlet-api:2.5"
}

war {
    from 'src/rootContent' // 添加文件集到war中 
    webInf { from 'src/additionalWebInf' } // 添加文件夹到WEB-INF目录下
    classpath fileTree('additionalLibs') // 添加文件夹
    classpath configurations.moreLibs // 添加一个配置到WEB-INF/lib目录下
    webXml = file('src/someWeb.xml') // 拷贝文件为WEB-INF/web.xml
}</pre>
<div class="blog_h2"><span class="graybg">使用Jetty插件</span></div>
<p>Jetty插件继承自War插件，可以通过下面的脚本添加该插件：</p>
<pre class="crayon-plain-tag">apply plugin: 'jetty'</pre>
<p>运行<pre class="crayon-plain-tag">gradle jettyRunWar</pre> 可以打包并部署到Jetty中运行。 </p>
<p>Jetty插件引入以下任务：</p>
<table style="width: 100%;" border="1" cellspacing="0" cellpadding="5">
<thead>
<tr>
<td style="text-align: center;">任务</td>
<td style="text-align: center;">依赖于</td>
<td style="text-align: center;">被依赖</td>
<td style="text-align: center;">说明 </td>
</tr>
</thead>
<tbody>
<tr>
<td>jettyRun</td>
<td>compile</td>
<td> </td>
<td>运行Jetty实例，并把解开的构建目录部署上去</td>
</tr>
<tr>
<td>jettyRunWar</td>
<td>war</td>
<td> </td>
<td>运行Jetty实例，并把打包后的war部署上去</td>
</tr>
<tr>
<td>jettyStop</td>
<td> </td>
<td> </td>
<td>停止Jetty实例</td>
</tr>
</tbody>
</table>
<p>下面的脚本示例了如何定制Jetty插件的属性：</p>
<pre class="crayon-plain-tag">jetty {
    contextPath '上下文路径，默认war的名称'
    httpPort '监听端口，默认8080'
    stopPort '管理端口'
    stopKey  '要求Jetty停止时，传递给他的key'
}</pre>
<div class="blog_h1"><span class="graybg">构建Android应用</span></div>
<p>Gradle是当前Android官方IDE—— Android Studio内置的构建工具，你可以脱离AS，直接通过命令行调用Gradle，完成Android工程的构建。</p>
<p>通过编写构建脚本， 你可以配置Android构建的如下几个方面：</p>
<ol>
<li>构建变体（Build variants）：可以生成多个不同配置的APK，例如Debug/Release配置，又例如针对不同的体系结构（x86、arm）</li>
<li>Android库依赖</li>
<li>Manifest条目：可以在构建变体配置中<span style="background-color: #c0c0c0;">覆盖</span>AndroidManifest.xml中某些元素的取值。如果需要创建多个具有不同应用名称、最小SDK版本要求、目标SDK版本的APK，可以设置Manifest条目。如果有多个Manifest文件，会根据buildType、productFlavor、/main Manifest、library Manifest的优先顺序自动合并</li>
<li>签名：可以指定数字签名参数，在构建过程中对APK进行签名</li>
<li>ProGuard：允许配置ProGuard，对APK进行混淆、裁剪和优化</li>
</ol>
<div class="blog_h2"><span class="graybg">工程与模块</span></div>
<p>Android Studio<span style="background-color: #c0c0c0;">区分工程（Project）、模块（两个概念）</span>：</p>
<ol>
<li>工程：表示一个顶级的Android开发结构（相当于Gradle的根工程），一个工程中可以包含多个模块（相当于Gradle子工程）</li>
<li>模块：表示一个可以独立构建、测试、调试的组件</li>
</ol>
<p>AS支持以下类型的模块：</p>
<ol>
<li>Android Application Module：包含手机、电视 或手表的程序代码和资源，依赖于Library Module。构建系统为此类模块生成APK归档</li>
<li>Android Library Module：可重用的Android程序代码和资源。构建系统为此类模块生成AAR（Android ARchive）归档</li>
<li>App Engine Module：包含用于与Google App Engine集成的代码和资源</li>
<li>Java Library Module：包含可重用的Java代码。构建系统为此类模块生成JAR归档</li>
</ol>
<p>AS为工程和每个模块都生成Gradle构建脚本（build.gradle），这些脚本完全遵守Gradle的规范。</p>
<p>Android Studio工程包含了一个Gradle Wrapper，此Wrapper包含一个Gradle jar、一个属性文件、Windows/Linux的Shell脚本。使用Gradle Wrapper可以确保总是运行 <pre class="crayon-plain-tag">local.properties</pre> 中声明的Gradle版本。你可以修改此文件，以使用不同的Gradle版本。</p>
<div class="blog_h2"><span class="graybg">Hello World</span></div>
<div class="blog_h3"><span class="graybg">工程构建文件</span></div>
<p>可以指定Android工程的依赖，以及所有模块的公共依赖：</p>
<pre class="crayon-plain-tag">//构建本身的依赖配置
buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        //使用特定版本的Gradle
        classpath 'com.android.tools.build:gradle:1.0.1'
    }
}
//针对Android工程和所有模块的依赖配置
allprojects {
   repositories {
       jcenter()
   }
}</pre>
<div class="blog_h3"><span class="graybg">模块构建文件 </span></div>
<p>每个模块都可以有自己的构建脚本，要对模块启用Android构建的支持，必须应用android插件：</p>
<pre class="crayon-plain-tag">//注意，不得同时应用java插件，会导致构建错误
apply plugin: 'android'
//或者：
apply plugin: 'com.android.application'</pre>
<p>可以在构建脚本中覆盖src/main/AndroidManifest.xml中的设置，或者对打包选项进行定制：</p>
<pre class="crayon-plain-tag">android {
    //Android 基本设置
    compileSdkVersion 16
    buildToolsVersion "23.0.2"
    
    //覆盖AndroidManifest.xml中的manifest properties设置
    defaultConfig {
        applicationId "cc.gmem.androidstudy"
        minSdkVersion 15
        targetSdkVersion 16
        versionCode 1
        versionName "1.0"
    }
    //构建类型设置
    //默认情况下，Android插件同时创建两个构建类型：Debug、Release。两者的主要区别是：
    //Debug使用自动创建的密钥签名，而Release版本在构建时不签名
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        debug {
            debuggable true
            //设置ApplicationId，这样设备上可以同时安装Debug版本和Release版本的APK
            applicationIdSuffix ".debug"
        }
        //从debug衍生一个构建类型
        jnidebug {
            initWith(buildTypes.debug)
            packageNameSuffix ".jnidebug"
            jniDebuggable true
        }
    }
}
//声明一些依赖项
dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    testCompile 'junit:junit:4.12'
    compile 'com.android.support:appcompat-v7:16.+'
}</pre>
<p>对于每个新创建的构建类型，Android插件自动创建Task：<pre class="crayon-plain-tag">assemble&lt;BuildTypeName&gt;</pre>  </p>
<div class="blog_h2"><span class="graybg">工程结构</span></div>
<p>Android插件默认使用如下的工程结构：</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>src/main/java</td>
<td rowspan="2">
<p>主源码集</p>
<p>Android的Manifest文件位于：src/main/AndroidManifest.xml</p>
<p>其它子目录包括res、assets、aidl、rs、jni、jniLibs等</p>
</td>
</tr>
<tr>
<td>src/main/resources</td>
</tr>
<tr>
<td>src/androidTest/java</td>
<td rowspan="2">Android测试源码集</td>
</tr>
<tr>
<td>src/androidTest/resources</td>
</tr>
</tbody>
</table>
<p>如果默认工程结构不能满足需要，可以使用如下脚本定制：</p>
<pre class="crayon-plain-tag">android {
    sourceSets {
        //此闭包修改源码集的默认设置
        main {
            manifest.srcFile 'AndroidManifest.xml' //指定Manif文件名称
            //指定子目录位置
            java.srcDirs = ['src']
            resources.srcDirs = ['src']
            aidl.srcDirs = ['src']
            renderscript.srcDirs = ['src']
            res.srcDirs = ['res']
            assets.srcDirs = ['assets']
        }
        androidTest.setRoot('tests') //把整个androidTest源码集移动到tests目录
    }
}</pre>
<div class="blog_h2"><span class="graybg">构建任务</span></div>
<p>Android插件自动引入以下Task：</p>
<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="width: 30%; text-align: center;">备注</td>
</tr>
</thead>
<tbody>
<tr>
<td>assemble</td>
<td>收集工程的输出文件并打包。对于Android工程，至少输出两个APK包：Debug/Release，它们分别由任务assembleDebug、assembleRelease完成。assemble依赖于这两个任务</td>
<td rowspan="3">
<p>和Java插件一样，Android引入这三个一般性Task。这些Task本身不做实质性的工作，它们只是作为anchor，把工作委托给其它Task执行</p>
<p>不同插件使用风格一致的一般任务命名约定，可以简化构建</p>
</td>
</tr>
<tr>
<td>check</td>
<td>运行所有检查。Android插件支持基于lint的静态检查</td>
</tr>
<tr>
<td>build</td>
<td>同时执行assemble和check</td>
</tr>
<tr>
<td>clean</td>
<td colspan="2">清除工程输出 </td>
</tr>
<tr>
<td>connectedCheck</td>
<td colspan="2">检查是否有连接上的设备或者模拟器  </td>
</tr>
<tr>
<td>deviceCheck</td>
<td colspan="2">检查设备是否支持API </td>
</tr>
<tr>
<td>installDebug</td>
<td colspan="2">安装Debug版本的APK到设备</td>
</tr>
<tr>
<td>installRelease</td>
<td colspan="2">安装Release版本的APK到设备</td>
</tr>
<tr>
<td>uninstallAll</td>
<td colspan="2">从设备上卸载所有版本的APK，依赖于：uninstallDebug、uninstallRelease、uninstallDebugAndroidTest</td>
</tr>
</tbody>
</table>
<div class="blog_h2"><span class="graybg">配置APK签名</span></div>
<p>默认的，Debug构建的keystore存放在<pre class="crayon-plain-tag">$HOME/.android/debug.keystore</pre> ，如果不存在会自动创建。下面的脚本块示例了如何创建新的签名配置（signingConfigs ）并且将其应用到某个构建类型：</p>
<pre class="crayon-plain-tag">android {
    signingConfigs {
        debug {
            storeFile file("debug.keystore")
        }
        //创建新的签名配置
        myConfig {
            storeFile file("other.keystore")
            storePassword "android"
            keyAlias "androiddebugkey"
            keyPassword "android"
        }
    }

    buildTypes {
        foo {
            //应用签名配置
            signingConfig signingConfigs.myConfig
        }
    }
}</pre>
</div><p>The post <a rel="nofollow" href="https://blog.gmem.cc/gradle-study-note">Gradle学习笔记</a> appeared first on <a rel="nofollow" href="https://blog.gmem.cc">绿色记忆</a>.</p>
]]></content:encoded>
			<wfw:commentRss>https://blog.gmem.cc/gradle-study-note/feed</wfw:commentRss>
		<slash:comments>2</slash:comments>
		</item>
	</channel>
</rss>
