前言: |
前段时间不才翻译了关于Sencha Touch 2 MVC架构的一系列文章,大家的认可让我备受鼓舞,也坚定了继续分享自己学习Sencha Touch(下文简称ST)过程和经验的决心。
在跟大家交流的过程中,发现很多入门者反映,学习ST最大的障碍有两个:1、官方文档跟进速度不够;2、官方sample集成程度较高,典型性不足。
我认同大家的观点:由于前期ST2版本更新极快,一个多月的时间从Beta到RC再到正式版,这期间小变动相当多,开发组的疯狂精神固然值得钦佩,可是截至目前为止,仍有一些地方未作详尽说明,而这对于刚接触ST的朋友来讲的确很困难。关于官方提供的sample集成度过高的问题,我觉得对从未接触过Ext产品系的朋友来说,这一点也的确棘手。
所以这一阶段我的打算是通过demo的方式,为刚开始学习的朋友们提供一些更具有典型性的实例,同时也尝试对官方说明不够的地方进行一下讲解,希望对大家有帮助。
Sencha Touch 交流 QQ 群 213119459 欢迎您的加入。
本节目标:加载 ST2 到你的网页,搭建运行环境 |
这一节的内容其实刚好可以对应官方文档的《Getting Started with Sencha Touch 2》http://docs.sencha.com/touch/2-0/#!/guide/getting_started
为什么我前面一直没有翻译这一章节,原因也在这里,对于ST2来讲,官方的这篇文档显然缺乏诚意,虽然它可以指点你写出一个hello world,但是对ST2运行环境的搭建细节说明不够,刚好今天我可以用一个完整的Demo来做一个详细说明。
ST1的时候,搭建基于ST的网页运行环境的确非常简单,如官方这篇文档所述已经足够(这也是我认为诚意不够的原因,为嘛不紧跟ST2的步伐呢)。
但是从ST2 Beta版开始,开发组提供了sencha-touch-all.js和sencha-touch.js两个js文件,其目的是允许开发人员选择ST框架是采用“一次性加载全部”的方式(简称all方式)还是“首次运行只加载核心代码,随后按需加载”的方式(简称core方式),在ST2的最终发布版中,core方式只有91k,而all方式有568k,这还都是压缩后的大小,这样的尺寸对比在mobile设备上意味着用户体验可能出现巨大差别,因此根据自身需求来选择加载模式还是很有必要的。
其实ST2的正式版发布后,很多人就开始头疼,因为大家发现下载的sdk包解压缩后,里面的例子无法直接双击在Chrome里运行,这还不算完,ST官方网站上提供的sample源码竟然也变得失去了可读性,没有了css文件的引入,没有了app.js的引入,只是html页面中被加入了一段压缩混淆后的js代码,很多人想不通,其实我当时也想不通:-)。
事情的真相是,正式版使用的那段混淆脚本叫做microloader,它通过异步加载一个app.json文件(这个文件也是正式版才出现的)来读取对运行环境的初始化设定,然后根据app.json中的设定再去加载相应的css和js文件,甚至于设定app缓存等等(还有很多至今搞不清楚的设定),也就是说,采用此种方式,要调用的js和css文件被隐藏了,显而易见,这种方式非常有助于保护你的源码,当然也不是绝对安全的js源码保护方式,但至少还是比什么都不做要强得多。
于是,ST2的正式版最终得以支持两种模式下的四种手段来实现ST2框架文件的加载。而这也是本节的demo所要说明的。
开发环境说明 |
ST的开发说到底还是web开发,因此它对开发环境和开发工具其实并不挑剔,小到notepad,中到notepad++/editplus,大到eclipse/visual studio都可以,当然如果考虑到方便性的话,最好还是有自动格式、关键字高亮、自动补齐功能,但无论怎样,目前似乎还没有可以像Aptana通过插件来支持jQuery那样完全支持ST的开发工具,我们期盼以后eclipse或者visual studio出现相应的插件吧。
对于普通开发者而言,我推荐notepad++,轻便的同时功能还算强大,如果你不怕块头大,Aptana和eclipse也是不错的选择。不过我用的是Visual Studio 2010,因为电脑上就有,无需安装了。
Demo代码完整结构 |
下面对上述结构做一详细说明:
最顶级是Demo和SenchaTouch两个文件夹,分别用来放置Demo程序代码和ST框架代码:
SenchaTouch文件夹下面的2.0.0文件夹表明当前ST框架的版本号,再下面就是ST2框架的所有相关资源文件;
Demo文件夹下就是我们此次编写的全部代码文件:
Demo所实现的效果:文本框与Picker结合实例 |
虽然本文目的是分别演示不同模式不同手段来加载ST2所需框架,但页面上若只写一个干巴巴的Hello World还是有些对不起观众,所以这里我实现了一个比较有实际使用价值的效果,那就是一个不可编辑文本框,该文本框的值只能通过一个Picker来选择。效果图如下:
下面开始一一介绍四种加载方式:
A.直接加载完整框架文件(all方式) |
这种加载方式跟ST1开始的方式是一样的,即在html页面中明确引入sencha-touch的完整框架js文件、css文件以及应用程序代码。
sencha-touch-all.html代码:
晕死!博客园将我的相对路径都自动转成了站内绝对路径,请自行将后面所有代码中的"http://www.cnblogs.com/"改成"。。/。。/"(。换成.)
<!DOCTYPE html>
<html>
<head>
<title>Hello Mobile</title>
<link rel="stylesheet" href="http://www.cnblogs.com/SenchaTouch/2.0.0/css/sencha-touch.css" type="text/css">
<script type="text/javascript" src="http://www.cnblogs.com/SenchaTouch/2.0.0/sencha-touch-all-debug.js"></script>
<script type="text/javascript" src="app-all.js"></script>
</head>
<body></body>
</html>
app-all.js代码(代码逻辑很简单,看注释即可):
Ext.application({
name: 'Demo',
viewport: {
autoMaximize: true // 该属性可以设置页面自动最大化(隐藏地址栏)
},
launch: function () {
Ext.create("Ext.Panel", {
fullscreen: true,
items: [
{
xtype: 'fieldset',
margin: 10,
title: '文本框与Picker结合实例',
items: [
{
xtype: 'textfield',
name: 'aTextField',
id: 'aTextField',
readOnly: true, // 把文本框设为只读,禁止输入
label: '取值结果',
clearIcon: true,
listeners: {
// 侦听文本框的focus事件,获取到焦点时触发
focus: function () {
this.disable(); // 先禁用文本框,防止系统调出软键盘
Ext.getCmp('aPicker').show(); // 然后显示用来选择内容的Picker
}
}
}
]
}
]
});
// 定义一个Picker供文本框联动
aPicker = Ext.create('Ext.Picker', {
name: 'aPicker',
id: 'aPicker',
hidden: true,
listeners: {
// 侦听change事件,Picker的值改变同时也设定文本框的值
change: function () {
Ext.getCmp('aTextField').setValue(aPicker.getValue().question);
},
// 侦听hide事件,当Picker消失时将文本框状态恢复为enable
hide: function () {
Ext.getCmp('aTextField').enable();
}
},
slots: [
{
name: 'question',
data: [
{
text: '无',
value: ''
},
{
text: '最喜欢的颜色',
value: 'color'
},
{
text: '最喜欢的运动',
value: 'sport'
},
{
text: '最喜欢的明星',
value: 'star'
}
]
}
]
});
// 前面定义的Picker控件必须显式加入Viewport,否则无法被调用显示
// Ext.Viewport是Sencha Touch自动创建的一个顶级容器
Ext.Viewport.add(aPicker);
}
});
B.先加载核心框架文件,然后按需加载其他组件(core方式) |
此种加载方式是ST2新增的,他为我们提供了更多地选择,避免每个app都要加载巨大的sencha-touch-all.js。
不过具体使用的时候你会发现,这种core方式虽然相比all方式需要下载的文件小了,但同样存在一些弊端:
所以最终选择哪中方式,还要经过你自己仔细测算和评估自身需求而定。
sencha-touch-core.html代码(相比A所采用的all方式,仅仅改变了对应用程序代码文件(app-core.js取代了app-all.js)和框架js文件(sencha-touch-debug.js文件取代sencha-touch-all-debug.js文件)的引用:
<!DOCTYPE html>
<html>
<head>
<title>Hello Mobile</title>
<link rel="stylesheet" href="http://www.cnblogs.com/SenchaTouch/2.0.0/css/sencha-touch.css" type="text/css">
<script type="text/javascript" src="http://www.cnblogs.com/SenchaTouch/2.0.0/sencha-touch-all-debug.js"></script>
<script type="text/javascript" src="app-core.js"></script>
</head>
<body></body>
</html>
app-core.js代码(着重观察开始的部分,后面的代码与all方式没有区别):
// 第一件事就是给src这个源文件目录指定一个命名空间(具体参见官方文档翻译中的External Dependencies,即外部依赖)
Ext.Loader.setPath({
'Ext': 'http://www.cnblogs.com/SenchaTouch/2.0.0/src'
});
// 然后根据application用到的控件来添加对以下组件的引用(有人说Ext.TitleBar没用到啊,其实用到了,在Picker里面)
// 大家请注意,其实这里的Ext命名空间指的就是上面Ext.Loader.setPath方法中指定的外部依赖命名空间
Ext.require(['Ext.form.FieldSet', 'Ext.picker.Picker', 'Ext.TitleBar']);
// 下面的代码部分跟all方式完全保持了一致,故省略……
Ext.application({
name: 'Demo',
……
注意:A和B两种方式由于对js和css文件采用了相对路径的直接引用,所以直接在资源管理器双击运行就可以看到效果。
C.通过Microloader加载ST框架(Code-behind方式) |
前文说过,Microloader是ST2正式版加入的新功能,通过这种模式加载ST2框架的话,在Chrome浏览器里无法直接查看效果,Safari可以。
采用Microloader模式又可以细分为Code-Inline和Code-behind两种方式,Code-inline指的是把js脚本直接写在html页面里,Code-behind指的是链接外部js文件来实现。先看第一种(这也是官方sdk例子当中采用的方式):
JsCodeBehind.html代码(简单的不能再简单了,只引用了microloader文件夹下的development.js文件,经测试,其他两个文件均无效,注意这里给script加了一个id):
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>Microloader</title>
<script id="microloader" type="text/javascript" src="http://www.cnblogs.com/SenchaTouch/2.0.0/microloader/development.js"></script>
</head>
<body>
</body>
</html>
我们知道这个microloader的功能是加载并读取app.json文件里的设置,那么当然有必要看一下app.json代码:
{
"name": "Demo", // app名称
"js": [
{
"path": "http://www.cnblogs.com/SenchaTouch/2.0.0/sencha-touch-all-debug.js" // 引用的ST框架文件,这里我们选用all模式
},
{
"path": "app.js", // 引用的应用程序代码文件
"update": "delta"
}
],
"css": [
{
"path": "http://www.cnblogs.com/SenchaTouch/2.0.0/css/sencha-touch.css", // 引用的css文件
"update": "delta"
}
],
"appCache": {
"cache": [
"JsCodeBehind.html",
"JsCodeInline.html"
],
"network": [
"*"
],
"fallback": []
},
"extras": [
],
"archivePath": "archive",
"buildPaths": {
"testing": "http://www.cnblogs.com/deploy/testing/Demo",
"production": "http://www.cnblogs.com/deploy/production/Demo",
"package": "http://www.cnblogs.com/deploy/package/Demo",
"native": "http://www.cnblogs.com/deploy/native/Demo"
},
"buildOptions": {
"product": "touch",
"minVersion": 3,
"debug": false,
"logger": "no"
},
"id": "3a867610-670a-11e1-a90e-4318029d18bb"
}
由于官方尚未对app.json文件中的其他配置做详细说明,因此我们在这里只关注前面3个配置,这些已经足够我们正常运行ST应用程序了。为了方便起见,该文件中指定了调用的ST框架文件是sencha-touch-all-debug.js(all方式),应用程序代码文件是app.js,它的代码跟A中的没有任何区别,这里就不再贴了。
D.通过Microloader加载ST框架(Code-inline方式) |
Code-inline方式,顾名思义,把js代码直接写在html文件里,这是目前ST官网实例中所采用的方式(您可能已经注意到了,官方的例子与提供下载的sdk包中同一个例子所采取的方式并不相同)!
我们先随便找一个官网部署的例子来看一下它的代码(这里选择的是正式版新增的例子TouchStylehttp://dev.sencha.com/deploy/touch/examples/production/touchstyle/index.html):
<!DOCTYPE HTML>
<html manifest="cache.manifest" lang="en-US">
<head>
<meta charset="UTF-8">
<title>TouchStyle</title>
<script type="text/javascript">(function(n){function r(a){function b(a,j){var c=a.length,b,e;for(b=0;b<c;b++){e=a[b];var d=a,J=b,k=void 0;"string"==typeof e&&(e={path:e});e.shared?(e.version=e.shared,k=e.shared+e.path):(y.href=e.path,k=y.href);e.uri=k;e.key=g+"-"+k;f[k]=e;d[J]=e;e.type=j;e.index=b;e.collection=a;e.ready=!1;e.evaluated=!1}return a}var c;"string"==typeof a?(c=a,a=z(c)):c=JSON.stringify(a);var g=a.id,d=g+"-"+A+o,f={};this.key=d;this.css=b(a.css,"css");this.js=b(a.js,"js");this.assets=this.css.concat(this.js);this.getAsset=
function(a){return f[a]};this.store=function(){s(d,c)}}function v(a,b){i.write('<meta name="'+a+'" content="'+b+'">')}function p(a,b,c){var g=new XMLHttpRequest,d,c=c||B;try{g.open("GET",a,!0),g.onreadystatechange=function(){4==g.readyState&&(d=g.status,200==d||304==d||0==d?b(g.responseText):c())},g.send(null)}catch(f){c()}}function K(a,b){var c=i.createElement("iframe");m.push({iframe:c,callback:b});c.src=a+".html";c.style.cssText="width:0;height:0;border:0;position:absolute;z-index:-999;visibility:hidden";
i.body.appendChild(c)}function C(a,b,c){var g=!!a.shared;if(!g)var d=b,f=a.version,h,b=function(j){h=j.substring(0,f.length+4);h!=="/*"+f+"*/"?confirm("Requested: '"+a.uri+"' with checksum: "+f+" but received: "+h.substring(2,f.length)+"instead. Attemp to refresh the application?")&&L():d(j)};(g?K:p)(a.uri,b,c)}function D(a){var b=a.data,a=a.source.window,c,g,d,f;for(c=0,g=m.length;c<g;c++)if(d=m[c],f=d.iframe,f.contentWindow===a){d.callback(b);i.body.removeChild(f);m.splice(c,1);break}}function E(a){"undefined"!=
typeof console&&(console.error||console.log).call(console,a)}function s(a,b){try{l.setItem(a,b)}catch(c){if(c.code==c.QUOTA_EXCEEDED_ERR){var g=t.assets.map(function(a){return a.key}),d=0,f=l.length,h=!1,j;for(g.push(t.key);d<=f-1;)j=l.key(d),-1==g.indexOf(j)?(l.removeItem(j),h=!0,f--):d++;h&&s(a,b)}}}function u(a){try{return l.getItem(a)}catch(b){return null}}function L(){F||(F=!0,p(o,function(a){(new r(a)).store();n.location.reload()}))}function G(a){function b(a,b){var d=a.collection,e=a.index,
g=d.length,f;a.ready=!0;a.content=b;for(f=e-1;0<=f;f--)if(a=d[f],!a.ready||!a.evaluated)return;for(f=e;f<g;f++)if(a=d[f],a.ready)a.evaluated||c(a);else break}function c(a){a.evaluated=!0;if("js"==a.type)try{eval(a.content)}catch(b){E("Error evaluating "+a.uri+" with message: "+b)}else{var c=i.createElement("style"),e;c.type="text/css";c.textContent=a.content;"id"in a&&(c.id=a.id);"disabled"in a&&(c.disabled=a.disabled);e=document.createElement("base");e.href=a.path.replace(/\/[^\/]*$/,"/");w.appendChild(e);
w.appendChild(c);w.removeChild(e)}delete a.content;0==--f&&g()}function g(){function c(){d&&b()}function b(){var a=q.onUpdated||B;if("onSetup"in q)q.onSetup(a);else a()}function f(){l.store();e.forEach(function(a){s(a.key,a.content)});b()}var e=[],d=!1,g=!1,k=function(){g=!0},i=function(){h.swapCache();d=!0;k()},m;n.removeEventListener("message",D,!1);h.status==h.UPDATEREADY?i():h.status==h.CHECKING||h.status==h.DOWNLOADING?(h.onupdateready=i,h.onnoupdate=h.onobsolete=k):k();!1!==navigator.onLine&&
p(o,function(b){t=l=new r(b);var d;l.assets.forEach(function(b){d=a.getAsset(b.uri);(!d||b.version!==d.version)&&e.push(b)});m=e.length;0==m?g?c():k=c:e.forEach(function(b){function c(){C(b,function(a){b.content=a;0==--m&&(g?f():k=f)})}var d=a.getAsset(b.uri),e=b.path,h=b.update;!d||!h||null===u(b.key)||"delta"!=h?c():p("deltas/"+e+"/"+d.version+".json",function(a){try{var c=b,d;var e=u(b.key),h=z(a),a=[],j,i,l;if(0===h.length)d=e;else{for(i=0,l=h.length;i<l;i++)j=h[i],"number"===typeof j?a.push(e.substring(j,
j+h[++i])):a.push(j);d=a.join("")}c.content=d;0==--m&&(g?f():k=f)}catch(n){E("Malformed delta content received for "+b.uri)}},c)})})}var d=a.assets,f=d.length,l;t=a;H("message",D,!1);0==f?g():d.forEach(function(a){var c=u(a.key);null===c?C(a,function(c){s(a.key,c);b(a,c)},function(){b(a,"")}):b(a,c)})}function I(a){null!==i.readyState.match(/interactive|complete|loaded/)?G(a):H("DOMContentLoaded",function(){G(a)},!1)}var B=function(){},m=[],i=n.document,w=i.head,H=n.addEventListener,l=n.localStorage,
h=n.applicationCache,z=JSON.parse,y=i.createElement("a"),x=i.location,A=x.origin+x.pathname+x.search,o="app.json",F=!1,t;if("undefined"===typeof q)var q=n.Ext={};q.blink=function(a){var b=u(a.id+"-"+A+o);v("viewport","width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no");v("apple-mobile-web-app-capable","yes");v("apple-touch-fullscreen","yes");b?(a=new r(b),I(a)):p(o,function(b){a=new r(b);a.store();I(a)})}})(this);
;Ext.blink({"id":"3a867610-670a-11e1-a90e-4318029d18bb"})</script>
</head>
<body>
</body>
</html>
虽然只是经过了简单混淆,可还是看得人很凌乱,不过我们可以轻易地找到app.json这个字符串。
对比之后,你会发现官网部署的几乎所有例子都采用了这一段代码,而且没有一点的区别。那么理论上这段代码就应该是通用的,所以我尝试在自己的页面中同样拷贝加入这一段代码,但可悲的事实告诉我NO!想不通啊想不通……
当然活人不能被尿憋死,这段代码不行我就把microloader文件夹下三个js文件中的代码依次拷贝过来测试,最后只有development.js这个文件中的代码有效(因为Code-behind模式就只有它能行,所以这个结果是显而易见的),于是最终的JsCodeInline.html代码如下:
<!DOCTYPE HTML>
<html>
<head>
<meta charset="UTF-8">
<title>Microloader</title>
<script type="text/javascript">
(function () {
function write(content) {
document.write(content);
}
function meta(name, content) {
write('<meta name="' + name + '" content="' + content + '">');
}
var xhr = new XMLHttpRequest();
xhr.open('GET', 'app.json', false);
xhr.send(null);
var options = eval("(" + xhr.responseText + ")"), scripts = options.js || [], styleSheets = options.css || [], i, ln, path;
meta('viewport', 'width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0, user-scalable=no');
meta('apple-mobile-web-app-capable', 'yes');
meta('apple-touch-fullscreen', 'yes');
for (i = 0, ln = styleSheets.length; i < ln; i++) {
path = styleSheets[i];
if (typeof path != 'string') {
path = path.path;
}
write('<link rel="stylesheet" href="' + path + '">');
}
for (i = 0, ln = scripts.length; i < ln; i++) {
path = scripts[i];
if (typeof path != 'string') {
path = path.path;
}
write('<script src="' + path + '"></' + 'script>');
}
})();
</script>
</head>
<body>
</body>
</html>
当然了,app.json和app.js文件没有任何不同,最终测试效果也完全一致。
结语: |
到这里,我们完整解析了全部的ST2框架加载模式和手段,希望通过这篇文章可以帮助你理解ST2带来的新功能。
为了方便大家自己调试,在这里奉上此Demo的全部源码:猛击这里下载
注意:此系列文章属博主原创,转载请注明作者信息和原始链接,谢谢合作。
Sencha Touch 交流 QQ 群 213119459 欢迎您的加入。