筋斗云移动UI框架 - JDCloud Mobile UI framework
亦称“变脸式应用”。应用程序以逻辑页面(page)为基本单位,每个页面的html/js可完全分离。主要特性:
@see showPage
@see popPageStack
@key .mui-container 应用容器。
@event muiInit () DOM事件。this为当前应用容器。
先在主应用html中,用.mui-container类标识应用容器,在运行时,所有逻辑页面都将在该对象之下。如:
<body class="mui-container">
应用初始化时会发出muiInit事件,该事件在页面加载完成($.ready)后,显示首页前调用。在这里调用MUI.showPage可动态显示首页。
每个逻辑页面(page)以及它对应的脚本(js)均可以独立出一个文件开发,也可以直接嵌在主页面的应用容器中。
@key .mui-page 逻辑页面。
@key mui-script DOM属性。逻辑页面对应的JS文件。
@key mui-initfn DOM属性。逻辑页面对应的初始化函数,一般包含在mui-script指定的JS文件中。
如添加一个订单页,使用外部页面,可以添加一个order.html (html片段):
<div mui-initfn="initPageOrder" mui-script="order.js">
...
</div>
如果使用内部页面,则可以写为:
<script type="text/html" id="tpl_order">
<div mui-initfn="initPageOrder" mui-script="order.js">
...
</div>
</script>
@key .hd 页面顶栏
@key .bd 页面主体
@key .ft 页面底栏
@key .btn-icon 顶栏图标按钮
页面中常常包含hd, bd等结构,如
<div mui-initfn="initPageMe">
<div class="hd">
<a href="javascript:hd_back();" class="btn-icon"><i class="icon icon-back"></i></a>
<h2>个人信息</h2>
</div>
<div class="bd">
this is the body
</div>
<div class="ft">
this is the footer
</div>
</div>
(v5.2)
hd和ft可以有多个, bd只能有一个。框架将自动为它们计算和设置位置。
hd一般用于显示页面标题、返回和菜单。在hd中出现的第一个h1或h2标签的文字将自动设置为当前文档标题。
在微信中(如公众号或小程序),第一个hd会自动隐藏,以避免与微信的标题栏重复。
app.css中定义了btn-icon
为顶栏图标按钮类,如果在hd
中有多个btn-icon
,则依次为左一,右一,左二,右二按钮,例如:
<div class="hd">
<!-- 左一: 返回按钮 -->
<a href="javascript:hd_back();" class="btn-icon"><i class="icon icon-back"></i></a>
<!-- 右一: 更多选项按钮 -->
<a href="#dlgMenu" class="btn-icon"><i class="icon icon-menu"></i></a>
<!-- 左二: 无 -->
<a />
<!-- 右二: 编辑按钮 -->
<a class="btn-icon"><i class="icon icon-edit"></i></a>
<h2>标题</h2>
</div>
该页面代码模块(即初始化函数)可以放在一个单独的文件order.js:
function initPageOrder()
{
var jpage = this;
jpage.on("pagebeforeshow", onBeforeShow);
jpage.on("pageshow", onShow);
jpage.on("pagehide", onHide);
...
}
逻辑页面加载过程,以加载页面"#order"为例:
MUI.showPage("#order");
MUI.options.pageFolder
指定。(v3.3)页面初始化函数可返回一个新的jpage对象,从而便于与vue等库整合,如:
function initPageOrder()
{
// vue将this当作模板,创建新的DOM对象vm.$el.
var vm = new Vue({
el: this[0],
data: {},
method: {}
});
var jpage = $(vm.$el);
jpage.on("pagebeforeshow", onBeforeShow);
...
return jpage;
}
@event pagecreate (ev) DOM事件。this为当前页面,习惯名为jpage。
@event pagebeforeshow (ev, opt) DOM事件。this为当前页面。opt参数为`MUI.showPage(pageRef, opt?)`中的opt,如未指定则为`{}`。(v5.4) 设置backNoRefresh选项会忽略此事件,这时可用pagebeforeshow.always替代。
@event pageshow (ev, opt) DOM事件。this为当前页面。opt参数与pagebeforeshow事件的opt参数一样。
@event pagehide (ev) DOM事件。this为当前页面。
逻辑页代码片段允许嵌入style,例如:
<div mui-initfn="initPageOrder" mui-script="order.js">
<style>
.p-list {
color: blue;
}
.p-list div {
color: red;
}
</style>
</div>
@key mui-origin
style将被插入到head标签中,并自动添加属性mui-origin={pageId}
.
(版本v3.2)
框架在加载页面时,会将style中的内容自动添加逻辑页前缀,以便样式局限于当前页使用,相当于:
<style>
#order .p-list {
color: blue;
}
#order .p-list div {
color: red;
}
</style>
为兼容旧版本,如果css选择器以"#{pageId} "开头,则不予处理。
@key mui-nofix
如果不希望框架自动处理,可以为style添加属性mui-nofix
:
<style mui-nofix>
</style>
逻辑页中允许但不建议内嵌script代码,js代码应在mui-script对应的脚本中。非要使用时,注意将script放到div标签内:
<div mui-initfn="initPageOrder" mui-script="order.js">
<script>
// js代码
</script>
...
</div>
(版本v3.2)
如果逻辑页嵌入在script模板中,这时要使用script
, 应换用__script__
标签,如:
<script type="text/html" id="tpl_order">
<div mui-initfn="initPageOrder" mui-script="order.js">
...
</div>
<__script__>
// js代码,将在逻辑页加载时执行
</__script__>
</script>
默认进入应用时的主页为 MUI.options.homePage. 如果要根据参数动态显示页面,可在muiInit事件中操作,示例:
$(document).on("muiInit", myInit);
function myInit()
{
if (g_args.initPage) {
MUI.showPage(g_args.initPage);
}
}
访问http://server/app/?initPage=me
则默认访问页面"#me".
@see muiInit
例如,进入页面后,发现如果未登录,则自动转向登录页:
function onPageBeforeShow(ev)
{
// 登录成功后一般会设置g_data.userInfo, 如果未设置,则当作未登录
if (g_data.userInfo == null) {
MUI.showLogin();
return;
}
// 显示该页面...
}
在pagebeforeshow事件中做页面切换,框架保证不会产生闪烁,且在新页面上点返回按钮,不会返回到旧页面。
(v5.4) 如果想在页面加载前添加处理逻辑,请参考 MUI.options.onShowPage 回调,可处理检测是否登录这类需求。
除此之外如果多次调用showPage(包括在pageshow事件中调用),一般最终显示的是最后一次调用的页面,过程中可能产生闪烁,且可能会丢失一些pageshow/pagehide事件,应尽量避免。
@key mui-deferred
(版本v4.2)
如果逻辑页依赖某一个或多个库,这些库不想在主页面中用script默认加载,这时可以使用mui-deferred
属性。
逻辑页初始化函数mui-initfn将在该deferred对象操作成功后执行。
示例:某逻辑页依赖百度地图的js库,该js库使用动态加载:
// 主逻辑中定义返回Deferred对象
window.dfdBaiduMap = MUI.loadScript("http://api.map.baidu.com/getscript?v=2.0&ak=YOUR-APP-KEY");
// map.html 逻辑页中声明依赖该对象
<div mui-initfn="initPageMap" mui-script="map.js" mui-deferred="dfdBaiduMap">
...
</div>
// map.js
function initPageMap()
{
var jpage = this;
// 这时可以安全依赖库的对象,如BMap对象
}
如果不使用mui-deferred属性,则需要在initPageMap中小心的来写异步逻辑,比如:
// map.js
function initPageMap()
{
var jpage = this;
// 可以安全使用BMap对象
dfdBaiduMap.then(init);
function init() { ... }
}
一般会将加载依赖库包装成一个函数,比如要使用百度echarts显示统计图的页面,可定义函数:
var dfdStatLib_;
function loadStatLib()
{
if (dfdStatLib_ == null) {
dfdStatLib_ = $.when(
MUI.loadScript("../web/lib/echarts.min.js"),
MUI.loadScript("../web/lib/jdcloud-wui-stat.js")
);
}
return dfdStatLib_;
}
依赖echarts的页面,可以设置:
<div mui-deferred="loadStatLib()">
...
</div>
框架支持hash路由(默认方式)和文件路由(逻辑页面文件)两种方式。
默认hash路由:
http://server/app/index.html#order
对应{pageFolder=page}/order.html
,一般为page/order.html
http://server/app/index.html#order-list
对应page/order/list.html
http://server/app/index.html#order-list
在存在插件'order'时,对应{pluginFolder=../plugin}/order/m2/page/list.html
,一般为../plugin/order/m2/page/list.html
文件路由:在主页面中head标签中应添加:
<base href="./" mui-showHash="no">
之后,上面两个例子中,URL会显示为 http://server/app/page/order.html
和 http://server/app/page/order/list.html
@see options.showHash
注意:使用文件路由时,如果刷新页面将无法显示。必须在web服务器中设置URL重写规则来解决。apache请参考和修改m2/page/.htaccess文件,nginx请参考和修改m2/.ht.nginx文件。
特别地,还可以通过MUI.setUrl(url)
或MUI.showPage(pageRef, {url: url})
来定制URL,例如将订单id=100的逻辑页显示为RESTful风格:http://server/app/order/100
@see setUrl
为了刷新时仍能正常显示页面,应将页面设置为入口页,并在WEB服务器配置好URL重写规则。
@see callSvr 系列调用服务端接口的方法。
框架提供MUI.showLogin/MUI.logout操作.
调用MUI.tryAutoLogin可以支持自动登录.
登录后显示的主页,登录页,应用名称等应通过MUI.options.homePage/loginPage/appName等选项设置。
@see tryAutoLogin
@see showLogin
@see logout
@see options
框架提供导航栏、对话框、弹出框、弹出菜单等常用组件。
@key .mui-navbar 导航栏,Tab页
@key .mui-navbar.noactive
默认行为是点击后添加active类(比如字体发生变化),如果不需要此行为,可再添加noactive类。
示例:
<div class="mui-navbar">
<a mui-linkto="#lst1">待服务</a>
<a mui-linkto="#lst2">已完成</a>
</div>
@key .mui-dialog 对话框
对话框与页面(.mui-page)类似,可以包含hd, bd等部分。
它一般包含在一个页面中,id以"dlg"开头。示例:
<div id="dlgAddPerson" class="mui-dialog">
<div class="hd">
<h2>添加人物</h2>
</div>
<div class="bd weui-cells">
<div class="weui-cell weui-cell_access">
<label class="weui-cell_hd weui-label">添加:</label>
<select id="cboRelation" class="weui-cell__bd weui-select right" style="min-width:90px">
<option value="parent">父亲</option>
<option value="child">子女</option>
</select>
<div class="weui-cell__ft"></div>
</div>
</div>
<div class="ft">
<a id="btnCancel" class="mui-btn">取消</a>
<a id="btnOK" class="mui-btn primary">确定</a>
</div>
</div>
要弹出这个对话框:
MUI.showDialog(jpage.find("#dlgAddPerson"));
或者用a标签链接打开:
<a href="#dlgAddPerson">添加人物</a>
@see showDialog 弹出对话框
@see #muiAlert MUI.app_alert 提示框(app_alert)是一个id为`muiAlert`的特别对话框。
@see .mui-menu 弹出菜单,也是一类特别的对话框。
菜单是特殊的一类对话框。因而id以"dlg"开头,以便a标签通过href链接时,可直接弹出菜单(即打开对话框)。
@key .mui-menu 菜单
示例:添加右上角菜单(习惯上左上角为返回按钮,右上角为菜单按钮)
<!-- btn-icon依次标识左一,右一,左二,右二图标按钮 -->
<div class="hd">
<a href="javascript:hd_back();" class="btn-icon"><i class="icon icon-back"></i></a>
<a href="#dlgMenu" class="btn-icon"><i class="icon icon-menu"></i></a>
<h2>谱系图</h2>
</div>
<!-- 左上角弹出菜单,用top类标识 -->
<ul id="dlgMenu" class="mui-menu top">
<a href="javascript:PagePerson.showForAdd();"><li><i class="icon icon-add"></i>添加人物</li></a>
<li id="mnuQueryPerson"><i class="icon icon-search"></i>查找人物</li>
<a href="#dlgMenuShare"><li><i class="icon icon-viewfav"></i>分享到</li></a>
</ul>
<!-- 弹出菜单 -->
<ul id="dlgMenuShare" class="mui-menu">
<li id="li1">微信好友</li>
<li id="li2">微信朋友圈</li>
</ul>
@key #footer 底部导航栏
设置id为"footer"的导航, 框架会对此做些设置: 如果当前页面为导航栏中的一项时, 就会自动显示导航栏.
例: 在html中添加底部导航:
<div id="footer">
<a href="#home">订单</a>
<a href="#me">我</a>
</div>
如果要添加其它底部导航,可以用ft类加mui-navbar类,例如下例显示一个底部工具栏:
<div class="ft mui-navbar noactive">
<a href="javascript:;">添加</a>
<a href="javascript:;">更新</a>
<a href="javascript:;">删除</a>
</div>
仅当页面创建时才会加载。
<img src="../m/images/ui/carwash.png">
使用MUI框架的Web应用支持被安卓/苹果原生应用加载(通过cordova技术)。
设置说明:
不同的app大版本(通过URL参数cordova=?识别)或不同平台加载的插件是不一样的,要查看当前加载了哪些插件,可以在Web控制台中执行:
cordova.require('cordova/plugin_list')
对原生应用的额外增强包括:
@key topic-splashScreen
@see options.manualSplash
应用加载完成后,自动隐藏启动画面(SplashScreen)。如果需要自行隐藏启动画面,可以设置
MUI.options.manualSplash = true; // 可以放在H5应用的主js文件中,如index.js
然后开发者自己加载完后隐藏SplashScreen:
if (navigator.splashscreen && navigator.splashscreen.hide)
navigator.splashscreen.hide();
@key topic-iosStatusBar
可通过MUI.options.statusBarColor设置状态栏前景和背景色。
statusBarColor: "#,light" // 默认,背景与MUI.container(即.mui-container类)背景一致,白字。
statusBarColor: "#000000,light" // 黑底白字。
statusBarColor: "#ffffff,dark" // 白底黑字
statusBarColor: "none" // 不显示状态栏。(实际效果是纯色顶栏,而不是没有顶栏;如果要让页面平铺到顶栏,应自行调用`StatusBar.overlaysWebView(true)`)
如果不同页面有不同颜色,可以设置切换页面时自动按照页面头颜色来设置:
MUI.options.fixTopbarColor=true
(v6) 或直接调用MUI.fixTopbarColor()
。
注意此时要设置顶栏颜色,页面必须有页头即hd类并设置好颜色,若不需要页头可以设置隐藏.
如果希望自行设置状态栏,可以设置statusBarColor为null:
MUI.options.statusBarColor = null;
然后在deviceready事件中自行设置样式, 如
function muiInit() {
$(document).on("deviceready", onSetStatusBar);
function onSetStatusBar()
{
var bar = window.StatusBar;
if (bar) {
bar.styleLightContent();
bar.backgroundColorByHexString("#ea8010");
}
}
}
@key deviceready APP初始化后回调事件
APP初始化成功后,回调该事件。如果deviceready事件未被回调,则出现启动页无法消失、插件调用无效、退出程序时无提示等异常。
其可能的原因是:
<script type="text/javascript" src="http://3.3.3.3/1.js"></script>
框架自动根据系统环境为应用容器(.mui-container类)增加以下常用类标识:
@key .mui-android 安卓系统
@key .mui-ios 苹果IOS系统
@key .mui-weixin 微信浏览器
@key .mui-cordova 原生环境
在css中可以利用它们做针对系统的特殊设置。
如果使用了 jquery.touchSwipe 库,则默认支持手势:
@key mui-swipenav DOM属性
如果页面中某组件上的左右划与该功能冲突,可以设置属性mui-swipenav="no"来禁用页面前进后退功能,以确保组件自身的左右划功能正常:
<div id="div1" mui-swipenav="no"></div>
jpage.find("#div1").swipe({
swipeLeft: swipeH,
swipeRight: swipeH
});
function swipeH(ev, direction, distance, duration, fingerCnt, fingerData, currentDirection) {
if (direction == 'left') {
console.log("next");
}
else if (direction == 'right') {
console.log("prev");
}
}
@key .noSwipe CSS-class
左右划前进后退功能会导致横向滚动失效。可以通过添加noSwipe类(注意大小写)的方式禁用swipe事件恢复滚动功能:
<div class="noSwipe"></div>
典型应用是, 在开发前端页面时, 本地无须运行任何后端服务器(如apache/iis/php等), 直接跨域连接远程接口进行开发.
支持直接在浏览器中打开html/js文件运行应用.
需要浏览器支持CORS相关设置. 以下以chrome为例介绍.
例如, 远程接口的基础URL地址为 http://oliveche.com/jdcloud/
为chrome安装可设置CORS的插件(例如ForceCORS), 并设置:
添加URL: http://oliveche.com/*
Access-Control-Allow-Origin: file://
Access-Control-Allow-Credentials: true
在app.js中正确设置接口URL,如
$.extend(MUI.options, {
serverUrl: "http://oliveche.com/jdcloud/api.php"
// serverUrlAc: "ac"
});
这时直接在chrome中打开html文件即可连接远程接口运行起来.
以下参考文档介绍MUI模块提供的方法/函数(fn)、属性/变量(var)等,示例如下:
@fn showPage(pageName, title?, paramArr?) 一个函数。参数说明中问号表示参数可缺省。
@var options 一个属性。
@class batchCall(opt?={useTrans?=0}) 一个JS类。
@key topic-splashScreen key表示一般关键字。前缀为"topic-"用于某专题
@key .wui-page 一个CSS类名"wui-page",关键字以"."开头。
@key #wui-pages 一个DOM对象,id为"wui-pages",关键字以"#"开头。
对于模块下的fn,var,class这些类别,如非特别说明,调用时应加MUI前缀,如
MUI.showPage("#order");
var opts = MUI.options;
var batch = new MUI.batchCall();
batch.commit();
以下函数可不加MUI前缀:
intSort
numberSort
callSvr
callSvrSync
app_alert
参考mui-name.js模块。
生成指定区间的随机整数。示例:
var i = randInt(1, 10); // 1-10之间的整数,包含1或10
生成随机字符串,包含字母或数字,不包含易混淆的0或O。示例:
var dynCode = randChr(4); // e.g. "9BZ3"
解析url编码格式的查询字符串,返回对应的对象。
if (location.search) {
var queryStr = location.search.substr(1); // "?id=100&name=abc&val=3.14"去掉"?"号
var args = parseQuery(queryStr); // {id: 100, name: "abc", val: 3.14}
}
注意:
如果值为整数或小数,则会转成相应类型。如上例中 id为100,不是字符串"100".
将字符串转成boolean值。除"0", "1"外,还可以支持字符串 "on"/"off", "true"/"false"等。
重新加载当前页面,但不要#hash部分。
日期对象格式化字符串。
@param fmt 格式字符串。由以下组成:
yyyy - 四位年,如2008, 1999
yy - 两位年,如 08, 99
mm - 两位月,如 02, 12
dd - 两位日,如 01, 30
HH - 两位小时,如 00, 23
MM - 两位分钟,如 00, 59
SS - 两位秒,如 00, 59
支持这几种常用格式:
L - 标准日期时间,相当于 "yyyy-mm-dd HH:MM:SS"
D - 标准日期,相当于 "yyyy-mm-dd"
T - 标准时间,相当于 "HH:MM:SS"
示例:
var dt = new Date();
var dtStr1 = dt.format("D"); // "2009-10-20"
var dtStr2 = dt.format("yyyymmdd-HHMM"); // "20091020-2038"
将纯时间字符串生成一个日期对象。
var dt1 = parseTime("10:10:00");
var dt2 = parseTime("10:11");
将日期字符串转为日期时间格式。其效果相当于new Date(Date.parse(dateStr))
,但兼容性更好(例如在safari中很多常见的日期格式无法解析)
示例:
var dt1 = parseDate("2012-01-01");
var dt2 = parseDate("2012/01/01 20:00:09");
var dt3 = parseDate("2012.1.1 20:00");
支持时区,时区格式可以是"+8", "+08", "+0800", "Z"这些,如
parseDate("2012-01-01T09:10:20.328+0800");
parseDate("2012-01-01T09:10:20Z");
为日期对象加几天/小时等。参数n为整数,可以为负数。
@param sInterval Enum. 间隔单位. d-天; m-月; y-年; h-小时; n-分; s-秒
示例:
var dt = new Date();
dt.add("d", 1); // 1天后
dt.add("m", 1); // 1个月后
dt.add("y", -1); // 1年前
dt.add("h", 3); // 3小时后
dt.add("n", 30); // 30分钟后
dt.add("s", 30); // 30秒后
@see Date.diff
计算日期到另一日期间的间隔,单位由sInterval指定(具体值列表参见Date.add).
var dt = new Date();
...
var dt2 = new Date();
var days = dt.diff("d", dt2); // 相隔多少天
@see Date.add
从tm到tm1的时间差描述,如"2分钟前", "3天前"等。
tm和tm1可以为时间对象或时间字符串
根据时间段描述得到[起始时间,结束时间)
,注意结束时间是开区间(即不包含)。
假设今天是2015-9-9 周三:
getTmRange("本周", "2015-9-9") -> ["2015-9-7"(本周一), "2015-9-14")
getTmRange("上周") -> ["2015-8-31", "2015-9-7") // 或"前1周"
getTmRange("本月") -> ["2015-9-1", "2015-10-1")
getTmRange("上月") -> ["2015-8-1", "2015-9-1")
getTmRange("今年") -> ["2015-1-1", "2016-1-1") // 或"本年"
getTmRange("去年") -> ["2014-1-1", "2015-1-1") // 或"上年"
getTmRange("本季度") -> ["2015-7-1", "2015-10-1") // 7,8,9三个月
getTmRange("上季度") -> ["2015-4-1", "2015-7-1")
getTmRange("上半年") -> ["2015-1-1", "2015-7-1")
getTmRange("下半年") -> ["2015-7-1", "2016-1-1")
getTmRange("今天") -> ["2015-9-9", "2015-9-10") // 或"本日"
getTmRange("昨天") -> ["2015-9-8", "2015-9-9") // 或"昨日"
getTmRange("前1周") -> ["2015-8-31"(上周一),"2015-9-7"(本周一))
getTmRange("前3月") -> ["2015-6-1", "2015-9-1")
getTmRange("前3天") -> ["2015-9-6", "2015-9-9")
getTmRange("近1周") -> ["2015-9-3","2015-9-10")
getTmRange("近3月") -> ["2015-6-10", "2015-9-10")
getTmRange("近3天") -> ["2015-9-6", "2015-9-10") // "前3天"+今天
dscr可以是
"近|前|上" N "个"? "小时|日|周|月|年|季度"
"本|今" "小时|日/天|周|月|年|季度"
注意:"近X周"包括今天(即使尚未过完)。
示例:快捷填充
<td>
<select class="cboTmRange">
<option value ="本月">本月</option>
<option value ="上月">上月</option>
<option value ="本周">本周</option>
<option value ="上周">上周</option>
<option value ="今年">今年</option>
<option value ="去年">去年</option>
</select>
</td>
var txtTmRange = jdlg.find(".cboTmRange");
txtTmRange.change(function () {
var range = WUI.getTmRange(this.value);
if (range) {
WUI.setFormData(jfrm, {tm1: range[0], tm2: range[1]}, {setOnlyDefined: true});
}
});
// 初始选中
setTimeout(function () {
txtTmRange.change();
});
设置cookie值。如果只是为了客户端长时间保存值,一般建议使用 setStorage.
@see getCookie
@see delCookie
@see setStorage
使用localStorage存储(或使用sessionStorage存储, 如果useSession=true)。
value可以是简单类型,也可以为数组,对象等,后者将自动在序列化后存储。
如果设置了window.STORAGE_PREFIX, 则键值(name)会加上该前缀.
示例:
setStorage("id", "100");
var id = getStorage("id");
delStorage("id");
示例2:存储对象:
window.STORAGE_PREFIX = "jdcloud_"; // 一般在app.js中全局设置
var obj = {id:10, name:"Jason"};
setStorage("obj", obj); // 实际存储键值为 "jdcloud_obj"
var obj2 = getStorage("obj");
alert(obj2.name);
@var STORAGE_PREFIX 本地存储的键值前缀
如果指定, 则调用setStorage/getStorage/delStorage时都将自动加此前缀, 避免不同项目的存储项冲突.
@see getStorage
@see delStorage
取storage中的一项。
默认使用localStorage存储,如果useSession=true,则使用sessionStorage存储。
如果浏览器不支持Storage,则使用cookie实现.
@see setStorage
@see delStorage
@param rs= {h=[header], d=[ @row ]} rs对象(RowSet)
@return arr=[ %obj ]
rs对象用于传递表格,包含表头与表内容。
函数用于将服务器发来的rs对象转成数组。
示例:
var rs = {
h: ["id", "name"],
d: [ [100, "Tom"], [101, "Jane"] ]
};
var arr = rs2Array(rs);
// 结果为
arr = [
{id: 100, name: "Tom"},
{id: 101, name: "Jane"}
];
@see rs2Hash
@see rs2MultiHash
@param rs= {h, d} rs对象(RowSet)
@return hash= {key => %obj}
示例:
var rs = {
h: ["id", "name"],
d: [ [100, "Tom"], [101, "Jane"] ]
};
var hash = rs2Hash(rs, "id");
// 结果为
hash = {
100: {id: 100, name: "Tom"},
101: {id: 101, name: "Jane"}
};
key可以为一个函数,返回实际key值,示例:
var hash = rs2Hash(rs, function (o) {
return "USER-" + o.id;
});
// 结果为
hash = {
"USER-100": {id: 100, name: "Tom"},
"USER-101": {id: 101, name: "Jane"}
};
key函数也可以返回[key, value]数组:
var hash = rs2Hash(rs, function (o) {
return ["USER-" + o.id, o.name];
});
// 结果为
hash = {
"USER-100": "Tom",
"USER-101": "Jane"
};
@see rs2Array
数据分组(group by).
@param rs= {h, d} rs对象(RowSet)
@return hash= {key => [ %obj ]}
示例:
var rs = {
h: ["id", "name"],
d: [ [100, "Tom"], [101, "Jane"], [102, "Tom"] ]
};
var hash = rs2MultiHash(rs, "name");
// 结果为
hash = {
"Tom": [{id: 100, name: "Tom"}, {id: 102, name: "Tom"}],
"Jane": [{id: 101, name: "Jane"}]
};
key也可以是一个函数,返回实际的key值,示例,按生日年份分组:
var rs = {
h: ["id", "name", "birthday"],
d: [ [100, "Tom", "1998-10-1"], [101, "Jane", "1999-1-10"], [102, "Tom", "1998-3-8"] ]
};
// 按生日年份分组
var hash = rs2MultiHash(rs, function (o) {
var m = o.birthday.match(/^\d+/);
return m && m[0];
});
// 结果为
hash = {
"1998": [{id: 100, name: "Tom", birthday: "1998-10-1"}, {id: 102, name: "Tom", birthday:"1998-3-8"}],
"1999": [{id: 101, name: "Jane", birthday: "1999-1-10"}]
};
key作为函数,也可返回[key, value]:
var hash = rs2MultiHash(rs, function (o) {
return [o.name, [o.id, o.birthday]];
});
// 结果为
hash = {
"Tom": [[100, "1998-10-1"], [102, "1998-3-8"]],
"Jane": [[101, "1999-1-10"]]
};
@see rs2Hash
@see rs2Array
将字符串代表的压缩表("v1:v2:v3,...")转成对象数组。
e.g.
var users = "101:andy,102:beddy";
var varr = list2varr(users);
// varr = [["101", "andy"], ["102", "beddy"]];
var arr = rs2Array({h: ["id", "name"], d: varr});
// arr = [ {id: 101, name: "andy"}, {id: 102, name: "beddy"} ];
var cmts = "101\thello\n102\tgood";
var varr = list2varr(cmts, "\t", "\n");
// varr=[["101", "hello"], ["102", "good"]]
将对象数组转成字符串代表的压缩表("v1:v2:v3,...")。
示例:
var objarr = [
{id:100, name:'name1', qty:2},
{id:101, name:'name2', qty:3}
];
var list = objarr2list(objarr, ["id","qty"]);
// 返回"100:2,101:3"
var list2 = objarr2list(objarr, function (e, i) { return e.id + ":" + e.qty; });
// 结果同上
整数排序. 用于datagrid column sorter:
<th data-options="field:'id', sortable:true, sorter:intSort">编号</th>
小数排序. 用于datagrid column sorter:
<th data-options="field:'score', sortable:true, sorter:numberSort">评分</th>
取符合条件(fn)的对象,一般可使用$.closest替代
示例:
var url = "http://xxx/api.php";
if (a)
url = appendParam(url, "a=" + a);
if (b)
url = appendParam(url, "b=" + b);
appendParam(url, $.param({a:1, b:3}));
支持url中带有"?"或"#",如
var url = "http://xxx/api.php?id=1#order";
appendParam(url, "pay=1"); // "http://xxx/api.php?id=1&pay=1#order";
示例:
var url = "http://xxx/api.php?a=1&b=3&c=2";
var url1 = deleteParam(url, "b"); // "http://xxx/api.php?a=1&c=2";
var url = "http://server/jdcloud/m2/?logout#me";
var url1 = deleteParam(url, "logout"); // "http://server/jdcloud/m2/?#me"
当前应用运行在微信中。
当前应用运行在IOS平台,如iphone或ipad中。
当前应用运行在安卓平台。
如果str符合整数或小数,则返回相应类型。
对模板做字符串替换
var tpl = "<li><p>{name}</p><p>{dscr}</p></li>";
var data = {name: 'richard', dscr: 'hello'};
var html = applyTpl(tpl, data);
// <li><p>richard</p><p>hello</p></li>
设置延迟执行。当delayCnt=1时与setTimeout效果相同。
多次置于事件队列最后,一般3次后其它js均已执行完毕,为idle状态
e.g.
var str = kvList2Str({"CR":"Created", "PA":"Paid"}, ';', ':');
// str="CR:Created;PA:Paid"
解析key-value列表字符串,返回kvMap。
示例:
var map = parseKvList("CR:新创建;PA:已付款", ";", ":");
// map: {"CR": "新创建", "PA":"已付款"}
var map = parseKvList("CR:新创建;PA:已付款", ";", ":", true);
// map: {"新创建":"CR", "已付款":"PA"}
Q("abc") -> 'abc'
Q("a'bc") -> 'a\'bc'
将文本或图片转成html,常用于将筋斗云后端返回的图文内容转成html在网页中显示。示例:
var item = {id: 1, name: "商品1", content: "商品介绍内容", pics: "100,102"};
var html = MUI.text2html(item.content, item.pics);
jpage.find("#content").html(html);
文字转html示例:
var html = MUI.text2html("hello\nworld");
生成html为
<p>hello</p>
<p>world</p>
支持简单的markdown格式,如"# ","## "分别表示一二级标题, "- "表示列表(注意在"#"或"-"后面有英文空格):
# 标题1
内容1
# 标题2
内容2
- 列表1
- 列表2
函数可将图片编号列表转成img列表,如:
var html = MUI.text2html(null, "100,102");
生成
<img src="../api.php/att?thumbId=100">
<img src="../api.php/att?thumbId=102">
var a = {a: 1};
WUI.extendNoOverride(a, {b: 'aa'}, {a: 99, b: '33', c: 'bb'});
// a = {a: 1, b: 'aa', c: 'bb'}
定义JS模块。这是一个全局函数。
定义一个模块:
jdModule("jdcloud.common", JdcloudCommon);
function JdcloudCommon() {
var self = this;
// 对外提供一个方法
self.rs2Array = rs2Array;
function rs2Array(rs)
{
return ...;
}
}
获取模块对象:
var mCommon = jdModule("jdcloud.common");
var arr = mCommon.rs2Array(rs);
返回模块映射列表。
var moduleMap = jdModule(); // 返回 { "jdcloud.common": JdcloudCommon, ... }
支持出错自动重连的websocket client. 默认连接同域下的jdserver。
/jdserver
,也可指定全路径,如 ws://oliveche.com/jdserver
示例:
// 返回websocket proxy对象
var ws = jdPush("tighten", function (msg, ws) {
console.log(msg)
// 发送消息
ws.send({ac:"hello"}); // 发送JS对象会自动转JSON发送。
}, opt);
// 关闭
ws.close();
开发环境可配置代理服务器连接jdserver,比如使用Apache,在web根目录下配置.htaccess:
(确保已打开wstunnel模块且允许htaccess文件)
# 连接已部署的websocket server
rewriterule ^jdserver ws://oliveche.com/jdserver [P,L]
# 或连本地websocket server (注意最后/不可少)
# rewriterule ^jdserver ws://127.0.0.1:8081/ [P,L]
jdserver同时支持http和websocket,建议设置为:(注意顺序)
rewriterule ^jdserver/(.+) http://127.0.0.1:8081/$1 [P,L]
rewriterule ^jdserver ws://127.0.0.1:8081/ [P,L]
或
rewriterule ^jdserver/(.+) http://oliveche.com/jdserver/$1 [P,L]
rewriterule ^jdserver ws://oliveche.com/jdserver/ [P,L]
这样,可以使用基于HTTP的push接口给别的客户端发消息:
callSvr("/jdserver/push", $.noop, {
app: "app1",
user: "*", // 表示发给app1中所有人。也可指定`user1`, `user1,user2`, `user*`这样
msg: {
ac: "msg1",
data: "hello"
}
});
// 返回推送人次数量,示例:2
上面通过msg字段发送js对象。
注意jdPush默认只处理json消息: handleMsg(jsonObj),其它消息只在控制台打印;
想处理其它消息,可调用jdPush时设置opt.dataType='text'
取在线用户:
callSvr("/jdserver/getUsers", { app: "app1" });
// 返回示例: ['user1', 'user2' ]
示例:websocket连接jdserver,然后给自己发消息
jdPush('app1'); // 默认用户为g_data.userInfo.id
callSvr('/jdserver/push', {app: 'app1', user: '*', msg: 'hi'});
// 在控制台可收到hi
取DOM对象中带name属性的子对象的内容, 放入一个JS对象中, 以便手工调用callSvr.
注意:
与setFormData配合使用时, 可以只返回变化的数据.
jf.submit(function () {
var ac = jf.attr("action");
callSvr(ac, fn, getFormData(jf));
});
在dialog的onValidate/onOk回调中,由于在显示对话框时自动调用过setFormData,所以用getFormData只返回有修改变化的数据。如果要取所有数据可设置参数doGetAll=true:
var data = WUI.getFormData(jfrm, true);
这也只返回非disabled的组件,如果包括disabled组件的值也需要,可以用
var data = WUI.getFormData(jfrm, "all");
如果在jo对象中存在有name属性的file组件(input[type=file][name]),或指定了属性enctype="multipart/form-data",则调用getFormData会返回FormData对象而非js对象,
再调用callSvr时,会以"multipart/form-data"格式提交数据。一般用于上传文件。
示例:
<div>
课程文档
<input name="pdf" type="file" accept="application/pdf">
</div>
或传统地:
<form method="POST" enctype='multipart/form-data'>
课程文档
<input name="pdf" type="file" accept="application/pdf">
</form>
如果有多个同名组件(name相同,且非disabled状态),最终值将以最后组件为准。
如果想要以数组形式返回所有值,应在名字上加后缀"[]",示例:
行统计字段: <select name="gres[]" class="my-combobox fields"></select>
行统计字段2: <select name="gres[]" class="my-combobox fields"></select>
列统计字段: <select name="gres2" class="my-combobox fields"></select>
列统计字段2: <select name="gres2" class="my-combobox fields"></select>
取到的结果示例:
{ gres: ["id", "name"], gres2: "name" }
@see setFormData
专门取虚拟字段的值。例如:
<select name="whId" class="my-combobox" data-options="url:..., jd_vField:'whName'"></select>
用WUI.getFormData可取到{whId: xxx}
,而WUI.getFormData_vf遍历带name属性且设置了jd_vField选项的控件,调用接口getValue_vf(ji)来取其显示值。
因而,为支持取虚拟字段值,控件须定义getValue_vf接口。
<input name="orderType" data-options="jd_vField:'orderType'" disabled>
注意:与getFormData不同,它不忽略有disabled属性的控件。
@see defaultFormItems
表单对象遍历。对表单jo(实际可以不是form标签)下带name属性的控件,交给回调cb处理。
注意:
对于checkbox,设置时根据val确定是否选中;取值时如果选中取value属性否则取value-off属性。
缺省value为"on", value-off为空(非标准属性,本框架支持),可以设置:
<input type="checkbox" name="flag" value="1">
<input type="checkbox" name="flag" value="1" value-off="0">
@param cb (ji, name, it) it.getDisabled/setDisabled/getValue/setValue/getShowbox
当cb返回false时可中断遍历。
示例:
WUI.formItems(jdlg.find(".my-fixedField"), function (ji, name, it) {
var fixedVal = ...
if (fixedVal || fixedVal == '') {
it.setReadonly(true);
var forAdd = beforeShowOpt.objParam.mode == FormMode.forAdd;
if (forAdd) {
it.setValue(fixedVal);
}
}
else {
it.setReadonly(false);
}
});
@fn getFormItem (ji)
获取对话框上一个组件的访问器。示例,设置名为orderId的组件值:
var ji = jdlg.find("[name=orderId]"); // 或者用 var ji = $(frm.orderId);
var it = WUI.getFormItem(ji); // 或者用 var it = ji.gn()
it.setValue("hello"); // 类似于ji.val("hello"),但支持各种复杂组件
还可以用更简洁的jo.gn,以及支持用链式风格的调用:
var it = jdlg.gn("orderId");
it.val("hello").disabled(true); // 等价于 it.setValue("hello"); it.setDisabled(true);
@see jQuery.fn.gn (name?)
@var getFormItemExt
可通过扩展WUI.getFormItemExt[新类型]
来为表单扩展其它类型控件。示例:
WUI.getFormItemExt["myinput"] = function (ji) {
if (ji.hasClass("myinput"))
return new MyInputFormItem(ji);
}
function MyInputFormItem(ji) {
WUI.FormItem.call(this, ji);
}
MyInputFormItem.prototype = $.extend(new WUI.FormItem(), {
getValue: function () {
return this.ji.val();
}
});
按名字访问组件(gn表示getElementByName),返回访问器(iterator),用于对各种组件进行通用操作。
示例:
var it = jdlg.gn("orderId");
var v = it.val(); // 取值,相当于 jdlg.find("[name=orderId]").val(); 但兼容my-combobox, wui-combogrid, wui-subobj等复杂组件。
it.val(100); // 设置值
// it.val([100, "ORDR-100"]); // 对于combogrid, 可以传数组,同时设置value和text
var isVisible = it.visible(); // 取是否显示
it.visible(false); // 设置隐藏
var isDisabled = it.disabled();
it.disabled(true);
var isReadonly = it.readonly();
it.readonly(true);
如果未指定name,则以jQuery对象本身作为组件返回访问器。所以也可以这样用:
var jo = jdlg.find("[name=orderId]");
var it = jo.gn(); // name可以省略,表示取自身
it.val(100);
用于为带name属性的DOM对象设置内容为data[name].
要清空所有内容, 可以用 setFormData(jo), 相当于增强版的 form.reset().
注意:
@param opt {setOrigin?=false, setOnlyDefined?=false}
@param opt.setOrigin 为true时将data设置为数据源, 这样在getFormData时, 只会返回与数据源相比有变化的数据.
缺省会设置该DOM对象数据源为空.
@param opt.setOnlyDefined 设置为true时,只设置form中name在data中存在的项,其它项保持不变;而默认是其它项会清空。
对象关联的数据源, 可以通过 jo.data("origin") 来获取, 或通过 jo.data("origin", newOrigin) 来设置.
示例:
<div id="div1">
<p>订单描述:<span name="dscr"></span></p>
<p>状态为:<input type=text name="status"></p>
<p>金额:<span name="amount"></span>元</p>
</div>
Javascript:
var data = {
dscr: "筋斗云教程",
status: "已付款",
amount: "100"
};
var jo = $("#div1");
var data = setFormData(jo, data);
$("[name=status]").html("已完成");
var changedData = getFormData(jo); // 返回 { dscr: "筋斗云教程", status: "已完成", amount: "100" }
var data = setFormData(jo, data, {setOrigin: true});
$("[name=status]").html("已完成");
var changedData = getFormData(jo); // 返回 { status: "已完成" }
$.extend(jo.data("origin_"), changedData); // 合并变化的部分到数据源.
@see getFormData
@param fnOK 加载成功后的回调函数
@param ajaxOpt 传递给$.ajax的额外选项。
默认未指定ajaxOpt时,简单地使用添加script标签机制异步加载。如果曾经加载过,可以重用cache。
注意:再次调用时是否可以从cache中取,是由服务器的cache-control决定的,在web和web/page目录下的js文件一般是禁止缓存的,再次调用时会从服务器再取,若文件无更改,服务器会返回304状态。
这是因为默认我们使用Apache做服务器,在相应目录下.htaccess中配置有缓存策略。
如果指定ajaxOpt,且非跨域,则通过ajax去加载,可以支持同步调用。如果是跨域,仍通过script标签方式加载,注意加载完成后会自动删除script标签。
返回defered对象(与$.ajax类似),可以用 dfd.then() / dfd.fail() 异步处理。
常见用法:
动态加载一个script,异步执行其中内容:
loadScript("1.js", onload); // onload中可使用1.js中定义的内容
loadScript("http://otherserver/path/1.js"); // 跨域加载
加载并立即执行一个script:
loadScript("1.js", {async: false});
// 可立即使用1.js中定义的内容
注意:如果是跨域加载,不支持同步调用($.ajax的限制),如:
loadScript("http://oliveche.com/1.js", {async: false});
// 一旦跨域,选项{async:false}指定无效,不可立即使用1.js中定义的内容。
示例:在菜单中加一项“工单工时统计”,动态加载并执行一个JS文件:
store.html中设置菜单:
<a href="javascript:WUI.loadScript('page/mod_工单工时统计.js')">工单工时统计</a>
在page/mod_工单工时统计.js
文件中写报表逻辑,mod
表示一个JS模块文件,示例:
function show工单工时统计()
{
DlgReportCond.show(function (data) {
var queryParams = WUI.getQueryParam({createTm: [data.tm1, data.tm2]});
var url = WUI.makeUrl("Ordr.query", { res: 'id 工单号, code 工单码, createTm 生产日期, itemCode 产品编码, itemName 产品名称, cate2Name 产品系列, itemCate 产品型号, qty 数量, mh 理论工时, mh1 实际工时', pagesz: -1 });
WUI.showPage("pageSimple", "工单工时统计!", [url, queryParams, onInitGrid]);
});
}
show工单工时统计();
如果JS文件修改了,点菜单时可以实时执行最新的内容。
如果要动态加载script,且使用后删除标签(里面定义的函数会仍然保留),建议直接使用$.getScript
,它等同于:
loadScript("1.js", {cache: false});
[小技巧]
在index.js/app.js等文件中写代码,必须刷新整个页面才能加载生效。
可以先把代码写在比如 web/test.js 中,这样每次修改后不用大刷新,直接在chrome控制台上加载运行:
WUI.loadScript("test.js")
等改好了再拷贝到真正想放置这块代码的地方。修改已有的框架中函数也可以这样来快速迭代。
执行JS代码,返回JS对象,示例:
source='{a: 1, b:"hello", c: function () {} }'
前面允许有单行注释,如
source='// comment\n {a: 1, b:"hello", c: function () {} }'
JS代码一般以花括号开头。在用于DOM属性时,也允许没有花括号的这种写法,如:
<div data-options="a:1,b:'hello',c:true"></div>
上例可返回 {a:1, b:'hello', c:true}
.
也支持各种表达式及函数调用,如:
<div data-options="getSomeOption()"></div>
更复杂的,如果代码不只是一个选项,前面还有其它JS语句,则返回最后一行语句的值,
最后一行可以是变量、常量、函数调用等,但不可以是花括号开头的选项,对选项须用括号包起来:
function hello() { }
({a: 1, b:"hello", c: hello })
或:
function hello() { }
var ret={a: 1, b:"hello", c: hello }
ret
传入的参数变量ctx可以在代码中使用,用于框架与扩展代码的交互,如:
{a: 1, b:"hello", c: ctx.fn1() }
执行出错时错误显示在控制台。调用者可回调处理错误。
evalOptions(src, null, function (ex) {
app_alert("文件" + url + "出错: " + ex);
});
注意:受限于浏览器,若使用try { eval(str) } catch(ex) {}
结构会导致原始的错误行号丢失,
为了避免大量代码中有错误时调试困难,在大于1行代码时,不使用try-catch,而是直接抛出错误。
从远程获取JSON结果.
注意: 与$.getJSON不同, 本函数不直接调用JSON.parse解析结果, 而是将返回当成JS代码使用eval执行得到JSON结果再回调fnOK.
示例:
WUI.loadJson("1.js", function (data) {
// handle json value `data`
});
1.js可以是返回任意JS对象的代码, 如:
// 可以有注释
{
a: 2 * 3600,
b: "hello",
// c: {}
d: function () {
},
}
复杂地,可以定义变量、函数,注意最后返回值对象要用小括号把返回对象括起来,如:
var schema = {
title: "菜单配置",
};
function onReady() {
}
({
schema: schema,
onReady: onReady
})
建议用模块方式来写,即用(function() { ... return ... })()
把定义内容包起来,这样变量和函数不会污染全局空间:
(function () {
var schema = ...
function onReady() {...}
return {
schema: schema,
onReady: onReady
}
})();
不处理结果时, 则该函数与$.getScript效果类似.
@param options 参考$.ajax参数
@options.async 默认为异步,设置{async:false}
表示同步调用。
@see evalOptions
动态加载css文件, 示例:
WUI.loadCss("lib/bootstrap.min.css");
设置日期框, 如果输入了非法日期, 自动以指定日期(如未指定, 用当前日期)填充.
setDateBox($("#txtComeDt"), function () { return genDefVal()[0]; });
设置时间框, 如果输入了非法时间, 自动以指定时间(如未指定, 用当前时间)填充.
setTimeBox($("#txtComeTime"), function () { return genDefVal()[1]; });
用于简化异步编程. 可将不易读的回调方式改写为易读的顺序执行方式.
var dfd = $.getScript("http://...");
function onSubmit()
{
dfd.then(function () {
foo();
bar();
});
}
可改写为:
function onSubmit()
{
if (waitFor(dfd)) return;
foo();
bar();
}
生成"#112233"形式的颜色值.
rgb(255,255,255) -> "#ffffff"
将jquery取到的颜色转成16进制形式,如:"rgb(4, 190, 2)" -> "#04be02"
示例:
var color = rgb2hex( $(".mui-container").css("backgroundColor") );
和使用$.data()差不多,更好用一些. 例:
$(o).jdata().hello = 100;
$(o).jdata({hello:100, world:200});
通过限定图片大小来压缩图片,用于图片预览和上传。
不支持IE8及以下版本。
函数cb的回调参数: picData={b64src,blob,w,h,w0,h0,quality,name,mimeType,size0,size,b64size,info}
b64src为base64格式的Data URL, 如 "data:image/jpeg;base64,/9j/4AAQSk...", 用于给image或background-image赋值显示图片;
可以赋值给Image.src:
var img = new Image();
img.src = picData.b64src;
或
$("<div>").css("background-image", "url(" + picData.b64src + ")");
blob用于放到FormData中上传:
fd.append('file', picData.blob, picData.name);
其它picData属性:
[预览和上传示例]
HTML:
<form action="upfile.php">
<div class="img-preview"></div>
<input type="file" /><br/>
<input type="submit" >
</form>
用picData.b64src来显示预览图,并将picData保存在img.picData_属性中,供后面上传用。
var jfrm = $("form");
var jpreview = jfrm.find(".img-preview");
var opt = {maxSize:1280};
jfrm.find("input[type=file]").change(function (ev) {
$.each(this.files, function (i, fileObj) {
compressImg(fileObj, function (picData) {
$("<img>").attr("src", picData.b64src)
.prop("picData_", picData)
.appendTo(jpreview);
//$("<div>").css("background-image", "url("+picData.b64src+")").appendTo(jpreview);
}, opt);
});
this.value = "";
});
上传picData.blob到服务器
jfrm.submit(function (ev) {
ev.preventDefault();
var fd = new FormData();
var idx = 1;
jpreview.find("img").each(function () {
// 名字要不一样,否则可能会覆盖
fd.append('file' + idx, this.picData_.blob, this.picData_.name);
++idx;
});
$.ajax({
url: jfrm.attr("action"),
data: fd,
processData: false,
contentType: false,
type: 'POST',
// 允许跨域调用
xhrFields: {
withCredentials: true
},
success: cb
});
return false;
});
参考:JIC.js (https://github.com/brunobar79/J-I-C)
TODO: 用完后及时释放内存,如调用revokeObjectURL等。
触发含有异步操作的事件,在异步事件完成后继续。兼容同步事件处理函数,或多个处理函数中既有同步又有异步。
返回Deferred对象,或false表示要求取消之后操作。
@param ev 事件名,或事件对象$.Event()
示例:以事件触发方式调用jo的异步方法submit:
var dfd = WUI.triggerAsync(jo, 'submit');
if (dfd === false)
return;
dfd.then(doNext);
function doNext() { }
jQuery对象这样提供异步方法:triggerAsync会用事件对象ev创建一个dfds数组,将Deferred对象存入即可支持异步调用。
jo.on('submit', function (ev) {
var dfd = $.ajax("upload", ...);
if (ev.dfds)
ev.dfds.push(dfd);
});
将array转成tree. 注意它会修改arr(添加children属性),但返回新的数组。
var ret = WUI.makeTree([
{id: 1},
{id: 2, fatherId: 1},
{id: 3, fatherId: 2},
{id: 4, fatherId: 1}
]);
结果:
ret = [
{id: 1, children: [
{id: 2, fatherId: 1, children: [
{id: 3, fatherId: 2},
],
{id: 4, fatherId: 1}
]
]
返回一个属性做eval后的js值。
示例:读取一个对象值:
var opt = evalAttr(jo, "data-opt");
<div data-opt="{id:1, name:\"data1\"}"><div>
考虑兼容性,也支持忽略括号的写法,
<div data-opt="id:1, name:\"data1\""><div>
读取一个数组:
var arr = evalAttr(jo, "data-arr");
<div data-arr="['aa', 'bb']"><div>
读取一个函数名(或变量):
var fn = evalAttr(jo, "mui-initfn");
<div mui-initfn="initMyPage"><div>
@see evalOptions
中止之后的调用, 直接返回.
直接返回. 用法:
throw new DirectReturn();
可直接调用app_abort();
框架自动在脚本出错时弹框报错,并上报服务端(syslog).
可通过WUI.options.skipErrorRegex忽略指定错误。
第一次调用,根据jo上设置的data-options属性及指定的defVal初始化,或为{}
。
存到jo.prop("muiOptions")上。之后调用,直接返回该属性。
@see evalAttr
@var queryHint 查询用法提示
@param kvList {key=>value}, 键值对,值中支持操作符及通配符。也支持格式 [ [key, value] ], 这时允许key有重复。
根据kvList生成BPQ协议定义的{obj}.query的cond参数。
例如:
var kvList = {phone: "13712345678", id: ">100", addr: "上海*", picId: "null"};
WUI.getQueryCond(kvList);
有多项时,每项之间以"AND"相连,以上定义将返回如下内容:
"phone='13712345678' AND id>100 AND addr LIKE '上海*' AND picId IS NULL"
示例二:
var kvList = [ ["phone", "13712345678"], ["id", ">100"], ["addr", "上海*"], ["picId", "null"] ];
WUI.getQueryCond(kvList); // 结果同上。
设置值时,支持以下格式:
支持and/or查询,但不支持在其中使用括号:
以下表示的范围相同:
{k1:'1-5,7-10', k2:'1-10 and <>6'}
符号优先级依次为:"-"(类似and) ","(类似or) and or
在详情页对话框中,切换到查找模式,在任一输入框中均可支持以上格式。
(v5.5) value支持用数组表示范围(前闭后开区间),特别方便为起始、结束时间生成条件:
var cond = getQueryCond({tm: ["2019-1-1", "2020-1-1"]}); // 生成 "tm>='2019-1-1' AND tm<'2020-1-1'"
var cond = getQueryCond({tm: [null, "2020-1-1"]}); // 生成 "tm<'2020-1-1'"。数组中任一值为null或''都一样会被忽略。
var cond = getQueryCond({tm: [null, null]); // 返回空串''
@see getQueryParam
@see getQueryParamFromTable 获取datagrid的当前查询参数
@see doFind
(v5.5) 支持在key中包含查询提示。如"code/s"表示不要自动猜测数值区间或日期区间。
比如输入'126231-191024'时不会当作查询126231到191024的区间。
(v6) 日期、时间字段查询时,可使用WUI.getTmRange
函数支持的时间区间如"今天","本周","本月", "今年", "近3天(小时|周|月|季度|年)”,"前3天(小时|周|月|季度|年)”等。
@see wui-find-hint
根据键值对生成BQP协议中{obj}.query接口需要的cond参数.
即 {cond: WUI.getQueryCond(kvList) }
示例:
WUI.getQueryParam({phone: '13712345678', id: '>100'})
返回
{cond: "phone='13712345678' AND id>100"}
@see getQueryCond
@see getQueryParamFromTable 获取datagrid的当前查询参数
连续5次点击某处,每次点击间隔不超过2s, 执行隐藏动作。
例:
// 连续5次点击当前tab标题,重新加载页面. ev为最后一次点击事件.
var self = WUI;
self.doSpecial(self.tabMain.find(".tabs-header"), ".tabs-selected", function (ev) {
self.reloadPage();
self.reloadDialog(true);
// 弹出菜单
//jmenu.menu('show', {left: ev.pageX, top: ev.pageY});
return false;
});
连续3次点击对话框中的字段标题,触发查询:
WUI.doSpecial(jdlg, ".wui-form-table td", fn, 3);
复制到剪贴板。
出错时,取出错调用的上下文信息。
ctx: {ac, tm, tv, ret}
设置为true禁用batchCall, 仅用于内部测试。
当前batchCall对象,用于内部调试。
在后端接口尚无法调用时,可以配置MUI.mockData做为模拟接口返回数据。
调用callSvr时,会直接使用该数据,不会发起ajax请求。
mockData={ac => data/fn}
mockData中每项可以直接是数据,也可以是一个函数:fn(param, postParam)->data
例:模拟"User.get(id)"和"User.set()(key=value)"接口:
var user = {
id: 1001,
name: "孙悟空",
};
MUI.mockData = {
// 方式1:直接指定返回数据
"User.get": [0, user],
// 方式2:通过函数返回模拟数据
"User.set": function (param, postParam) {
$.extend(user, postParam);
return [0, "OK"];
}
}
// 接口调用:
var user = callSvrSync("User.get");
callSvr("User.set", {id: user.id}, function () {
alert("修改成功!");
}, {name: "大圣"});
实例详见文件 mockdata.js。
在mockData的函数中,可以用this变量来取ajax调用参数。
要取HTTP动词可以用this.type
,值为GET/POST/PATCH/DELETE之一,从而可模拟RESTful API.
可以通过MUI.options.mockDelay设置模拟调用接口的网络延时。
@see options.mockDelay
模拟数据可直接返回[code, data]格式的JSON数组,框架会将其序列化成JSON字符串,以模拟实际场景。
如果要查看调用与返回数据日志,可在浏览器控制台中设置 MUI.options.logAction=true,在控制台中查看日志。
如果设置了MUI.callSvrExt,调用名(ac)中应包含扩展(ext)的名字,例:
MUI.callSvrExt['zhanda'] = {...};
callSvr(['token/get-token', 'zhanda'], ...);
要模拟该接口,应设置
MUI.mockData["zhanda:token/get-token"] = ...;
@see callSvrExt
也支持"default"扩展,如:
MUI.callSvrExt['default'] = {...};
callSvr(['token/get-token', 'default'], ...);
或
callSvr('token/get-token', ...);
要模拟该接口,可设置
MUI.mockData["token/get-token"] = ...;
@key ajaxOpt.jdFilter 禁用返回格式合规检查.
以下调用, 如果1.json符合[code, data]
格式, 则只返回处理data部分; 否则将报协议格式错误:
$.ajax("1.json", {dataType: "json"})
$.get("1.json", null, console.log, "json")
$.getJSON("1.json", null, console.log)
对于ajax调用($.ajax,$.get,$.post,$.getJSON等), 若明确指定dataType为"json"或"text", 且未指定jdFilter为false,
则框架按筋斗云返回格式即[code, data]
来处理只返回data部分, 不符合该格式, 则报协议格式错误.
以下调用未指定dataType, 或指定了jdFilter=false, 则不会应用筋斗云协议格式:
$.ajax("1.json")
$.get("1.json", null, console.log)
$.ajax("1.json", {jdFilter: false}) // jdFilter选项明确指定了不应用筋斗云协议格式
@param ctx {ac, tm, tv?, tv2?, noLoadingImg?}
@param rv BQP协议原始数据,如 "[0, {id: 1}]",一般是字符串,也可以是JSON对象。
@return data 按接口定义返回的数据对象,如 {id: 1}. 如果返回值===RV_ABORT,调用函数应直接返回,不回调应用层。
注意:如果callSvr设置了noex:1
选项,则当调用失败时返回false。
取服务端接口URL对应的目录。可用于拼接其它服务端资源。
相当于dirname(MUI.options.serverUrl);
例如:
serverUrl为"../jdcloud/api.php" 或 "../jdcloud/",则MUI.baseUrl返回 "../jdcloud/"
serverUrl为"http://myserver/myapp/api.php" 或 "http://myserver/myapp/",则MUI.baseUrl返回 "http://myserver/myapp/"
生成对后端调用的url.
var params = {id: 100};
var url = MUI.makeUrl("Ordr.set", params);
注意:函数返回的url是字符串包装对象,可能含有这些属性:{makeUrl=true, action?, params?}
这样可通过url.action得到原始的参数。
支持callSvr扩展,如:
var url = MUI.makeUrl('zhanda:login');
(deprecated) 为兼容旧代码,action可以是一个数组,在WUI环境下表示对象调用:
WUI.makeUrl(['Ordr', 'query']) 等价于 WUI.makeUrl('Ordr.query');
在MUI环境下表示callSvr扩展调用:
MUI.makeUrl(['login', 'zhanda']) 等价于 MUI.makeUrl('zhanda:login');
特别地, 如果action是相对路径, 或是'.php'文件, 则不会自动拼接WUI.options.serverUrl:
callSvr("./1.json"); // 如果是callSvr("1.json") 则url可能是 "../api.php/1.json"这样.
callSvr("./1.php");
@see callSvrExt
@param ac String. action, 交互接口名. 也可以是URL(比如由makeUrl生成)
@param params Object. URL参数(或称HTTP GET参数)
@param postParams Object. POST参数. 如果有该参数, 则自动使用HTTP POST请求(postParams作为POST内容), 否则使用HTTP GET请求.
@param fn Function(data). 回调函数, data参考该接口的返回值定义。
@param userOptions 用户自定义参数, 会合并到$.ajax调用的options参数中.可在回调函数中用"this.参数名"引用.
常用userOptions:
指定contentType和设置自定义HTTP头(headers)示例:
var opt = {
contentType: "text/xml",
headers: {
Authorization: "Basic aaa:bbb"
}
};
callSvr("hello", $.noop, "<?xml version='1.0' encoding='UTF-8'?><return><code>0</code></return>", opt);
想为ajax选项设置缺省值,可以用callSvrExt中的beforeSend回调函数,也可以用$.ajaxSetup,
但要注意:ajax的dataFilter/beforeSend选项由于框架已用,最好不要覆盖。
@see callSvrExt[].beforeSend (opt) 为callSvr选项设置缺省值
@return deferred对象,在Ajax调用成功后回调。
例如,
var dfd = callSvr(ac, fn1);
dfd.then(fn2);
function fn1(data) {}
function fn2(data) {}
在接口调用成功后,会依次回调fn1, fn2. 在回调函数中this表示ajax参数。例如:
callSvr(ac, function (data) {
// 可以取到传入的参数。
console.log(this.key1);
}, null, {key1: 'val1'});
(v5.4) 支持失败时回调:
var dfd = callSvr(ac);
dfd.fail(function (data) {
console.log('error', data);
console.log(this.ctx_.ret); // 和设置选项{noex:1}时回调中取MUI.lastError.ret 或 this.lastError相同。
});
@key callSvr.noex 调用接口时忽略出错,可由回调函数fn自己处理错误。
当后端返回错误时, 回调fn(false)
(参数data=false). 可通过 MUI.lastError.ret 或 this.lastError 取到返回的原始数据。
示例:
callSvr("logout");
callSvr("logout", api_logout);
function api_logout(data) {}
callSvr("login", api_login);
function api_login(data) {}
callSvr("info/hotline.php", {q: '大众'}, api_hotline);
function api_hotline(data) {}
// 也可使用makeUrl生成的URL如:
callSvr(MUI.makeUrl("logout"), api_logout);
callSvr(MUI.makeUrl("logout", {a:1}), api_logout);
callSvr("User.get", function (data) {
if (data === false) { // 仅当设置noex且服务端返回错误时可返回false
// var originalData = MUI.lastError.ret; 或
// var originalData = this.lastError;
return;
}
foo(data);
}, null, {noex:1});
@see lastError 出错时的上下文信息
框架会自动在ajaxOption中增加ctx_属性,它包含 {ac, tm, tv, tv2, ret} 这些信息。
当设置MUI.options.logAction=1时,将输出这些信息。
callSvr支持FormData对象,可用于上传文件等场景。示例如下:
@key example-upload
HTML:
file: <input id="file1" type="file" multiple>
<button type="button" id="btn1">upload</button>
JS:
jpage.find("#btn1").on('click', function () {
var fd = new FormData();
$.each(jpage.find('#file1')[0].files, function (i, e) {
fd.append('file' + (i+1), e);
});
callSvr('upload', api_upload, fd);
function api_upload(data) { ... }
});
@key callSvrExt
当调用第三方API时,也可以使用callSvr扩展来代替$.ajax调用以实现:
例:合作方接口使用HTTP协议,格式如(以生成token调用为例)
http://<Host IP Address>:<Host Port>/lcapi/token/get-token?user=用户名&password=密码
返回格式为:{code, msg, data}
成功返回:
{
"code":"0",
"msg":"success",
"data":[ { "token":"xxxxxxxxxxxxxx" } ]
}
失败返回:
{
"code":"4001",
"msg":"invalid username or password",
"data":[]
}
callSvr扩展示例:
MUI.callSvrExt['zhanda'] = {
makeUrl: function(ac, param) {
// 只需要返回接口url即可,不必拼接param
return 'http://hostname/lcapi/' + ac;
},
dataFilter: function (data) {
if ($.isPlainObject(data) && data.code !== undefined) {
if (data.code == 0)
return data.data;
if (this.noex)
return false;
app_alert("操作失败:" + data.msg, "e");
}
else {
app_alert("服务器通讯协议异常!", "e"); // 格式不对
}
}
};
在调用时,ac参数使用"{扩展名}:{调用名}"的格式:
callSvr('zhanda:token/get-token', {user: 'test', password: 'test123'}, function (data) {
console.log(data);
});
旧的调用方式ac参数使用数组,现在已不建议使用:
callSvr(['token/get-token', 'zhanda'], ...);
@key callSvrExt[].makeUrl (ac, param)
根据调用名ac生成url, 注意无需将param放到url中。
注意:
对方接口应允许JS跨域调用,或调用方支持跨域调用。
@key callSvrExt[].dataFilter (data) = null/false/data
对调用返回数据进行通用处理。返回值决定是否调用callSvr的回调函数以及参数值。
callSvr(ac, callback);
callback(data)
.callback
.callSvr(ac, callback, postData, {noex:1})
,表示由应用层回调函数来处理出错: callback(false)
。当返回false时,应用层可以通过MUI.lastError.ret
来获取服务端返回数据。
@see lastError 出错时的上下文信息
(支持版本: v3.1)
如果要修改callSvr缺省调用方法,可以改写 MUI.callSvrExt['default']。示例:
MUI.callSvrExt['default'] = {
makeUrl: function(ac) {
return '../api.php/' + ac;
},
dataFilter: function (data) {
var ctx = this.ctx_ || {};
if (data && $.isArray(data) && data.length >= 2 && typeof data[0] == "number") {
if (data[0] == 0)
return data[1];
if (this.noex)
{
return false;
}
if (data[0] == E_NOAUTH) {
// 如果支持自动重登录
//if (MUI.tryAutoLogin()) {
// $.ajax(this);
//}
// 不支持自动登录,则跳转登录页
MUI.popPageStack(0);
MUI.showLogin();
return;
}
else if (data[0] == E_AUTHFAIL) {
app_alert("验证失败,请检查输入是否正确!", "e");
return;
}
else if (data[0] == E_ABORT) {
console.log("!!! abort call");
return;
}
logError();
app_alert("操作失败:" + data[1], "e");
}
else {
logError();
app_alert("服务器通讯协议异常!", "e"); // 格式不对
}
function logError()
{
console.log("failed call");
console.log(ctx);
}
}
};
@key callSvrExt[].beforeSend (opt) 为callSvr或$.ajax选项设置缺省值
如果有ajax选项想设置,可以使用beforeSend回调,例如POST参数使用JSON格式:
MUI.callSvrExt['default'] = {
beforeSend: function (opt) {
// 示例:设置contentType
if (opt.contentType == null) {
opt.contentType = "application/json;charset=utf-8";
}
// 示例:添加HTTP头用于认证
if (g_data.auth) {
if (opt.headers == null)
opt.headers = {};
opt.headers["Authorization"] = "Basic " + g_data.auth;
}
}
}
可以从opt.ctx中取到{ac, ext, noex, dfd}等值(如opt.ctx.ac),可以从opt.url中取到{ac, params}值。
如果要设置请求的HTTP headers,可以用opt.headers = {header1: "value1", header2: "value2"}
.
更多选项参考jquery文档:jQuery.ajax的选项。
接口示例:更新订单
PATCH /orders/{ORDER_ID}
调用成功仅返回HTTP状态,无其它内容:"200 OK" 或 "204 No Content"
调用失败返回非2xx的HTTP状态及错误信息,无其它内容,如:"400 bad id"
为了处理HTTP错误码,应设置:
MUI.callSvrExt["default"] = {
beforeSend: function (opt) {
opt.handleHttpError = true;
},
dataFilter: function (data) {
var ctx = this.ctx_;
if (ctx && ctx.status) {
if (this.noex)
return false;
app_alert(ctx.statusText, "e");
return;
}
return data;
}
}
$.ajaxSetup({error: fn})
,$.ajax({error: fn})
,它会覆盖框架的处理.如果接口在出错时,返回固定格式的错误对象如{code, message},可以这样处理:
MUI.callSvrExt["default"] = {
beforeSend: function (opt) {
opt.handleHttpError = true;
},
dataFilter: function (data) {
var ctx = this.ctx_;
if (ctx && ctx.status) {
if (this.noex)
return false;
if (data && data.message) {
app_alert(data.message, "e");
}
else {
app_alert("操作失败: 服务器错误. status=" + ctx.status + "-" + ctx.statusText, "e");
}
return;
}
return data;
}
}
调用接口时,HTTP谓词可以用callSvr的userOptions中给定,如:
callSvr("orders/" + orderId, fn, postParam, {type: "PATCH"});
这种方式简单,但因调用名ac是变化的,不易模拟接口。
如果要模拟接口,可以保持调用名ac不变,像这样调用:
callSvr("orders/{id}", {id: orderId}, fn, postParam, {type: "PATCH"});
于是可以这样做接口模拟:
MUI.mockData = {
"orders/{id}": function (param, postParam) {
var ret = "OK";
// 获取资源
if (this.type == "GET") {
ret = orders[param.id];
}
// 更新资源
else if (this.type == "PATCH") {
$.extend(orders[param.id], postParam);
}
// 删除资源
else if (this.type == "DELETE") {
delete orders[param.id];
}
return [0, ret];
}
};
不过这种写法需要适配,以生成正确的URL,示例:
MUI.callSvrExt["default"] = {
makeUrl: function (ac, param) {
ac = ac.replace(/\{(\w+)\}/g, function (m, m1) {
var ret = param[m1];
assert(ret != null, "缺少参数");
delete param[m1];
return ret;
});
return "./api.php/" + ac;
}
}
支持Promise/Deferred编程风格:
var dfd = callSvr("...");
dfd.then(function (data) {
console.log(data);
})
.catch(function (err) {
app_alert(err);
})
.finally(...)
支持catch/finally等Promise类接口。接口逻辑失败时,dfd.reject()触发fail/catch链。
支持await编程风格,上例可写为:
// 使用await时callSvr调用失败是无法返回的,加{noex:1}选项可让失败时返回false
var rv = callSvr("...", $.noop, null, {noex:1});
if (rv === false) {
// 失败逻辑 dfd.catch. 取错误信息用WUI.lastError={ac, tm, tv, ret}
console.log(WUI.lastError.ret)
}
else {
// 成功逻辑 dfd.then
}
// finally逻辑
示例:
let rv = await callSvr("Ordr.query", {res:"count(*) cnt", fmt:"one"})
let cnt = rv.cnt
(v5.5) 如果ac是调用相对路径, 则直接当成最终路径, 不做url拼接处理:
callSvr("./1.json"); // 如果是callSvr("1.json") 则实际url可能是 "../api.php/1.json"这样.
callSvr("../1.php");
相当于调用
$.ajax("../1.php", {dataType: "json", success: callback})
或
$.getJSON("../1.php", callback);
注意下面调用未指定dataType, 不会按筋斗云协议格式处理:
$.ajax("../1.php", {success: callback})
@see $.ajax
@return data 原型规定的返回数据
同步模式调用callSvr.
@see callSvr
该方法已不建议使用。上传文件请用FormData。
@see example-upload callSvr
@param $iframe 一个隐藏的iframe组件.
@param callOpt 用户自定义参数. 参考callSvr的同名参数. e.g. {noex: 1}
一般对后端的调用都使用callSvr函数, 但像上传图片等操作不方便使用ajax调用, 因为要自行拼装multipart/form-data格式的请求数据.
这种情况下可以使用form的提交和一个隐藏的iframe来实现类似的调用.
先定义一个form, 在其中放置文件上传控件和一个隐藏的iframe. form的target属性设置为iframe的名字:
<form data-role="content" action="upload" method=post enctype="multipart/form-data" target="ifrUpload">
<input type=file name="file[]" multiple accept="image/*">
<input type=submit value="上传">
<iframe id='ifrUpload' name='ifrUpload' style="display:none"></iframe>
</form>
然后就像调用callSvr函数一样调用setupCallSvrViaForm:
var url = MUI.makeUrl("upload", {genThumb: 1});
MUI.setupCallSvrViaForm($frm, $frm.find("iframe"), url, onUploadComplete);
function onUploadComplete(data)
{
alert("上传成功");
}
批量调用。将若干个调用打包成一个特殊的batch调用发给服务端。
注意:
示例:
var batch = new MUI.batchCall();
callSvr("Family.query", {res: "id,name"}, api_FamilyQuery);
callSvr("User.get", {res: "id,phone"}, api_UserGet);
batch.commit();
以上两条调用将一次发送到服务端。
在批处理中,默认每条调用是一个事务,如果想把批处理中所有调用放到一个事务中,可以用useTrans选项:
var batch = new MUI.batchCall({useTrans: 1});
callSvr("Attachment.add", api_AttAdd, {path: "path-1"});
callSvr("Attachment.add", api_AttAdd, {path: "path-2"});
batch.commit();
在一个事务中,所有调用要么成功要么都取消。
任何一个调用失败,会导致它后面所有调用取消执行,且所有已执行的调用会回滚。
参数中可以引用之前结果中的值,引用部分需要用"{}"括起来,且要在opt.ref参数中指定哪些参数使用了引用:
MUI.useBatchCall();
callSvr("..."); // 这个返回值的结果将用于以下调用
callSvr("Ordr.query", {
res: "id,dscr",
status: "{$-1.status}", // 整体替换,结果可以是一个对象
cond: "id>{$-1.id}" // 部分替换,其结果只能是字符串
}, api_OrdrQuery, {
ref: ["status", "cond"] // 须在ref中指定需要处理的key
});
特别地,当get/post整个是一个字符串时,直接整体替换,无须在ref中指定,如:
callSvr("Ordr.add", $.noop, "{$-1}", {contentType:"application/json"});
以下为引用格式示例:
{$1} // 第1次调用的结果。
{$-1} // 前1次调用的结果。
{$-1.path} // 取前一次调用结果的path属性
{$1[0]} // 取第1次调用结果(是个数组)的第0个值。
{$1[0].amount}
{$-1.price * $-1.qty} // 可以做简单的数值计算
如果值计算失败,则当作"null"填充。
综合示例:
MUI.useBatchCall();
callSvr("Ordr.completeItem", $.noop, {itemId:1})
callSvr("Ordr.completeItem", $.noop, {itemId:2, qty:2})
callSvr("Ordr.calc", $.noop, {items:["{$1}", "{$2}"]}, {contentType:"application/json", ref:["items"] });
callSvr("Ordr.add", $.noop, "{$3}", {contentType:"application/json"});
@see useBatchCall
@see disableBatch
@see m_curBatch
之后的callSvr调用都加入批量操作。例:
MUI.useBatchCall();
callSvr("Family.query", {res: "id,name"}, api_FamilyQuery);
callSvr("User.get", {res: "id,phone"}, api_UserGet);
可指定多少毫秒以内的操作都使用批处理,如10ms内:
MUI.useBatchCall(null, 10);
如果MUI.disableBatch=true, 该函数不起作用。
@see batchCall
@see disableBatch
当前页面。
注意:
要查看从哪个页面来,可以用 MUI.prevPageId。
要查看最近一次调用MUI.showPage转向的页面,可以用 MUI.getToPageId().
@see prevPageId
@see getToPageId ()
上一个页面的id, 首次进入时为空.
如果为false, 则必须手工执行 MUI.showPage 来显示第一个页面。
如果指定, 则在下次showPage时生效.
初次进入App时无动画效果.
示例: 在返回上一页时指定不要动画效果:
MUI.nextShowPageOpt = {ani: 'none'};
history.back();
因为未直接调用MUI.showPage, 可以用nextShowPageOpt来传递参数. 此参数用后即焚.
设置当前地址栏显示的URL. 如果url中不带hash部分,会自动加上当前的hash.
MUI.setUrl("page/home.html"); // 设置url
MUI.setUrl("?a=1&b=2"); // 设置url参数
MUI.setUrl("?"); // 清除url参数部分。
如果要设置或删除参数,建议使用:
MUI.setUrlParam("a", 1); // 如果参数存在,则会自动覆盖。
MUI.deleteUrlParam("a"); // 从url中删除参数a部分,如果g_args中有参数a,也同时删除。
一般用于将应用程序内部参数显示到URL中,以便在刷新页面时仍然可显示相同的内容,或用于分享链接给别人。
例如订单页的URL为http://server/app/#order
,现在希望:
id=100
的订单,在URL中显示http://server/app/?orderId=100#order
id=100
的订单。示例:在逻辑页order
的pagebeforeshow
回调函数中,处理内部参数opt
或URL参数g_args
:
function initPageOrder()
{
var jpage = this;
var orderId_;
jpage.on("pagebeforeshow", onPageBeforeShow);
function onPageBeforeShow(ev, opt)
{
// 如果orderId_未变,不重新加载
var skip = false;
if (g_args.orderId) {
orderId_ = g_args.orderId;
// 只在初始进入时使用一次,用后即焚
delete g_args.orderId;
}
else if (opt.orderId) {
orderId_ = opt.orderId;
}
else {
skip = true;
}
if (! orderId_) { // 参数不合法时跳回主页。
MUI.showHome();
return;
}
if (skip)
return;
MUI.setUrl("?orderId=" + orderId_);
app_alert("show order " + orderId_);
}
}
在例子中,opt
为MUI.showPage()
时指定的参数,如调用MUI.showPage("#order", {orderId: 100});
时,opt.orderId=100
.
而g_args
为全局URL参数,如打开 http://server/app/index.html?orderId=100#order
时,g_args.orderId=100
.
注意逻辑页#order
应允许作为入口页进入,否则刷新时会跳转回主页。可在index.js中的validateEntry参数中加上逻辑页:
MUI.validateEntry([
...,
"#order"
]);
注意setUrl中以"?"开头,表示添加到URL参数中,保持URL主体部分不变。
如果MUI.options.showHash=false
,则MUI.setUrl("?orderId=100")
会将URL设置为http://server/app/page/order.html?orderId=100
.
我们甚至可以设置RESTful风格的URL: MUI.setUrl("order/100")
会将URL设置为 http://server/app/order/100
.
在上面两个例子中,为了确保刷新URL时能正常显示,必须在Web服务器上配置URL重写规则,让它们都重定向到 http://server/app/?orderId=100#order
.
自动修改g_args全局变量和当前url(会调用MUI.setUrl方法)。
MUI.deleteUrlParam("wxpay");
// 原先url为 http://myserver/myapp/index.html?wxpay=ORDR-11&storeId=1
// 调用后为 http://myserver/myapp/index.html?storeId=1
修改当前url,添加指定参数。
e.g.
MUI.setUrlParam("wxauth", 1);
@param pageId String. 页面名字. 仅由字母、数字、"_"等字符组成。
@param pageRef String. 页面引用(即location.hash),以"#"开头,后面可以是一个pageId(如"#home")或一个相对页的地址(如"#info.html", "#emp/info.html")。
如果未指定,则使用当前URL的hash或指定的主页(MUI.options.homePage). "#"表示主页。
@param opt {ani, url, backNoRefresh} (v3.3) 该参数会传递给pagebeforeshow/pageshow回调函数。
opt.ani:: String. 动画效果。设置为"none"禁用动画。默认页面由右向左进入,设置为"up"表示由下向上进入(常用于popup页面)。
opt.url:: String. 指定在地址栏显示的地址。如 showPage("#order", {url: "?id=100"})
可设置显示的URL为 page/order.html?id=100
.
@see setUrl
在应用内无刷新地显示一个页面。
例:
MUI.showPage("#order");
显示order页,先在已加载的DOM对象中找id="order"的对象,如果找不到,则尝试找名为"tpl_home"的模板DOM对象,如果找不到,则以ajax方式动态加载页面"page/order.html"。
注意:
加载成功后,会将该页面的id设置为"order",然后依次:
调用 mui-initfn中指定的初始化函数,如 initPageOrder
触发pagecreate事件
触发pagebeforeshow事件
触发pageshow事件
动态加载页面时,缺省目录名为page
,如需修改,应在初始化时设置pageFolder选项:
MUI.options.pageFolder = "mypage";
也可以显示一个指定路径的页面:
MUI.showPage("#page/order.html");
由于它对应的id是order, 在显示时,先找id="order"的对象是否存在,如果不存在,则动态加载页面"page/order.html"并为该对象添加id="order".
在HTML中, 如果标签的href属性以"#"开头,则会自动以showPage方式无刷新显示,如:
<a href="#order">order</a>
<a href="#emp/empinfo.html">empinfo</a>
可以通过mui-opt
属性设置showPage的参数(若有多项,以逗号分隔),如:
<a href="#me" mui-opt="ani:'none'">me</a>
如果不想在应用内打开页面,只要去掉链接中的"#"即可:
<a href="emp/empinfo.html">empinfo</a>
特别地,如果href属性以"#dlg"开头,则会自动以showDialog方式显示对话框,如
<a href="#dlgSetUserInfo">set user info</a>
点击后相当于调用:
MUI.showDialog(MUI.activePage.find("#dlgSetUserInfo"));
(v3.3) opt参数会传递到pagebeforeshow/pageshow参数中,如
MUI.showPage("order", {orderId: 100});
function initPageOrder()
{
var jpage = this;
jpage.on("pagebeforeshow", function (ev, opt) {
// opt={orderId: 100}
});
jpage.on("pageshow", function (ev, opt) {
// opt={orderId: 100}
});
}
(v5.2)
@param opt.backNoRefresh ?=false 从新页面返回后,不要刷新当前页
实际为A->B页面跳转后,此后若有B->A跳转,不触发A页面的pagebeforeshow事件。
在initPage时,也可直接在页面上设置: jpage.prop("backNoRefresh", ["page1", "page2"])
, 表示从page1, page2转到当前页面,不触发pagebeforeshow事件。注意,数组中保存的是pageId,不是pageRef.
(v5.4) 设置backNoRefresh选项会导致pagebeforeshow事件不触发,对于必须依赖pagebeforeshow事件的逻辑,可以监听pagebeforeshow.always
事件。
(v5.3)
支持一个页面模板可创建多个页面实例。
MUI.showPage("udt__费用");
MUI.showPage("udt__供应商");
两者用同一套html/js,但数据不会干扰。
(v5.4)
@key mui-ani 指定本页面进入时的动画效果. 支持"up"(由下向上), "pop"(fade展开)。
@key slideIn
@key slideOut
支持扩展动画效果。例如新动画名为"xx",请参考mui.css定义slideIn_xx, slideOut_xx类,即可使用:
在page上指定进入动画:
<div mui-initfn="initSynopsis" mui-script="doctorSynopsis.js" mui-ani="up">
在显示页面时指定动画:
MUI.showPage("#doctorSynopsis", {ani: "up"});
或
<a href="#doctorSynopsis" mui-opt="ani:'up'">页面1<a>
设置文档标题。默认在切换页面时,会将文档标题设置为逻辑页的标题(hd
块中的h1
或h2
标签)。
文档原始标题可通过MUI.title
获得。
@param pageRef 如未指定,表示当前页。
删除一个页面。
@param pageRef 如未指定,表示当前页。
@param opt 传递给MUI.showPage的opt参数。参考MUI.showPage.
重新加载指定页面。不指定pageRef时,重加载当前页。
页面栈,MUI.popPageStack对它操作
n=0: 退到首层, >0: 指定pop几层
常用场景:
添加订单并进入下个页面后, 点击后退按钮时避免再回到添加订单页面, 应调用
MUI.popPageStack(); // 当前页(提交订单页)被标记poped
MUI.showPage("#xxx"); // 进入下一页。之后回退时,可跳过被标记的前一页
如果添加订单有两步(两个页面),希望在下个后面后退时跳过前两个页面, 可以调用
MUI.popPageStack(2);
MUI.showPage("#xxx");
如果想在下个页面后退时直接回到初始进入应用的逻辑页(不一定是首页), 可以调用:(注意顺序!)
MUI.showPage("#xxx");
MUI.popPageStack(0); // 标记除第一页外的所有页为poped, 所以之后回退时直接回到第一页。
如果只是想立即跳回两页,不用调用popPageStack,而应调用:
history.go(-2);
@return 可以不返回, 或返回一个回调函数beforeShow, 在每次Dialog显示前调用.
使用该函数可设置dialog的初始化回调函数和beforeShow回调.
使用方法:
MUI.setupDialog(jdlg, function () {
var jdlg = this;
jdlg.find("#btnOK").click(btnOK_click);
function btnOK_click(ev) { }
function beforeShow() {
// var jdlg = this;
var jtxt = jdlg.find("#txt1");
callSvr("getxxx", function (data) {
jtxt.val(data);
});
}
return beforeShow;
});
@key #muiAlert
@param type 对话框类型: "i": info, 信息提示框; "e": error, 错误框; "w": warning, 警告框; "q": question, 确认框(会有"确定"和"取消"两个按钮); "p": prompt, 输入框
@param fn Function(text?) 回调函数,当点击确定按钮时调用。当type="p" (prompt)时参数text为用户输入的内容。
@param opt Object. 可选项。 timeoutInterval表示几秒后自动关闭对话框。defValue用于输入框(type=p)的缺省值.
opt.onCancel: 用于"q", 点取消时回调.
示例:
// 信息框,3s后自动点确定
app_alert("操作成功", function () {
MUI.showPage("#orders");
}, {timeoutInterval: 3000});
// 错误框
app_alert("操作失败", "e");
// 确认框(确定/取消)
app_alert("立即付款?", "q", function () {
MUI.showPage("#pay");
});
// 输入框
app_alert("输入要查询的名字:", "p", function (text) {
callSvr("Book.query", {cond: "name like '%" + text + "%'});
});
可自定义对话框,接口如下:
示例:
<div id="muiAlert" class="mui-dialog">
<h3 class="p-title"></h3>
<div class="p-msg"></div>
<input type="text" id="txtInput"> <!-- 当type=p时才会显示 -->
<div>
<a href="javascript:;" id="btnOK" class="mui-btn primary">确定</a>
<a href="javascript:;" id="btnCancel" class="mui-btn">取消</a>
</div>
</div>
app_alert一般会复用对话框 muiAlert, 除非层叠开多个alert, 这时将clone一份用于显示并在关闭后删除。
(v5.2)
@param opt.keep ?=false 如果设置为true,则如果已经有弹出框,重用这个框而非重新弹出一个新框。
常用于显示进度,如:
app_alert("正在处理: 0/1...");
app_alert("正在处理: 1/1...", {keep:true});
app_alert("处理完成!", {keep:true});
标识应用当前是否正在与服务端交互。一般用于自动化测试。
也常用于防止重复提交,示例:
jpage.find(".btnUpload").click(btnUpload_click);
function btnUpload_click() {
// 防止重复点击提交
if (MUI.isBusy)
return;
callSvr("upload", ...);
}
应用参数。
URL参数会自动加入该对象,例如URL为 http://{server}/{app}/index.html?orderId=10&dscr=上门洗车
,则该对象有以下值:
g_args.orderId=10; // 注意:如果参数是个数值,则自动转为数值类型,不再是字符串。
g_args.dscr="上门洗车"; // 对字符串会自动进行URL解码。
框架会自动处理一些参数:
@see parseQuery URL参数通过该函数获取。
值是一个整数,默认为0. 可用它来判断WEB应用是否在APP容器中运行。
如果非0,表示WEB应用在苹果或安卓APP中运行,且数值代表原生应用容器的版本号。
示例:检查用户APP版本是否可以使用某些插件。
if (g_cordova) { // 在原生APP中。可以使用插件。
// 假如在IOS应用的大版本3中,加入了某插件,如果用户未升级,可提示他升级:
if (g_cordova < 3 && isIOS()) {
app_alert("您的版本太旧,XX功能无法使用,请升级到最新版本");
}
}
WEB应用容器应在URL中传递cordova参数,表示容器版本号。该版本号会保存在ApiLog的ver字段中。
如果容器不支持上述约定,可在WEB应用初始化时设置g_cordova变量来做兼容,示例:
// UserAgent for infiniti app
// android example: Mozilla/5.0 ... AppVersion/1.2.4 ... AppName/dafengche+infiniti
// iphone example: Mozilla/5.0 ... Souche/Dafengche/spartner/infiniti/InfinitiInhouse/1.2.4
function initApp() {
var ua = navigator.userAgent;
var m;
if ((m = ua.match(/android.*appversion\/([\d.]+)/i)) || (m = ua.match(/iphone.*infinitiInhouse\/([\d.]+)/i))) {
MUI.options.appName = "emp-m";
var ver = m[1];
if (m = ver.match(/(\d+)\.(\d+)\.(\d+)/)) {
window.g_cordova = parseInt(m[1]) * 10000 + parseInt(m[2]) * 100 + parseInt(m[3]);
}
}
}
initApp();
@see 原生应用支持
应用全局共享数据。
在登录时,会自动设置userInfo属性为个人信息。所以可以通过 g_data.userInfo==null 来判断是否已登录。
serverRev用于标识服务端版本,如果服务端版本升级,则应用可以实时刷新以更新到最新版本。
@key g_data.userInfo
@key g_data.serverRev
@key g_data.initClient
应用初始化时,调用initClient接口得到的返回值,通常为{plugins, ...}
@key g_data.testMode,g_data.mockMode 测试模式和模拟模式
TODO: MUI.data
可用的选项如下。
@var options.appName ?=user 应用名称
用于与后端通讯时标识app.
@var options.loginPage ?="#login" login逻辑页面的地址
@var options.homePage ?="#home" 首页地址
@var options.pageFolder ?="page" 逻辑页面文件(html及js)所在文件夹
@var options.statusBarColor ?="#,light" 设置状态栏颜色,默认为应用程序背景色和白字。
@see topic-iosStatusBar
(版本v5.0)
利用statusbar插件设置标题栏。
其中背景设置使用"#000"或"#000000"这种形式,特别地,只用"#"可表示使用当前应用程序的背景色(.mui-container背景颜色)。
前景设置使用"light"(白色)或"dark"(黑色)。
设置为"none"表示隐藏标题栏。
设置为空("")表示禁止框架设置状态栏。
@var options.fixTopbarColor ?=false
如果为true, 则自动根据页面第一个hd的背景色设置手机顶栏颜色.
适合每个页面头部颜色不同的情况. 更复杂的情况, 可使用MUI.setTopbarColor
手工设置顶栏颜色.
@var options.manualSplash ?=false
@see topic-splashScreen
@var options.logAction ?=false Boolean. 是否显示详细日志。
可用于交互调用的监控。
@var options.PAGE_SZ ?=20 分页大小,下拉列表每次取数据的缺省条数。
@var options.mockDelay ?=50 模拟调用后端接口的延迟时间,单位:毫秒。仅对异步调用有效。
@see mockData 模拟调用后端接口
@var options.serverUrl ?="./" 服务端接口地址设置。
@var options.serverUrlAc 表示接口名称的URL参数。
示例:
$.extend(MUI.options, {
serverUrl: "http://myserver/myapp/api.php",
serverUrlAc: "ac"
});
接口"getuser(id=10)"的HTTP请求为:
http://myserver/myapp/api.php?ac=getuser&id=10
如果不设置serverUrlAc(默认为空),则HTTP请求为:
http://myserver/myapp/api.php/getuser?id=10
支持上面这种URL的服务端,一般配置过pathinfo机制。
再进一步,如果服务端设置了rewrite规则可以隐藏api.php,则可设置:
$.extend(MUI.options, {
serverUrl: "http://myserver/myapp/", // 最后加一个"/"
});
这样发起的HTTP请求为:
http://myserver/myapp/getuser?id=10
@var options.pluginFolder ?="../plugin" 指定筋斗云插件目录
筋斗云插件提供具有独立接口的应用功能模块,包括前端、后端实现。
@var options.showHash ?=true
默认访问逻辑页面时,URL地址栏显示为: "index.html#me"
只读,如果值为false, 则地址栏显示为: "index.html/page/me.html".
注意:该选项不可通过js设置为false,而应在主页面中设置:
<base href="./" mui-showHash="no">
在showHash=false时,必须设置base标签, 否则逻辑页将无法加载。
@var options.disableFastClick ?=false
在IOS+cordova环境下,点击事件会有300ms延迟,默认会加载lib/fastclick.min.js解决。
该库会导致部分场景下点击失效问题。这时可以通过在关键点击元素上设置"needsclick"类来解决。
例如:fastclick库与图片裁切库image-process-tool有些冲突, ios手机APP中点修改头像无法弹出图片选择框. JS初始化配置如下:
var zxImageProcess = new ZxImageProcess({
// 触发文件选择的元素
selector: jpage.find(".downSelect-btn[value=1]")[0],
...
});
最终将绑定用于点击的元素 <div class='downSelect-btn'></div>
改为 <div class='downSelect-btn needsclick'></div>
解决。
发现IOS上点击失效问题,可先设置options.disableFastClick=true
检查问题是否消失来判定。
TODO: cordova-ios未来将使用WkWebView作为容器(目前仍使用UIWebView),将不再有点击延迟问题,到时将去除FastClick库。
@var options.onAutoLogin 自动登录
@event autoLogin 自动登录事件(v5.4)
设置如何自动登录系统,进入应用后,一般会调用tryAutoLogin,其中会先尝试重用已有会话,如果当前没有会话则回调onAutoLogin自动登录系统。
返回true则跳过后面系统默认的登录过程,包括使用本地保存的token自动登录以及调用login接口。
一般用于微信认证后绑定用户身份,示例:
$.extend(MUI.options, {
...
onAutoLogin: onAutoLogin
});
function onAutoLogin()
{
// 发起微信认证
var param = {state: location.href};
location.href = "../weixin/auth.php?" + $.param(param);
// 修改了URL后直接跳出即可。不用返回true
MUI.app_abort();
}
(v5.4)也可以用autoLogin事件:
$(document).on("autoLogin", onAutoLogin);
@var options.enableWxLogin 微信认证登录
设置enableWxLogin为true,或者appName为"user",则如果URL中有参数wxCode, 就调用后端"login2(wxCode)"接口登录认证。
一般用于从微信小程序调用H5应用。
要求后端已实现login2接口。
$.extend(MUI.options, {
...
enableWxLogin: true
});
@var options.enableSwitchApp 自动保存和切换应用
同一个目录下的多个应用,支持自动切换。
例如原生APP(或微信小程序中)的URL为用户端,但在登录页或个人中心页可切换到员工端。
当进入员工端并登录成功后,希望下次打开APP后直接进入员工端,做法如下:
在H5应用中设置选项options.enableSwitchApp=true。(例如在app.js中设置,这样所有应用都允许跳转)
应用登录后将自动记录当前URL到localStorage.appPage. (可通过设置STORAGE_PREFIX实现不同应用系列的隔离)
在APP中初次打开H5应用(或在浏览器新tab页中打开)时,会在进入应用后自动检查和切换应用(将在MUI.validateEntry函数中检查,一般H5应用的主JS文件入口处默认会调用它)。
通过sessionStorage.switchApp是否有值来判定是否初次打开页面.
@var options.onShowPage (pageRef, opt) 显示页面前回调
(v5.4) 在调用MUI.showPage时触发调用,参数与MUI.showPage相同,用于显示任何页面前通用的操作。
此回调在页面加载或显示之前(先于目的页面的pagecreate/pagebeforeshow等事件)。
如果返回false,则取消本次showPage调用。
示例1:允许用户未登录使用,但除了home页面,进入其它页面均要求登录。
注意:系统默认要求登录才能进入,若要修改,可在muiInit事件中修改调用MUI.tryAutoLogin(..., allowNoLogin=true)
来实现允许未登录进入。
此需求如果放在每个页面的pagebeforeshow中处理则非常麻烦,可在onShowPage中统一处理。
$.extend(MUI.options, {
...
onShowPage: onShowPage
});
...
// MUI.tryAutoLogin(handleLogin, "User.get");
MUI.tryAutoLogin(handleLogin, "User.get", true); // 允许未登录进入。
// 如果未登录,跳转login。
function onShowPage(pageRef, opt) {
if (pageRef == "#home" || pageRef == "#setUserInfo" || pageRef.substr(0, 6) == "#login")
return;
// 如果是未登录进入,则跳转登录页。
if (!g_data.userInfo) {
MUI.showLogin();
return false;
}
}
示例2:接上例,当系统在微信中使用时,允许用户使用微信身份自动登录,并可以查看home页面。
但如果用户尚未绑定过手机号,在进入其它页面时,必须先绑定手机号。
$.extend(MUI.options, {
...
onShowPage: onShowPage
});
// 如果手机号没有填写,则要求填写并返回false。
function onShowPage(pageRef, opt) {
if (pageRef == "#home" || pageRef == "#setUserInfo" || pageRef.substr(0, 6) == "#login")
return;
// 如果是未登录进入,则跳转登录页。
if (!g_data.userInfo) {
MUI.showLogin(pageRef);
return false;
}
if (g_data.userInfo && !g_data.userInfo.phone) {
PageSetUserInfo.userInit = true;
PageSetUserInfo.fromPageRef = pageRef;
MUI.showPage("#setUserInfo");
return false;
}
}
@var options.showLoadingDelay ?= 500 延迟显示加载图标
(v5.4) 默认如果在500ms内如果远程调用成功, 则不显示加载图标.
@var options.skipErrorRegex 定义要忽略的错误
示例:有video标签时,缩小窗口或全屏预览时,有时会报一个错(见下例),暂不清楚解决方案,也不影响执行,可以先安全忽略它不要报错:
$.extend(MUI.options, {
skipErrorRegex: /ResizeObserver loop limit exceeded/i,
});
@var options.allowNoLogin (page) (v6) 返回页面是否需要登录的函数
示例:"hello"页面或"test"开头的页面无须登录可直接打开:
MUI.options.allowNoLogin = function (page) {
return page == "hello" || /^test/.test(page);
}
用于模块扩展。
// 定制模块的接口调用地址
MUI.options.moduleExt.callSvr = function (name) {
// name为callSvr调用的接口名,返回实际URL地址;示例:
var map = {
"Ordr__Mes.query" => "../../mes/api/Ordr.query",
"Ordr__Item.query" => "../../mes/api/Item.query"
}
return map[name] || name;
}
详细用法案例,可参考:筋斗云开发实例讲解 - 系统复用与微服务方案。
参数加密特性。默认为1(开启),在后端接口返回当前是测试模式时,会改为0(关闭)。
也可以在chrome控制台中直接修改,如MUI.options.xparam=0
。
带缩略图的图片编号保存风格。
@param fn Function(data); 与callSvr时的回调相同,data为服务器返回的数据。
函数中可以使用this["userPost"] 来获取post参数。
@param opt.validate: Function(jf, queryParam={ac?,...}).
如果返回false, 则取消submit. queryParam为调用参数,可以修改。
(v5.3) 支持异步提交,返回Deferred对象时,表示在Deferred.resolve之后再提交。
form提交时的调用参数, 如果不指定, 则以form的action属性作为queryParam.ac发起callSvr调用.
form提交时的POST参数,由带name属性且不带disabled属性的组件决定, 可在validate回调中设置.
设置POST参数时,固定参数可以用<input type="hidden">
标签来设置,自动计算的参数可以先放置一个隐藏的input组件,然后在validate回调中来设置。
示例:
<form action="fn1">
<input name="name" value="">
<input name="type" value="" style="display:none">
<input type="hidden" name="wantAll" value="1">
</form>
MUI.setFormSubmit(jf, api_fn1, {
validate: function(jf, queryParam) {
// 检查字段合法性
if (! isValidName(jf[0].name.value)) {
app_alert("bad name");
return false;
}
// 设置GET参数字段"cond"示例
queryParam.cond = "id=1";
// 设置POST参数字段"type"示例
jf[0].type.value = ...;
}
});
如果之前调用过setFormData(jo, data, {setOrigin:true})来展示数据, 则提交时,只会提交被修改过的字段,否则提交所有字段。
@param opt.onNoAction: Function(jf). 当form中数据没有变化时, 不做提交. 这时可调用该回调函数.
(v5.3)
异步提交示例:点击提交后,先上传照片,照片传完获取到picId,然后做之后提交动作
MUI.setFormSubmit(jf, api_fn1, {
validate: function(jf, queryParam) {
var dfd = $.Deferred();
uploadPic.submit().then(function (picId) {
jf[0].picId.value = picId;
dfd.resolve();
});
return dfd;
}
});
@param colorHex 颜色值,格式如 "#fafafa", 可用MUI.rgb2hex函数转换.
@param style dark|light
设置顶栏颜色和字体黑白风格.
用于原生应用,让顶栏颜色与页面hd部分的颜色自动保持一致。
@param page=pageRef/jpage 如果指定, 则登录成功后转向该页面; 否则转向登录前所在的页面.
显示登录页. 注意: 登录页地址通过MUI.options.loginPage指定, 缺省为"#login".
<div data-role="page" id="login">
...
</div>
注意:
显示主页。主页是通过 MUI.options.homePage 来指定的,默认为"#home".
要取主页名可以用:
var jpage = $(MUI.options.homePage);
@see options.homePage
@param dontReload 如果非0, 则注销后不刷新页面.
注销当前登录, 成功后刷新页面(除非指定dontReload=1)
设置入口页,allowedEntries是一个数组, 如果初始页面不在该数组中, 则URL中输入该逻辑页时,会自动转向主页。
示例:
MUI.validateEntry([
"#home",
"#me",
/^#udt__/ # (v5.3) 支持正则式
]);
尝试自动登录,如果失败则转到登录页(除非allowNoLogin=true)。
@param onHandleLogin Function(data). 调用后台login()成功后的回调函数(里面使用this为ajax options); 可以直接使用MUI.handleLogin
@param reuseCmd String. 当session存在时替代后台login()操作的API, 如"User.get", "Employee.get"等, 它们在已登录时返回与login相兼容的数据. 因为login操作比较重, 使用它们可减轻服务器压力.
@param allowNoLogin Boolean. 缺省未登录时会自动跳转登录页面, 如果设置为true, 如不会自动跳转登录框, 表示该应用允许未登录时使用.
@return Boolean. true=登录成功; false=登录失败.
该函数应该在muiInit事件中执行, 以避免框架页面打开主页。
$(document).on("muiInit", myInit);
function myInit()
{
// redirect to login if auto login fails
MUI.tryAutoLogin(handleLogin, "User.get");
}
function handleLogin(data)
{
MUI.handleLogin(data);
// g_data.userInfo已赋值
}
打开首页面逻辑:
@param data 调用API "login"成功后的返回数据.
处理login相关的操作, 如设置g_data.userInfo, 保存自动登录的token等等.
可以根据用户属性在此处定制home页,例如:
if(role == "SA"){
MUI.options.homePage = "#sa-home";
}
else if (role == "MA") {
MUI.options.homePage = "#ma-home";
}
@var dfdLogin
(v6) 用于在登录完成状态下执行操作的Deferred/Promise对象。
示例:若未登录,则在登录后显示消息;若已登录则直接显示消息
WUI.dfdLogin.then(function () {
app_show("hello");
});
系统支持通过URL参数lang指定语言,如指定英文版本:http://myserver/myapp/m2/index.html?lang=en
如果未指定lang参数,则根据html的lang属性来确定语言,如指定英文版:
<html lang="en">
默认为开发语言(lang="dev"),以中文为主。英文版下若想切换到开发语言,可以用http://myserver/myapp/m2/index.html?lang=dev
g_args.lang中保存着实际使用的语言。
自带英文语言翻译文件lib/lang-en.js,当lang=en时加载它。可扩展它或以它为模板创建其它语言翻译文件。
语言翻译文件中,设置全局变量LANG,将开发语言翻译为其它语言。
系统会自动为菜单项、页面标题、列表表头标题、对话框标题等查找翻译。
其它DOM组件若想支持翻译,可手工添加CSS类lang,如:
<div><label class="lang"><input type="checkbox" value="mgr">最高管理员</label></div>
<a href="javascript:logout()" class="logout"><span class="lang"><i class="icon-exit"></i>退出系统</span></a>
或在代码中,使用WUI.enhanceLang(jo)来为DOM组件支持翻译,或直接用T(str)翻译字符串。
注意lang类或enhanceLang函数不能设置组件下子组件的文字,可先取到文字组件再设置如WUI.enhanceLang(jo.find(".title"))
。
@fn T (s, defVal?) 字符串翻译
T函数用于将开发语言翻译为当前使用的语言。
@key .lang DOM组件支持翻译
@fn enhanceLang (jo) DOM组件支持翻译
原生插件与WEB接口版本匹配。
在cordova_plugins.js中使用,用于根据APP版本与当前应用标识,过滤当前Web可用的插件。
例如,从客户端(应用标识为user)版本2.0,商户端(应用标识为store)版本3.0开始,添加插件 geolocation,可配置filter如下:
module.exports = [
...
{
"file": "plugins/cordova-plugin-geolocation/www/android/geolocation.js",
"id": "cordova-plugin-geolocation.geolocation",
"clobbers": [
"navigator.geolocation"
],
"filter": [ ["user",2], ["store",3] ] // 添加filter
}
];
filterCordovaModule(module); // 过滤模块
配置后,尽管WEB已更新,但旧版本应用程序不会具有该接口。
filter格式: [ [app1, minVer?=1, maxVer?=9999], ...], 仅当app匹配且版本在minVer/maxVer之间才使用
如果未指定filter, 表示总是使用
app标识由应用定义,常用如: "user"-客户端;"store"-商户端
对obj中的以字符串表示的currency/date等类型进行转换。
判断类型的依据是属性名字,如以Tm结尾的属性(也允许带数字后缀)为日期属性,如"tm", "tm2", "createTm"都会被当作日期类型转换。
注意:它将直接修改传入的obj,并最终返回该对象。
obj = {id: 1, amount: "15.0000", payAmount: "10.0000", createTm: "2016-01-11 11:00:00"}
var order = MUI.formatField(obj); // obj会被修改,最终与order相同
// order = {id: 1, amount: 15, payAmount: 10, createTm: (datetime类型)}
返回操作,类似history.back(),但如果当前页是入口页时,即使没有前一页,也可转向pageRef页(未指定时为首页)。
一般用于顶部返回按钮:
<div class="hd">
<a href="javascript:hd_back();" class="btn-icon"><i class="icon icon-back"></i></a>
<h2>个人信息</h2>
</div>
向后端发送日志。后台必须已添加syslog插件。
日志可在后台Syslog表中查看,客户端信息可查看ApiLog表。
@param module app,fw(framework),page
@param pri ERR,INF,WARN
示例:
MUI.syslog("app", "ERR", "fail to pay: " + err.msg);
注意:如果操作失败,本函数不报错。
为列表添加下拉刷新和上拉加载功能。
例:页面元素如下:
<div mui-initfn="initPageOrders" mui-script="orders.js">
<div class="bd">
<div class="p-list"></div>
</div>
</div>
设置下拉列表的示例代码如下:
var pullListOpt = {
onLoadItem: showOrderList
};
var container = jpage.find(".bd")[0];
initPullList(container, pullListOpt);
var nextkey;
function showOrderList(isRefresh)
{
var jlst = jpage.find(".p-list");
var param = {res: "id desc", cond: "status=1"};
if (nextkey == null)
isRefresh = true;
if (isRefresh)
jlst.empty();
param.pagekey = nextkey;
callSvr("Ordr.query", param, function (data) {
// create items and append to jlst
// ....
if (data.nextkey)
nextkey = data.nextkey;
// TODO: 处理分页结束即nextkey为空的情况。
});
}
注意:
*** 由于处理分页的逻辑比较复杂,请调用 initPageList替代, 即使只有一个list;它会屏蔽nextkey, refresh等细节,并做一些优化。像这样调用:
initPageList(jpage, {
pageItf: PageOrders,
navRef: null,
listRef: jlst,
onGetQueryParam: ...
onAddItem: ...
});
本函数参数如下:
@param container 容器,它的高度应该是限定的,因而当内部内容过长时才可出现滚动条
@param opt {onLoadItem, autoLoadMore?=true, threshold?=180, onHint?, onPull?}
@param opt.onLoadItem function(isRefresh)
在合适的时机,它调用 onLoadItem(true) 来刷新列表,调用 onLoadItem(false) 来加载列表的下一页。在该回调中this为container对象(即容器)。实现该函数时应当自行管理当前的页号(pagekey)
@param opt.autoLoadMore 当滑动到页面下方时(距离底部TRIGGER_AUTOLOAD=30px以内)自动加载更多项目。
@param threshold 像素值。
手指最少下划或上划这些像素后才会触发实际加载动作。
@param opt.onHint function(ac, dy, threshold)
ac 动作。"D"表示下拉(down), "U"表示上拉(up), 为null时应清除提示效果.
dy,threshold 用户移动偏移及临界值。dy>threshold时,认为触发加载动作。
提供提示用户刷新或加载的动画效果. 缺省实现是下拉或上拉时显示提示信息。
@param opt.onHintText function(ac, uptoThreshold)
修改用户下拉/上拉时的提示信息。仅当未设置onHint时有效。onHint会生成默认提示,如果onHintText返回非空,则以返回内容替代默认内容。
内容可以是一个html字符串,所以可以加各种格式。
ac:: String. 当前动作,"D"或"U".
uptoThreshold:: Boolean. 是否达到阈值
@param opt.onPull function(ev)
如果返回false,则取消上拉加载或下拉刷新行为,采用系统默认行为。
@alias initNavbarAndList
列表页逻辑框架.
对一个导航栏(class="mui-navbar")及若干列表(class="p-list")的典型页面进行逻辑封装;也可以是若干button对应若干div-list区域,一次只显示一个区域;
特别地,也可以是只有一个list,并没有button或navbar对应。
它包括以下功能:
基本页面结构如下:
<div mui-initfn="initPageOrders" mui-script="orders.js">
<div class="hd">
<h2>订单列表</h2>
</div>
<div class="hd">
<div class="mui-navbar">
<a href="javascript:;" class="active" mui-linkto="#lst1">待服务</a>
<a href="javascript:;" mui-linkto="#lst2">已完成</a>
</div>
</div>
<div class="bd">
<div id="lst1" class="p-list active" data-cond="status='PA'"></div>
<div id="lst2" class="p-list" data-cond="status='RE'"></div>
</div>
</div>
上面页面应注意:
js调用逻辑示例:
var lstItf = initPageList(jpage, {
pageItf: PageOrders,
//以下两项是缺省值:
//navRef: ">.hd .mui-navbar",
//listRef: ">.bd .p-list",
// 设置查询参数,静态值一般通过在列表对象上设置属性 data-ac, data-cond以及data-queryParam等属性来指定更方便。
onGetQueryParam: function (jlst, queryParam) {
queryParam.ac = "Ordr.query";
queryParam.orderby = "id desc";
// queryParam.cond 已在列表data-cond属性中指定
},
onAddItem: function (jlst, itemData) {
var ji = $("<li>" + itemData.title + "</li>");
ji.appendTo(jlst);
}
});
由于指定了pageItf属性,当外部页面设置了 PageOrders.refresh = true后,再进入本页面,所有关联的列表会在展现时自动刷新。且PageOrders.refresh会被自动重置为false.
一个button对应一个list; 打开页面时只展现一个列表,点击相应按钮显示相应列表。
如果没有用navbar组件,而是一组button对应一组列表,点一个button显示对应列表,也可以使用本函数。页面如下:
<div mui-initfn="initPageOrders" mui-script="orders.js">
<div class="hd">
<h2>订单列表</h2>
</div>
<div class="bd">
<div class="p-panelHd">待服务</div>
<div class="p-panel">
<div id="lst1" class="p-list active"></div>
</div>
<div class="p-panelHd">已完成</div>
<div class="p-panel">
<div id="lst2" class="p-list"></div>
</div>
</div>
</div>
js调用逻辑示例:
jpage.find(".p-panel").height(500); // !!! 注意:必须为list container指定高度,否则无法出现下拉列表。一般根据页高自动计算。
var lstItf = initPageList(jpage, {
pageItf: PageOrders,
navRef: ".p-panelHd", // 点标题栏,显示相应列表区
listRef: ".p-panel .p-list", // 列表区
...
});
注意:navRef与listRef中的组件数目一定要一一对应。除了使用选择器,也可以直接用jQuery对象为navRef和listRef赋值。
只有一个list 的简单情况,也可以调用本函数简化分页处理.
仍考虑上例,假如那两个列表需要进入页面时就同时显示,那么可以分开一一设置如下:
jpage.find(".p-panel").height(500); // 一定要为容器设置高度
var lstItf = initPageList(jpage, {
pageItf: PageOrders,
navRef: "", // 置空,表示不需要button链接到表,下面listRef中的多表各自显示不相关。
listRef: ".p-panel .p-list", // 列表区
...
});
上例中,listRef参数也可以直接使用jQuery对象赋值。
navRef是否为空的区别是,如果非空,则表示listRef是一组互斥的列表,点击哪个button,就会设置哪个列表为active列表。当切到当前页时,只显示或刷新active列表。
如果是只包含一个列表的简单页面:
<div mui-initfn="initPageOrders" mui-script="orders.js">
<div class="hd">
<h2>订单列表</h2>
</div>
<div class="bd">
<div class="p-list"></div>
</div>
</div>
由于bd对象的高度已自动设置,要设置p-list对象支持上下拉加载,可以简单调用:
var lstItf = initPageList(jpage, {
pageItf: PageOrders,
navRef: "", // 一定置空,否则默认值是取mui-navbar
listRef: ".p-list"
...
});
原理是在合适的时机,自动调用类似这样的逻辑:
var queryParam = {ac: "Ordr.query"};
opt.onGetQueryParam(jlst, queryParam);
callSvr(queryParam.ac, queryParam, function (data) {
$.each(rs2Array(data), function (i, itemData) {
opt.onAddItem(jlst, itemData);
});
if (data.d.length == 0)
opt.onNoItem(jlst);
});
@param opt {onGetQueryParam?, onAddItem?, onNoItem?, pageItf?, navRef?=">.hd .mui-navbar", listRef?=">.bd .p-list", onBeforeLoad?, onLoad?, onGetData?, canPullDown?=true, onRemoveAll?, jContainer?}
@param opt 分页相关 { pageszName?="pagesz", pagekeyName?="pagekey", localPageSize? }
@param opt.onGetQueryParam Function(jlst, queryParam/o)
queryParam: {ac?, res?, cond?, ...}
框架在调用callSvr之前,先取列表对象jlst上的data-queryParam属性作为queryParam的缺省值,再尝试取data-ac, data-res, data-cond, data-orderby属性作为queryParam.ac等参数的缺省值,
最后再回调 onGetQueryParam。
<ul data-queryParam="{q: 'famous'}" data-ac="Person.query" data-res="*,familyName" data-cond="status='PA' and name like '王%'">
</ul>
此外,框架将自动管理 queryParam.pagekey/pagesz 参数。
@param opt.onAddItem (jlst, itemData, param)
param={idx, arr, isFirstPage}
框架调用callSvr之后,处理每条返回数据时,通过调用该函数将itemData转换为DOM item并添加到jlst中。
判断首页首条记录,可以用
param.idx == 0 && param.isFirstPage
这里无法判断是否最后一页(可在onLoad回调中判断),因为有可能最后一页为空,这时无法回调onAddItem.
@param opt.onNoItem (jlst)
当没有任何数据时,可以插入提示信息。缺省会添加"没有数据"提示, 可由CSS类noData来定制样式.
一般可全局设置 initPageList.onNoItem 回调函数.
@param opt.pageItf - page interface {refresh?/io}
在订单页面(PageOrder)修改订单后,如果想进入列表页面(PageOrders)时自动刷新所有列表,可以设置 PageOrders.refresh = true。
设置opt.pageItf=PageOrders, 框架可自动检查和管理refresh变量。
@param opt.navRef,opt.listRef 指定navbar与list,可以是选择器,也可以是jQuery对象;或是一组button与一组div,一次显示一个div;或是navRef为空,而listRef为一个或多个不相关联的list.
@param opt.onBeforeLoad (jlst, isFirstPage)->Boolean 如果返回false, 可取消load动作。参数isFirstPage=true表示是分页中的第一页,即刚刚加载数据。
@param opt.onLoad (jlst, isLastPage) 参数isLastPage=true表示是分页中的最后一页, 即全部数据已加载完。
@param opt.onGetData (data, pagesz, pagekey?) 每次请求获取到数据后回调。pagesz为请求时的页大小,pagekey为页码(首次为null). this为当前jlst
@param opt.onRemoveAll (jlst) 清空列表操作,默认为 jlst.empty()
@return PageListInterface= {refresh, markRefresh, loadMore}
@param opt.jContainer 设置列表所有的容器,默认为页面body(".bd")对象。
注意jContainer必须有固定高度(.bd会由框架自动设置高度),否则会造成无法上下拉动,除非设置了 opt.canPullDown=false。
可以对以下两个CSS class指定样式:
@key mui-pullPrompt CSS-class 下拉刷新提示块
@key mui-loadPrompt CSS-class 自动加载提示块
@key example-list-choose
常见需求:在一个页面上,希望进入另一个列表页,选择一项后返回。
可定义页面接口如下(主要是choose方法和onChoose回调):
var PageOrders = {
...
// onChoose(order={id,dscr,...})
choose: function (onChoose) {
this.chooseOpt_ = {
onChoose: onChoose
}
MUI.showPage('#orders');
},
chooseOpt_: null // {onChoose}
};
在被调用页面上:
示例:
function initPageOrders()
{
jpage.on("pagehide", onPageHide);
function li_click(ev)
{
var order = $(this).data('obj');
if (PageOrders.chooseOpt_) {
PageOrders.chooseOpt_.onChoose(order);
return false;
}
// 正常点击操作 ...
}
function onPageHide()
{
PageOrders.chooseOpt_ = null;
}
}
在调用时:
PageOrders.choose(onChoose);
function onChoose(order)
{
// 处理order
history.back(); // 由于进入列表选择时会离开当前页面,这时应返回
}
默认按BQP协议的分页机制访问服务端,其规则是:
如果不是最后一页,服务端应返回nextkey字段;返回列表的格式可以是 table格式如
{
h: [ "field1","field2" ],
d: [ ["val1","val2"], ["val3","val4"], ... ]
nextkey: 2
}
也可以用list参数指定列表,如
{
list: [
{field1: "val1", field2: "val2"},
{field1: "val3", field2: "val4"},
],
nextkey: 2
}
例1:假定后端分页机制为(jquery-easyui datagrid分页机制):
page=1&rows=20
{ total: 83, rows: [ {...}, ... ] }
适配方法为:
var listItf = initPageList(jpage, {
...
pageszName: 'rows',
pagekeyName: 'page',
// 设置 data.list, data.nextkey (如果是最后一页则不要设置); 注意pagekey可以为空
onGetData: function (data, pagesz, pagekey) {
data.list = data.rows;
if (pagekey == null)
pagekey = 1;
if (data.total > pagesz * pagekey)
data.nextkey = pagekey + 1;
}
});
@key initPageList.options initPageList默认选项
如果需要作为全局默认设置可以这样:
$.extend(MUI.initPageList.options, {
pageszName: 'rows',
onNoItem: function (jlst) { ... }
...
});
例2:假定后端分页机制为:
curPage=1&maxLine=20
返回数据通过字段curPage, countPage, investList 分别表示当前页码, 总页数,列表数据,如 { curPage:1, countPage: 5, investList: [ {...}, ... ] }
var listItf = initPageList(jpage, {
...
pageszName: 'maxLine',
pagekeyName: 'curPage',
// 设置 data.list, data.nextkey (如果是最后一页则不要设置); 注意pagekey可以为空
onGetData: function (data, pagesz, pagekey) {
data.list = data.investList;
if (data.curPage < data.countPage)
data.nextkey = data.curPage + 1;
}
});
例3:假定后端就返回一个列表如[ {...}, {...} ]
,不支持分页。
什么都不用设置,仍支持下拉刷新,因为刚好会当成最后一页处理,上拉不再加载。
@key .mui-pullHint 指定下拉提示显示位置
显示下拉刷新提示时,默认是在列表所在容器的最上端位置显示的。如果需要指定显示位置,可使用css类"mui-pullHint",示例如下:
<div class="bd">
<div>下拉列表演示</div>
<div class="mui-pullHint"></div> <!-- 如果没有这行,则下拉提示会在容器最上方,即"下拉列表演示"这行文字的上方-->
<div id="lst1"></div>
<div id="lst2"></div>
</div>
例:在多页列表中,有一些页只做静态展示使用,不需要上拉或下拉:
<div mui-initfn="initPageOrders" mui-script="orders.js">
<div class="hd">
<h2>订单列表</h2>
</div>
<div class="hd">
<div class="mui-navbar">
<a href="javascript:;" class="active" mui-linkto="#lst1">待服务</a>
<a href="javascript:;" mui-linkto="#lst2">已完成</a>
<a href="javascript:;" mui-linkto="#lst3">普通页</a>
</div>
</div>
<div class="bd">
<div id="lst1" class="p-list active" data-cond="status='PA'"></div>
<div id="lst2" class="p-list" data-cond="status='RE'"></div>
<div id="lst3" class="mui-noPull">
<p>本页面没有下拉加载或上拉刷新功能</p>
</div>
</div>
</div>
例子中使用了类"mui-noPull"来标识一个TAB页不是列表页,无需分页操作。
@key .mui-noPull 如果一个列表页项的class中指定了此项,则显示该列表页时,不允许下拉。
还可以通过设置onPull选项来灵活设置,例:
var listItf = initPageList(jpage, ...,
onPull(ev, jlst) {
if (jlst.attr("id") == "lst3")
return false;
}
);
@param opt.onPull function(ev, jlst)
jlst:: 当前活动页。函数如果返回false,则取消所有上拉加载或下拉刷新行为,使用系统默认行为。
只上拉加载,不需要下拉刷新行为。随着列表增长而自动向下滚动,在滚动到底时自动加载下一页。
这时容器允许没有固定高度,而是可禁止下拉刷新行为:
var listItf = initPageList(jpage,
...,
canPullDown: false,
);
@param opt.canPullDown ?=true 是否允许下拉刷新
设置为false时,当列表到底部时,可以自动加载下一页,但没有下拉刷新行为,这时页面容器也不需要确定高度。
@param opt.localPageSize
服务器一次性返回所有数据,在前端不想一次性全部显示,比如也按10条一页分页显示,下拉加载下一页,称为本地分页.
这个场景下可以设置opt.localPageSize=10
,示例:
var lstIf = MUI.initPageList(jpage, {
...
localPageSize: 10, // 设置本地分页
onGetQueryParam: function (jlst, queryParam) {
queryParam.ac = "Ordr.query";
...
queryParam.pagesz = -1; // 服务端不分页
},
onAddItem: onAddItem
});
也支持是远程分页+本地分页混用, 但没有意义, 容易造成错乱, 故请匆混用.
FormMode.forAdd/forSet/forFind.
TODO: example
根据当前formMode自动显示或隐藏jo下的DOM对象.
示例: 对以下DOM对象
<div id="div1">
<div id="div2"></div>
<div id="div3" class="forAdd"></div>
<div id="div4" class="forSet"></div>
<div id="div5" class="forSet forAdd"></div>
</div>
调用showByFormMode(jo, FormMode.forAdd)时, 显示 div2, div3, div5;
调用showByFormMode(jo, FormMode.forSet)时, 显示 div2, div4, div5;
详情页框架. 用于对象的添加/查看/更新/删除多合一页面.
form.action为对象名.
@param opt {pageItf, jform?=jpage.find("form:first"), onValidate?, onGetData?, onNoAction?=history.back, onAdd?, onSet?, onGet?, onDel?}
pageItf: {formMode, formData}; formData用于forSet模式下显示数据, 它必须有属性id.
Form将则以pageItf.formData作为源数据, 除非它只有id一个属性(这时将则调用callSvr获取源数据)
onValidate: Function(jform, queryParam); 提交前的验证, 或做字段补全的工作, 或补全调用参数。queryParam是查询参数,它可能包含{ac?, res?, ...},可以进行修改。(v5.3)支持返回Deferred对象做异步提交。
onGetData: Function(jform, queryParam); 在forSet模式下,如果需要取数据,则回调该函数,获取get调用的参数。
onNoAction: Function(jform); 一般用于更新模式下,当没有任何数据更改时,直接点按钮提交,其实不做任何调用, 这时将回调 onNoAction,缺省行为是返回上一页。
onAdd: Function(id); 添加完成后的回调. id为新加数据的编号.
onSet: Function(data); 更新完成后的回调, data为更新后的数据.
onGet: Function(data); 获取数据后并调用setFormData将数据显示到页面后,回调该函数, 可用于显示特殊数据.
onDel: Function(); 删除对象后回调.
示例:制作一个人物详情页PagePerson:
逻辑页面(html片段)示例如下:
<div mui-initfn="initPagePerson" mui-script="person.js">
...
<div class="bd">
<form action="Person">
编号:<input name="id" class="forSet">
<input name="name" required placeholder="输入名称">
<textarea name="dscr" placeholder="写点简介"></textarea>
<div class="forSet">人物标签</div>
<button type="submit" id="btnOK">确定</button>
<button type="button" id="btnDel">删除</button>
<input type="text" style="display:none" name="familyId">
</form>
</div>
</div>
注意:支持设置CSS类forSet,forAdd,用于标识只在更新或添加模式下使用。上例中编号id在添加时不出现,在更新时才显示。
调用initPageDetail使它成为支持添加、查看和更新的详情页:
var PagePerson = {
showForAdd: function (formData) ...
showForSet: function (formData) ...
};
function initPagePerson()
{
var jpage = this;
var pageItf = PagePerson;
var detailItf = MUI.initPageDetail(jpage, {
pageItf: pageItf, // 需要页面接口提供 formMode, formData等属性。
onValidate: function (jf) {
// 补足字段和验证字段,返回false则取消form提交。
if (pageItf.formMode == FormMode.forAdd) {
...
}
},
onAdd: function (id) {
PagePersons.show({refresh: true}); // 添加成功后跳到列表页并刷新。
},
onSet: function (data) {
app_alert("更新成功!", history.back); // 更新成功后提示信息,然后返回前一页。
},
onDel: function () {
PagePersons.show({refresh: true});
},
});
jpage.find("#btnDel").click(btnDel_click);
function btnDel_click(ev) {
app_alert("删除记录?", "q", detailItf.del.bind(detailItf));
}
}
// 其它页调用它:
PagePerson.showForAdd({familyId: 1}); // 添加人物,已设置familyId为1
PagePerson.showForSet(person); // 以person对象内容显示人物,可更新。
PagePerson.showForSet({id: 3}); // 以id=3查询人物并显示,可更新。
页面接口常常实现如下:
var PagePerson = {
// @fn PagePerson.showForAdd(formData?)
// formData={familyId, parentId?, parentOf?}
showForAdd: function(formData) {
this.formMode = FormMode.forAdd;
this.formData = formData;
MUI.showPage("#person");
},
// @fn PagePerson.showForSet(formData)
// formData={id,...}
showForSet: function (formData) {
this.formMode = FormMode.forSet;
this.formData = formData;
MUI.showPage("#person");
},
formMode: null,
formData: null,
};
对于forSet模式,框架先检查formData中是否只有id属性,如果是,则在进入页面时会自动调用{obj}.get获取数据.
<form action="Person">
<div name=familyName></div>
...
</form>
如果formData中有多个属性,则自动以formData的内容作为数据源显示页面,不再发起查询。
(v5.3) 在onValidate中返回Deferred对象,可支持异步提交。
示例:先上传完照片获得picId后,再添加或保存。
initPageDetail(jpage, {
...,
onValidate: function (jf) {
var dfd = $.Deferred();
// 上传照片完成后再提交
uploadPic.submit().then(function (picId) {
jf[0].picId.value = picId;
dfd.resolve();
});
return dfd;
},
onGet: function (data) {
// 显示照片
jpage.find(".uploadpic").attr("data-atts", data.picId);
uploadPic.reset();
},
}
@see setFormSubmit
@param jo jQuery DOM对象, 它是uploadpic类,或是包含一个或多个uploadpic类(上传区)的DOM对象。
@param opt {uploadParam?} 兼容MUI.compressImg函数opt参数,如 {quality=0.8, maxSize=1280, ...}
opt也可以是一个函数: optfn(jo) - jo为上传区, 返回该区的设置,这样就支持为每个上传区定义不同的选项。
初始化之后也可以这样为每一个上传区指定option:
var opt = MUI.getOptions(jo);
opt.xx =xxx;
@param opt.uploadParam 调用upload接口的额外参数。
目前调用筋斗云upload接口,使用参数{genThumb:1, autoResize:0}
,可以通过uploadParam指定额外参数。
注意:
引入库:由于要上传功能的页面不多,建议只在逻辑页面用到的时候引入,像这样:
<div mui-initfn="initPagePic" mui-script="pic.js" mui-deferred="loadUploadLib()">
loadUploadLib在app.js中有示例。在预览图片时,它自动检查和调用photoswipe库,优先用该库来预览。
示例HTML
<!-- 单图上传区: 比如上传用户头像。这里把预览图和文件按钮合一了,点预览图即可再选择文件 -->
<div class="uploadpic" id="userPic">
<div class="uploadpic-btn uploadpic-item">
<input type="file">
</div>
</div>
<!-- 多图上传区:比如上传产品明细图片。 -->
<div class="uploadpic" id="itemPics">
<div class="uploadpic-btn">
<input type="file" multiple>
</div>
</div>
JS
// 如果已有图片需要预览,将缩略图ID列表传入data-atts属性,在new UploadPic时会根据Id在图片预览区加上已经存在的图片
jpage.find("#userPic").attr("data-atts", "208");
jpage.find("#itemPics").attr("data-atts", "210,212,214");
也可以在上传区内放置一个带name属性的input, 框架将优先从它取值或设置值, 这样就不用手工取值和赋值了, 比如:
<div class="uploadpic">
// 这个input的value将与data-atts一致.
<input name="itemPics" style="display:none">
<div class="uploadpic-btn">
<input type="file" multiple>
</div>
</div>
// 初始化,显示预览图
var uploadPic = new MUI.UploadPic(jpage); // 可直接传uploadpic类的jQuery对象或包含它的jQuery DOM对象
// var uploadPic = new MUI.UploadPic(jpage, {maxSize:1600, uploadParam:{type:"task"}} ); // 指定选项
// 如果重新设置了data-atts属性,可调用
// uploadPic.reset();
// 点击提交时调用submit,当上传完成后,
uploadPic.submit().then(function (userPic, itemPics) {
});
// 如果要精细控制上传进度:
var dfd = uploadPic.submit(onUpload, onUploadProgress, onNoWork);
dfd.then(onUploadDone);
onUpload回调仅当需要上传照片或删除照片时调用,在照片上传完成后触发。一般用于将照片列表更新到相应对象上。如果有多个上传区更新,则会分别调用。
attIds为上传后返回的缩略图Id数组,this为当前上传区的jQuery对象。
如果函数返回一个Deferred对象(如callSvr调用),则onUploadDone(以及onUploadProgress的最后一次progress.done=true的回调)会在所有这些调用完成之后才触发。
// 每个上传区一旦有图片更新,则调用Task1.set更新图片列表。
function onUpload(attIds) {
// this对象为当前uploadpic
var pics = attIds.join(',');
var task = this.data("task_");
// 如果返回一个Deferred对象,则progress.done会等待该事件结束才发生
return callSvr("Task1.set", {id: task.id}, $.noop, {pics: pics});
}
onUploadProgress用于显示上传进度。如果未指定,框架使用默认的进度提示,同时会在console中显示上传进度。如下所示:
// progress: {curPicCnt/已上传照片数, picCnt/总共需上传的照片数, curAreaCnt/已完成的上传区数, areaCnt/总共需更新的上传区数, curSize/当前已完成的上传大小, size/总上传大小, done/是否全部完成, percent/上传完成百分数0-100}
// 示例:利用app_alert显示进度。
function onUploadProgress(progress)
{
var info = progress.picCnt>0? "正在上传... " + progress.percent + "% - " + progress.curPicCnt + "/" + progress.picCnt + "张照片": "更新照片";
var alertOpt = {keep: true};
if (progress.done) {
info += " - <b>完成!</b>";
alertOpt.timeoutInterval = 500;
}
else {
info += "...";
}
app_alert(info, alertOpt);
}
onNoWork在无任何更新时回调。这时onUpload和onUploadProgress都不会被调用到。
function onNoWork() {
app_alert("都保存好了。");
}
onUploadDone在全部上传完成后调用,参数分别为每个上传区的图片编号数组(不论该上传区是否需要更新)。
function onUploadDone(attIds, attIds1) {
// arguments
}
在预览位uploadpic-item对象上,设置了以下属性:
在上传区uploadpic对象上,私有数据存储在MUI.getOptions(jo)中:
@see compressImg
@event changepic uploadpic对应的jquery对象事件,在添加或删除图片时触发
示例:在选择图片后显示预览区,无图片则不显示
<div class="" v-show='uploadObjNum>0'>
<div class="uploadpic">...</div>
</div>
// 注意:添加多张图片时,会连续触发多次
jpage.find("#addMsg").on("changepic", function (ev) {
console.log('changepic');
vm.uploadObjNum = uploadPic.countPic();
});
清空全部图片:
uploadPic.empty();
// 等价于 uploadPic.reset(true);
修改了data-atts属性后重新刷新显示:
uploadPic.reset();
uploadPic.readonly(true);
var isReadonly = uploadPic.readonly();
要判断预览区有几张图,可以用:
var cnt = uploadPic.countPic(); // 总图片数
var oldCnt = uploadPic.countPic(1); // 已有图片数
var newCnt = uploadPic.countPic(2); // 新选择的图片数
当uploadPic包含多个上传区时,可以用filter指定之后的方法是针对哪一个区。注意filter只对下一次调用有效。
uploadPic.filter(idx).其它方法(); // idx为下标或jQuery的filter
示例:
var cnt = uploadPic.filter(0).countPic();
// 等价于 var cnt = uploadPic.filter(":eq(0)").countPic;
uploadPic.filter(".storePics").empty();