API参考 - 筋斗云前端(移动Web版)

最后更新:2023-07-13

@module MUI

1 基于逻辑页面的单网页应用

1.1 应用容器

1.2 逻辑页面

1.2.1 逻辑页内嵌style

1.2.2 逻辑页内嵌script

1.2.3 进入应用时动态显示初始逻辑页

1.2.4 在showPage过程中再显示另一个逻辑页

1.2.5 逻辑页声明依赖库

1.3 页面路由

2 服务端交互API

3 登录与退出

4 常用组件

4.1 导航栏

4.2 对话框

4.3 弹出菜单

4.4 底部导航

5 图片按需加载

6 原生应用支持

7 系统类标识

8 手势支持

9 跨域前端开发支持

10 参考文档说明

筋斗云移动UI框架 - JDCloud Mobile UI framework

1 基于逻辑页面的单网页应用

亦称“变脸式应用”。应用程序以逻辑页面(page)为基本单位,每个页面的html/js可完全分离。主要特性:

@see showPage

@see popPageStack

1.1 应用容器

@key .mui-container 应用容器。

@event muiInit () DOM事件。this为当前应用容器。

先在主应用html中,用.mui-container类标识应用容器,在运行时,所有逻辑页面都将在该对象之下。如:

<body class="mui-container">

应用初始化时会发出muiInit事件,该事件在页面加载完成($.ready)后,显示首页前调用。在这里调用MUI.showPage可动态显示首页。

1.2 逻辑页面

每个逻辑页面(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");

(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为当前页面。

1.2.1 逻辑页内嵌style

逻辑页代码片段允许嵌入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>
1.2.2 逻辑页内嵌script

逻辑页中允许但不建议内嵌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>
1.2.3 进入应用时动态显示初始逻辑页

默认进入应用时的主页为 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

1.2.4 在showPage过程中再显示另一个逻辑页

例如,进入页面后,发现如果未登录,则自动转向登录页:

function onPageBeforeShow(ev)
{
    // 登录成功后一般会设置g_data.userInfo, 如果未设置,则当作未登录
    if (g_data.userInfo == null) {
        MUI.showLogin();
        return;
    }
    // 显示该页面...
}

在pagebeforeshow事件中做页面切换,框架保证不会产生闪烁,且在新页面上点返回按钮,不会返回到旧页面。

(v5.4) 如果想在页面加载前添加处理逻辑,请参考 MUI.options.onShowPage 回调,可处理检测是否登录这类需求。

除此之外如果多次调用showPage(包括在pageshow事件中调用),一般最终显示的是最后一次调用的页面,过程中可能产生闪烁,且可能会丢失一些pageshow/pagehide事件,应尽量避免。

1.2.5 逻辑页声明依赖库

@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>
1.3 页面路由

框架支持hash路由(默认方式)和文件路由(逻辑页面文件)两种方式。

默认hash路由:

文件路由:在主页面中head标签中应添加:

<base href="./" mui-showHash="no">

之后,上面两个例子中,URL会显示为 http://server/app/page/order.htmlhttp://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重写规则。

2 服务端交互API

@see callSvr 系列调用服务端接口的方法。

3 登录与退出

框架提供MUI.showLogin/MUI.logout操作.
调用MUI.tryAutoLogin可以支持自动登录.

登录后显示的主页,登录页,应用名称等应通过MUI.options.homePage/loginPage/appName等选项设置。

@see tryAutoLogin

@see showLogin

@see logout

@see options

4 常用组件

框架提供导航栏、对话框、弹出框、弹出菜单等常用组件。

4.1 导航栏

@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>
4.2 对话框

@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 弹出菜单,也是一类特别的对话框。

4.3 弹出菜单

菜单是特殊的一类对话框。因而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>
4.4 底部导航

@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>

5 图片按需加载

仅当页面创建时才会加载。

<img src="../m/images/ui/carwash.png">

6 原生应用支持

使用MUI框架的Web应用支持被安卓/苹果原生应用加载(通过cordova技术)。

设置说明:

不同的app大版本(通过URL参数cordova=?识别)或不同平台加载的插件是不一样的,要查看当前加载了哪些插件,可以在Web控制台中执行:

cordova.require('cordova/plugin_list')

对原生应用的额外增强包括:

@key topic-splashScreen

@see options.manualSplash

@key topic-iosStatusBar

@see options.statusBarColor

可通过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类并设置好颜色,若不需要页头可以设置隐藏.

@see options.fixTopbarColor

如果希望自行设置状态栏,可以设置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事件未被回调,则出现启动页无法消失、插件调用无效、退出程序时无提示等异常。
其可能的原因是:

7 系统类标识

框架自动根据系统环境为应用容器(.mui-container类)增加以下常用类标识:

@key .mui-android 安卓系统

@key .mui-ios 苹果IOS系统

@key .mui-weixin 微信浏览器

@key .mui-cordova 原生环境

在css中可以利用它们做针对系统的特殊设置。

8 手势支持

如果使用了 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>

9 跨域前端开发支持

典型应用是, 在开发前端页面时, 本地无须运行任何后端服务器(如apache/iis/php等), 直接跨域连接远程接口进行开发.

支持直接在浏览器中打开html/js文件运行应用.
需要浏览器支持CORS相关设置. 以下以chrome为例介绍.
例如, 远程接口的基础URL地址为 http://oliveche.com/jdcloud/

这时直接在chrome中打开html文件即可连接远程接口运行起来.

10 参考文档说明

以下参考文档介绍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模块。

@fn assert(cond, dscr?)

@fn randInt(from, to)

生成指定区间的随机整数。示例:

var i = randInt(1, 10); // 1-10之间的整数,包含1或10

@fn randInt(from, to)

生成随机字符串,包含字母或数字,不包含易混淆的0或O。示例:

var dynCode = randChr(4); // e.g. "9BZ3"

@fn parseQuery(str)

解析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".

@fn tobool(v)

将字符串转成boolean值。除"0", "1"外,还可以支持字符串 "on"/"off", "true"/"false"等。

@fn reloadSite()

重新加载当前页面,但不要#hash部分。

@fn Date.format(fmt?=L)

日期对象格式化字符串。

@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"

@fn Date.addDay(n)

@fn Date.addHours(n)

@fn Date.addMin(n)

@fn Date.addMonth(n)

@fn parseTime(s)

将纯时间字符串生成一个日期对象。

var dt1 = parseTime("10:10:00");
var dt2 = parseTime("10:11");

@fn parseDate(dateStr)

将日期字符串转为日期时间格式。其效果相当于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");

@fn Date.add(sInterval, n)

为日期对象加几天/小时等。参数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

@fn Date.diff(sInterval, dtEnd)

计算日期到另一日期间的间隔,单位由sInterval指定(具体值列表参见Date.add).

var dt = new Date();
...
var dt2 = new Date();
var days = dt.diff("d", dt2); // 相隔多少天

@see Date.add

@fn getTimeDiffDscr(tm, tm1)

从tm到tm1的时间差描述,如"2分钟前", "3天前"等。

tm和tm1可以为时间对象或时间字符串

@fn WUI.getTmRange(dscr, now?)

根据时间段描述得到[起始时间,结束时间),注意结束时间是开区间(即不包含)。
假设今天是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();
});

@fn setCookie(name, value, days?=30)

设置cookie值。如果只是为了客户端长时间保存值,一般建议使用 setStorage.

@see getCookie

@see delCookie

@see setStorage

@fn getCookie(name)

取cookie值。

@see setCookie

@see delCookie

@fn delCookie(name)

删除一个cookie项。

@see getCookie

@see setCookie

@fn setStorage(name, value, useSession?=false)

使用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

@fn getStorage(name, useSession?=false)

取storage中的一项。
默认使用localStorage存储,如果useSession=true,则使用sessionStorage存储。

如果浏览器不支持Storage,则使用cookie实现.

@see setStorage

@see delStorage

@fn delStorage(name)

删除storage中的一项。

@see getStorage

@see setStorage

@fn rs2Array(rs)

@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

@fn rs2Hash(rs, key)

@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

@fn rs2MultiHash(rs, key)

数据分组(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

@fn list2varr(ls, colSep=':', rowSep=',')

将字符串代表的压缩表("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"]]

@fn objarr2list(objarr, fields, sep=':', sep2=',')

将对象数组转成字符串代表的压缩表("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; });
// 结果同上

@fn intSort(a, b)

整数排序. 用于datagrid column sorter:

<th data-options="field:'id', sortable:true, sorter:intSort">编号</th>

@fn numberSort(a, b)

小数排序. 用于datagrid column sorter:

<th data-options="field:'score', sortable:true, sorter:numberSort">评分</th>

@fn getAncestor(o, fn)

取符合条件(fn)的对象,一般可使用$.closest替代

@fn appendParam(url, param)

示例:

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";

@fn deleteParam(url, paramName)

示例:

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"

@fn isWeixin()

当前应用运行在微信中。

@fn isIOS()

当前应用运行在IOS平台,如iphone或ipad中。

@fn isAndroid()

当前应用运行在安卓平台。

@fn parseValue(str)

如果str符合整数或小数,则返回相应类型。

@fn applyTpl(tpl, data)

对模板做字符串替换

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>

@fn delayDo(fn, delayCnt?=3)

设置延迟执行。当delayCnt=1时与setTimeout效果相同。
多次置于事件队列最后,一般3次后其它js均已执行完毕,为idle状态

@fn kvList2Str(kv, sep, sep2)

e.g.

var str = kvList2Str({"CR":"Created", "PA":"Paid"}, ';', ':');
// str="CR:Created;PA:Paid"

@fn parseKvList(kvListStr, sep, sep2, doReverse?) -> kvMap

解析key-value列表字符串,返回kvMap。

示例:

var map = parseKvList("CR:新创建;PA:已付款", ";", ":");
// map: {"CR": "新创建", "PA":"已付款"}

var map = parseKvList("CR:新创建;PA:已付款", ";", ":", true);
// map: {"新创建":"CR", "已付款":"PA"}

@fn Q(str, q?="'")

Q("abc") -> 'abc'
Q("a'bc") -> 'a\'bc'

@fn text2html(str, pics)

将文本或图片转成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">

@fn extendNoOverride(a, b, ...)

var a = {a: 1};
WUI.extendNoOverride(a, {b: 'aa'}, {a: 99, b: '33', c: 'bb'});
// a = {a: 1, b: 'aa', c: 'bb'}

@fn jdModule(name?, fn?)

定义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, ... }

@fn jdPush(app, handleMsg, opt?={user, url, dataType})

支持出错自动重连的websocket client. 默认连接同域下的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

@fn getFormData(jo, doGetAll)

取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

@fn getFormData_vf(jo)

专门取虚拟字段的值。例如:

<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

@fn formItems(jo, cb)

表单对象遍历。对表单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();
    }
});

@fn jQuery.fn.gn(name?)

按名字访问组件(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);

@fn setFormData(jo, data?, opt?)

用于为带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

@fn loadScript(url, fnOK?, ajaxOpt?)

@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() 异步处理。

常见用法:

示例:在菜单中加一项“工单工时统计”,动态加载并执行一个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")

等改好了再拷贝到真正想放置这块代码的地方。修改已有的框架中函数也可以这样来快速迭代。

@fn evalOptions(_source, ctx?, errPrompt?)

执行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,而是直接抛出错误。

@fn loadJson(url, fnOK, options)

从远程获取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

@fn loadCss(url)

动态加载css文件, 示例:

WUI.loadCss("lib/bootstrap.min.css");

@fn setDateBox(jo, defDateFn?)

设置日期框, 如果输入了非法日期, 自动以指定日期(如未指定, 用当前日期)填充.

setDateBox($("#txtComeDt"), function () { return genDefVal()[0]; });

@fn setTimeBox(jo, defTimeFn?)

设置时间框, 如果输入了非法时间, 自动以指定时间(如未指定, 用当前时间)填充.

setTimeBox($("#txtComeTime"), function () { return genDefVal()[1]; });

@fn waitFor(deferredObj)

用于简化异步编程. 可将不易读的回调方式改写为易读的顺序执行方式.

var dfd = $.getScript("http://...");
function onSubmit()
{
    dfd.then(function () {
        foo();
        bar();
    });
}

可改写为:

function onSubmit()
{
    if (waitFor(dfd)) return;
    foo();
    bar();
}

@fn rgb(r,g,b)

生成"#112233"形式的颜色值.

rgb(255,255,255) -> "#ffffff"

@fn rgb2hex(rgb)

将jquery取到的颜色转成16进制形式,如:"rgb(4, 190, 2)" -> "#04be02"

示例:

var color = rgb2hex( $(".mui-container").css("backgroundColor") );

@fn jQuery.fn.jdata(val?)

和使用$.data()差不多,更好用一些. 例:

$(o).jdata().hello = 100;
$(o).jdata({hello:100, world:200});

@fn compressImg(img, cb, opt)

通过限定图片大小来压缩图片,用于图片预览和上传。
不支持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等。

@fn triggerAsync(jo, ev, paramArr)

触发含有异步操作的事件,在异步事件完成后继续。兼容同步事件处理函数,或多个处理函数中既有同步又有异步。
返回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);
});

@fn $.Deferred

@alias Promise

兼容Promise的接口,如then/catch/finally

@fn makeTree(arr, idField="id", fatherIdField="fatherId", childrenField="children")

将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}
    ]
]

@fn evalAttr(jo, name)

返回一个属性做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

@fn app_abort()

中止之后的调用, 直接返回.

@class DirectReturn

直接返回. 用法:

throw new DirectReturn();

可直接调用app_abort();

@fn windowOnError()

框架自动在脚本出错时弹框报错,并上报服务端(syslog).
可通过WUI.options.skipErrorRegex忽略指定错误。

@var m_enhanceFn

@fn enhanceWithin(jparent)

@fn getOptions(jo, defVal?)

第一次调用,根据jo上设置的data-options属性及指定的defVal初始化,或为{}
存到jo.prop("muiOptions")上。之后调用,直接返回该属性。

@see evalAttr

@fn getQueryCond(kvList)

@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

@fn getQueryParam(kvList)

根据键值对生成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的当前查询参数

@fn doSpecial(jo, filter, fn, cnt=5, interval=2s)

连续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);

@fn execCopy(text)

复制到剪贴板。

@var lastError = ctx

出错时,取出错调用的上下文信息。

ctx: {ac, tm, tv, ret}

@var disableBatch ?= false

设置为true禁用batchCall, 仅用于内部测试。

@var m_curBatch

当前batchCall对象,用于内部调试。

@var mockData 模拟调用后端接口。

在后端接口尚无法调用时,可以配置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 $.ajax

@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选项明确指定了不应用筋斗云协议格式

@fn enterWaiting(ctx?)

@param ctx {ac, tm, tv?, tv2?, noLoadingImg?}

@fn leaveWaiting(ctx?)

@fn defDataProc(rv)

@param rv BQP协议原始数据,如 "[0, {id: 1}]",一般是字符串,也可以是JSON对象。

@return data 按接口定义返回的数据对象,如 {id: 1}. 如果返回值===RV_ABORT,调用函数应直接返回,不回调应用层。

注意:如果callSvr设置了noex:1选项,则当调用失败时返回false。

@fn getBaseUrl()

取服务端接口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/"

@fn makeUrl(action, params?)

生成对后端调用的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

@fn callSvr(ac, [params?], fn?, postParams?, userOptions?) -> deferredObject

1 调用监控

2 文件上传支持(FormData)

3 callSvr扩展

4 适配RESTful API

5 ES6支持:jQuery的$.Deferred兼容Promise接口 / 使用await

6 直接取json类文件

@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 出错时的上下文信息

1 调用监控

框架会自动在ajaxOption中增加ctx_属性,它包含 {ac, tm, tv, tv2, ret} 这些信息。
当设置MUI.options.logAction=1时,将输出这些信息。

2 文件上传支持(FormData)

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) { ... }
});

3 callSvr扩展

@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);

当返回false时,应用层可以通过MUI.lastError.ret来获取服务端返回数据。

@see lastError 出错时的上下文信息

@key callSvrExt['default']

(支持版本: 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的选项。

4 适配RESTful API

接口示例:更新订单

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;
    }
}

如果接口在出错时,返回固定格式的错误对象如{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;
    }
}

5 ES6支持:jQuery的$.Deferred兼容Promise接口 / 使用await

支持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

6 直接取json类文件

(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

@fn callSvrSync(ac, [params?], fn?, postParams?, userOptions?)

@return data 原型规定的返回数据

同步模式调用callSvr.

@see callSvr

@fn setupCallSvrViaForm($form, $iframe, url, fn, callOpt)

该方法已不建议使用。上传文件请用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("上传成功");
}

@class batchCall(opt?={useTrans?=0})

批量调用。将若干个调用打包成一个特殊的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

@fn useBatchCall(opt?={useTrans?=0}, tv?=0)

之后的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

@var activePage

当前页面。

注意:

要查看从哪个页面来,可以用 MUI.prevPageId。
要查看最近一次调用MUI.showPage转向的页面,可以用 MUI.getToPageId().

@see prevPageId

@see getToPageId ()

@var prevPageId

上一个页面的id, 首次进入时为空.

@var container

应用容器,一般就是$(document.body)

@see .mui-container

@var showFirstPage?=true

如果为false, 则必须手工执行 MUI.showPage 来显示第一个页面。

@var nextShowPageOpt

如果指定, 则在下次showPage时生效.
初次进入App时无动画效果.

示例: 在返回上一页时指定不要动画效果:

MUI.nextShowPageOpt = {ani: 'none'};
history.back();

因为未直接调用MUI.showPage, 可以用nextShowPageOpt来传递参数. 此参数用后即焚.

@fn setUrl(url)

设置当前地址栏显示的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,现在希望:

示例:在逻辑页orderpagebeforeshow回调函数中,处理内部参数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_);
    }
}

在例子中,optMUI.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.

@fn deleteUrlParam(param)

自动修改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

@fn setUrlParam(param, val)

修改当前url,添加指定参数。
e.g.

MUI.setUrlParam("wxauth", 1);

@see deleteUrlParam MUI.appendParam

@fn showPage(pageId/pageRef?, opt?)

@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>

@fn setDocTitle(title)

设置文档标题。默认在切换页面时,会将文档标题设置为逻辑页的标题(hd块中的h1h2标签)。

文档原始标题可通过MUI.title获得。

@fn unloadPage(pageRef?)

@param pageRef 如未指定,表示当前页。

删除一个页面。

@fn reloadPage(pageRef?, opt?)

@param pageRef 如未指定,表示当前页。

@param opt 传递给MUI.showPage的opt参数。参考MUI.showPage.

重新加载指定页面。不指定pageRef时,重加载当前页。

@var m_pageStack

页面栈,MUI.popPageStack对它操作

@fn popPageStack(n?=1)

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);

@fn getToPageId()

返回最近一次调用MUI.showPage时转向页面的Id.

@see prevPageId

@fn showDialog(jdlg)

@fn closeDialog(jdlg, remove=false)

@fn setupDialog(jdlg, initfn)

@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;
});

@fn app_alert(msg, [type?=i], [fn?], opt?={timeoutInterval, defValue, onCancel(), keep})

@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});

@fn showLoading()

@fn hideLoading()

@var title

文档原始标题保存在MUI.title,在切换逻辑页面时,document.title会自动变更为当前页标题。

@see setDocTitle

@var isBusy

标识应用当前是否正在与服务端交互。一般用于自动化测试。
也常用于防止重复提交,示例:

jpage.find(".btnUpload").click(btnUpload_click);
function btnUpload_click() {
    // 防止重复点击提交
    if (MUI.isBusy)
        return;
    callSvr("upload", ...);
}

@var g_args

应用参数。

URL参数会自动加入该对象,例如URL为 http://{server}/{app}/index.html?orderId=10&dscr=上门洗车,则该对象有以下值:

g_args.orderId=10; // 注意:如果参数是个数值,则自动转为数值类型,不再是字符串。
g_args.dscr="上门洗车"; // 对字符串会自动进行URL解码。

框架会自动处理一些参数:

@see parseQuery URL参数通过该函数获取。

@var g_cordova

值是一个整数,默认为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 原生应用支持

@var g_data = {userInfo?, serverRev?, initClient?}

应用全局共享数据。

在登录时,会自动设置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

可用的选项如下。

@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 自动保存和切换应用

@key g_args.enableSwitchApp =1 应用自动切换

同一个目录下的多个应用,支持自动切换。
例如原生APP(或微信小程序中)的URL为用户端,但在登录页或个人中心页可切换到员工端。
当进入员工端并登录成功后,希望下次打开APP后直接进入员工端,做法如下:

在H5应用中设置选项options.enableSwitchApp=true。(例如在app.js中设置,这样所有应用都允许跳转)
应用登录后将自动记录当前URL。

在APP中初次打开H5应用(history.length<=1)时,会在进入应用后自动检查和切换应用(将在MUI.validateEntry函数中检查,一般H5应用的主JS文件入口处默认会调用它)。
最好在URL中添加参数enableSwitchApp=1强制检查,例如在chrome中初次打开页面history.length为2,不加参数就无法自动切换H5应用。

@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);
}

@var MUI.options.moduleExt

用于模块扩展。

// 定制模块的接口调用地址
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;
}

详细用法案例,可参考:筋斗云开发实例讲解 - 系统复用与微服务方案。

@var MUI.options.xparam

参数加密特性。默认为1(开启),在后端接口返回当前是测试模式时,会改为0(关闭)。
也可以在chrome控制台中直接修改,如MUI.options.xparam=0

@var MUI.options.useNewThumb

带缩略图的图片编号保存风格。

@fn setFormSubmit(jf, fn?, opt?={validate?, onNoAction?})

@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;
    }
});

@fn MUI.setTopbarColor(colorHex, style?)

@param colorHex 颜色值,格式如 "#fafafa", 可用MUI.rgb2hex函数转换.

@param style dark|light

设置顶栏颜色和字体黑白风格.

@fn fixTopbarColor()

用于原生应用,让顶栏颜色与页面hd部分的颜色自动保持一致。

@fn showLogin(page?)

@param page=pageRef/jpage 如果指定, 则登录成功后转向该页面; 否则转向登录前所在的页面.

显示登录页. 注意: 登录页地址通过MUI.options.loginPage指定, 缺省为"#login".

<div data-role="page" id="login">
...
</div>

注意:

@fn showHome()

显示主页。主页是通过 MUI.options.homePage 来指定的,默认为"#home".

要取主页名可以用:

var jpage = $(MUI.options.homePage);

@see options.homePage

@fn logout(dontReload?)

@param dontReload 如果非0, 则注销后不刷新页面.

注销当前登录, 成功后刷新页面(除非指定dontReload=1)

@fn validateEntry(@allowedEntries) 入口页检查

设置入口页,allowedEntries是一个数组, 如果初始页面不在该数组中, 则URL中输入该逻辑页时,会自动转向主页。

示例:

MUI.validateEntry([
    "#home",
    "#me",
    /^#udt__/  # (v5.3) 支持正则式
]);

@fn tryAutoLogin(onHandleLogin, reuseCmd?, allowNoLogin?=false)

尝试自动登录,如果失败则转到登录页(除非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已赋值
}

打开首页面逻辑:

@fn handleLogin(data)

@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");
});

@var LANG 多国语言支持/翻译

系统支持通过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组件支持翻译

@fn initClient(param?)

@class Plugins

@fn Plugins.exists(pluginName)

@fn Plugins.list()

@fn filterCordovaModule(module)

原生插件与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"-商户端

@fn formatField(obj) -> obj

对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类型)}

@fn hd_back(pageRef?)

返回操作,类似history.back(),但如果当前页是入口页时,即使没有前一页,也可转向pageRef页(未指定时为首页)。
一般用于顶部返回按钮:

<div class="hd">
    <a href="javascript:hd_back();" class="btn-icon"><i class="icon icon-back"></i></a>
    <h2>个人信息</h2>
</div>

@fn syslog(module, pri, content)

向后端发送日志。后台必须已添加syslog插件。
日志可在后台Syslog表中查看,客户端信息可查看ApiLog表。

@param module app,fw(framework),page

@param pri ERR,INF,WARN

示例:

MUI.syslog("app", "ERR", "fail to pay: " + err.msg);

注意:如果操作失败,本函数不报错。

@fn initPullList(container, opt)

为列表添加下拉刷新和上拉加载功能。

例:页面元素如下:

<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为空的情况。
    });
}

注意:

本函数参数如下:

@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,则取消上拉加载或下拉刷新行为,采用系统默认行为。

@fn initPageList(jpage, opt) -> PageListInterface

1 例:一个navbar与若干list的组合

2 例:若干button与若干list的组合

3 例:只有一个list

4 框架基本原理

5 参数说明

6 css类

7 列表页用于选择

8 分页机制与后端接口适配

9 下拉刷新提示信息

10 禁止下拉和上拉行为

11 仅自动加载,禁止下拉刷新行为

12 本地分页

@alias initNavbarAndList

列表页逻辑框架.

对一个导航栏(class="mui-navbar")及若干列表(class="p-list")的典型页面进行逻辑封装;也可以是若干button对应若干div-list区域,一次只显示一个区域;
特别地,也可以是只有一个list,并没有button或navbar对应。

它包括以下功能:

  1. 首次进入页面时加载默认列表
  2. 任一列表支持下拉刷新,上拉加载(自动管理刷新和分页)
  3. 点击导航栏自动切换列表,仅当首次显示列表时刷新数据
  4. 支持强制刷新所有列表的控制,一般定义在page接口中,如 PageOrders.refresh

1 例:一个navbar与若干list的组合

基本页面结构如下:

<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.

2 例:若干button与若干list的组合

一个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赋值。

3 例:只有一个list

只有一个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"
    ...
});

4 框架基本原理

原理是在合适的时机,自动调用类似这样的逻辑:

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);
});

5 参数说明

@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。

6 css类

可以对以下两个CSS class指定样式:

@key mui-pullPrompt CSS-class 下拉刷新提示块

@key mui-loadPrompt CSS-class 自动加载提示块

7 列表页用于选择

@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(); // 由于进入列表选择时会离开当前页面,这时应返回
}

8 分页机制与后端接口适配

默认按BQP协议的分页机制访问服务端,其规则是:

例1:假定后端分页机制为(jquery-easyui datagrid分页机制):

适配方法为:

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:假定后端分页机制为:

例3:假定后端就返回一个列表如[ {...}, {...} ],不支持分页。
什么都不用设置,仍支持下拉刷新,因为刚好会当成最后一页处理,上拉不再加载。

9 下拉刷新提示信息

@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>

10 禁止下拉和上拉行为

例:在多页列表中,有一些页只做静态展示使用,不需要上拉或下拉:

<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,则取消所有上拉加载或下拉刷新行为,使用系统默认行为。

11 仅自动加载,禁止下拉刷新行为

只上拉加载,不需要下拉刷新行为。随着列表增长而自动向下滚动,在滚动到底时自动加载下一页。
这时容器允许没有固定高度,而是可禁止下拉刷新行为:

var listItf = initPageList(jpage, 
    ...,
    canPullDown: false,
);

@param opt.canPullDown ?=true 是否允许下拉刷新

设置为false时,当列表到底部时,可以自动加载下一页,但没有下拉刷新行为,这时页面容器也不需要确定高度。

12 本地分页

@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
});

也支持是远程分页+本地分页混用, 但没有意义, 容易造成错乱, 故请匆混用.

@var FormMode

FormMode.forAdd/forSet/forFind.

TODO: example

@fn showByFormMode(jo, formMode)

根据当前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;

@fn initPageDetail(jpage, opt) -> PageDetailInterface={refresh(), del()}

详情页框架. 用于对象的添加/查看/更新/删除多合一页面.
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

@class MUI.UploadPic(jo, opt/optfn)

1 清空与重置

2 设置只读,不可添加删除图片

3 获取图片数

4 指定上传区操作

@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();
});

1 清空与重置

清空全部图片:

uploadPic.empty();
// 等价于 uploadPic.reset(true);

修改了data-atts属性后重新刷新显示:

uploadPic.reset();

2 设置只读,不可添加删除图片

uploadPic.readonly(true);
var isReadonly = uploadPic.readonly();

3 获取图片数

要判断预览区有几张图,可以用:

var cnt = uploadPic.countPic(); // 总图片数
var oldCnt = uploadPic.countPic(1); // 已有图片数
var newCnt = uploadPic.countPic(2); // 新选择的图片数

4 指定上传区操作

当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();
Generated by jdcloud-gendoc