HBuilderX

HBuilderX

极客开发工具
uni-app

uni-app

开发一次,多端覆盖
uniCloud

uniCloud

云开发平台
HTML5+

HTML5+

增强HTML5的功能体验
MUI

MUI

上万Star的前端框架

原生定位 - wap2app教程

定位 wap2app

场景说明

有定位需求的M站,通过手机浏览器访问时,经常因为定位问题导致用户体验下降、甚至用户流失,主要有以下几种情况:

  • 部分手机不支持HTML5定位,因网络原因,非国产手机HTML5定位失败的概率更高
  • 每次打开不同的M站,手机浏览器均会弹框提示定位权限,用户可能会误点击导致定位失败
  • HTML5定位效率较低,重依赖定位的业务,在定位成功之前,无法显示业务数据,导致用户长时间等待

如下截图是大家经常碰见的情况:

wap2app运行在5+引擎环境下,5+引擎可以调用系统原生的定位,定位效率及精度远高于浏览器的HTML5定位,因此建议在有定位需求的页面,将HTML5定位替换升级为5+的原生定位。

升级方案

我们在GitHub上开源了一个plusto项目,该项目可以根据平台实现API的自动转换,比如在5+引擎环境下,将浏览器默认定位升级为5+原生定位,实现一套代码平滑迁移至多个平台。

目前关于定位的替换API已完善,参考plusGeolocation.js文件,该js文件判断在5+引擎环境下,将navigator.geolocation.getCurrentPosition()等方法重写为原生定位的方法,这样开发者就无需修改M站定位相关逻辑,就可以自动更换为原生定位。

目前geolocation/h5toplus.js已实现对如下3个HTML5定位方法的重写替换:

  • navigator.geolocation.getCurrentPosition()
  • navigator.geolocation.watchPosition()
  • navigator.geolocation.clearwatch()

开发者只需按照如下步骤操作,即可将浏览器的HTML5定位升级为原生定位:

  1. 下载plusGeolocation.js(下载地址),并上传到M站的cdn服务器上
  2. 在M站上有定位需求的页面引用plusGeolocation.js,例如:
<script src="http://cdn.example.com/js/plusGeolocation.js" type="text/javascript" charset="utf-8"></script>

Tips1: plusGeolocation.js 是通过重写navigator.geolocation API来升级定位功能的,因此需确保在调用HTML5定位代码之前引用plusGeolocation.js;

Tips2:h5toplus.js文件开头就有运行环境的判断,若不是5+环境,则直接return,不会执行替换定位的他逻辑,因此开发者无需担心影响原有定位业务。

    if(!navigator.userAgent.match(/Html5Plus/i)) {  
        //非5+引擎环境,直接return;  
        return;  
    }
继续阅读 »

场景说明

有定位需求的M站,通过手机浏览器访问时,经常因为定位问题导致用户体验下降、甚至用户流失,主要有以下几种情况:

  • 部分手机不支持HTML5定位,因网络原因,非国产手机HTML5定位失败的概率更高
  • 每次打开不同的M站,手机浏览器均会弹框提示定位权限,用户可能会误点击导致定位失败
  • HTML5定位效率较低,重依赖定位的业务,在定位成功之前,无法显示业务数据,导致用户长时间等待

如下截图是大家经常碰见的情况:

wap2app运行在5+引擎环境下,5+引擎可以调用系统原生的定位,定位效率及精度远高于浏览器的HTML5定位,因此建议在有定位需求的页面,将HTML5定位替换升级为5+的原生定位。

升级方案

我们在GitHub上开源了一个plusto项目,该项目可以根据平台实现API的自动转换,比如在5+引擎环境下,将浏览器默认定位升级为5+原生定位,实现一套代码平滑迁移至多个平台。

目前关于定位的替换API已完善,参考plusGeolocation.js文件,该js文件判断在5+引擎环境下,将navigator.geolocation.getCurrentPosition()等方法重写为原生定位的方法,这样开发者就无需修改M站定位相关逻辑,就可以自动更换为原生定位。

目前geolocation/h5toplus.js已实现对如下3个HTML5定位方法的重写替换:

  • navigator.geolocation.getCurrentPosition()
  • navigator.geolocation.watchPosition()
  • navigator.geolocation.clearwatch()

开发者只需按照如下步骤操作,即可将浏览器的HTML5定位升级为原生定位:

  1. 下载plusGeolocation.js(下载地址),并上传到M站的cdn服务器上
  2. 在M站上有定位需求的页面引用plusGeolocation.js,例如:
<script src="http://cdn.example.com/js/plusGeolocation.js" type="text/javascript" charset="utf-8"></script>

Tips1: plusGeolocation.js 是通过重写navigator.geolocation API来升级定位功能的,因此需确保在调用HTML5定位代码之前引用plusGeolocation.js;

Tips2:h5toplus.js文件开头就有运行环境的判断,若不是5+环境,则直接return,不会执行替换定位的他逻辑,因此开发者无需担心影响原有定位业务。

    if(!navigator.userAgent.match(/Html5Plus/i)) {  
        //非5+引擎环境,直接return;  
        return;  
    }
收起阅读 »

原生分享 - wap2app教程

原生分享 wap2app

体验差距

因web能力限制,M站仅支持wap方式的分享,分享体验很糟糕,如下是一种典型实现(参考下方截图):

  • 点击微信分享后,显示一个二维码,用户需要启动微信扫描二维码,先在微信中打开这篇文章,然后再通过微信右上角的菜单分享出去;分享路径太长,操作麻烦;
  • 点击微博分享,需要登录微博wap站,完成授权后才能分享

wap2app运行在5+ 引擎下,是可以通过HTML5+的share模块直接调起系统原生分享的,同样场景,稍作改造,在5+引擎环境下调用原生分享,则体验会大大改观,如下为调用原生分享后的截图:

5+引擎还可以调起系统支持的更多分享,比如微博、QQ、短信、邮件等,如下为点击“更多分享”后的示例:

很明显,通过5+引擎调起原生分享后,分享路径更短、体验更好,更有利于分享内容的传播。

改造方案

要实现如上的分享体验,开发者只需要对M站稍作修改,判断是5+引擎的环境下,调用HTML5+的share模块API即可实现。

为简化开发,DCloud封装了plusShare.js函数,开发者引入该函数后,只需调用一个API,即可完成原生分享的改造。

引用plusShare.js文件

对M站上有分享功能的页面引入plusShare.js,js下载地址:GitHub,建议放在M站的cdn服务器上,如下:

<script src="http://cdn.example.com/js/plusShare.js" type="text/javascript" charset="utf-8"></script>

修改点击分享的实现

修改分享按钮的点击事件,假设之前的分享按钮点击实现如下:

document.getElementById("share").addEventListener("click", function() {  
    //原有wap分享实现  
});

引入plusShare后,参考如下方式修改代码即可:

    document.getElementById("share").addEventListener("click", function() {  
        if(navigator.userAgent.indexOf("Html5Plus") > -1) {  
            //5+ 原生分享  
            window.plusShare({  
                title: "my-app-name",//应用名字  
                content: "分享具体内容",  
                href: location.href,//分享出去后,点击跳转地址  
                thumbs: ["http://m.example.com/imgs/1.png"] //分享缩略图  
            }, function(result) {  
                //分享回调  
            });  
        } else {  
            //原有wap分享实现   
        }  
    });

注意:

  • 具体分享内容,开发者可以根据M站业务自定义设置,比如动态读取当前页面顶部大图作为thumbs参数
  • plusShare.js的具体用户参考plusShare教程
继续阅读 »

体验差距

因web能力限制,M站仅支持wap方式的分享,分享体验很糟糕,如下是一种典型实现(参考下方截图):

  • 点击微信分享后,显示一个二维码,用户需要启动微信扫描二维码,先在微信中打开这篇文章,然后再通过微信右上角的菜单分享出去;分享路径太长,操作麻烦;
  • 点击微博分享,需要登录微博wap站,完成授权后才能分享

wap2app运行在5+ 引擎下,是可以通过HTML5+的share模块直接调起系统原生分享的,同样场景,稍作改造,在5+引擎环境下调用原生分享,则体验会大大改观,如下为调用原生分享后的截图:

5+引擎还可以调起系统支持的更多分享,比如微博、QQ、短信、邮件等,如下为点击“更多分享”后的示例:

很明显,通过5+引擎调起原生分享后,分享路径更短、体验更好,更有利于分享内容的传播。

改造方案

要实现如上的分享体验,开发者只需要对M站稍作修改,判断是5+引擎的环境下,调用HTML5+的share模块API即可实现。

为简化开发,DCloud封装了plusShare.js函数,开发者引入该函数后,只需调用一个API,即可完成原生分享的改造。

引用plusShare.js文件

对M站上有分享功能的页面引入plusShare.js,js下载地址:GitHub,建议放在M站的cdn服务器上,如下:

<script src="http://cdn.example.com/js/plusShare.js" type="text/javascript" charset="utf-8"></script>

修改点击分享的实现

修改分享按钮的点击事件,假设之前的分享按钮点击实现如下:

document.getElementById("share").addEventListener("click", function() {  
    //原有wap分享实现  
});

引入plusShare后,参考如下方式修改代码即可:

    document.getElementById("share").addEventListener("click", function() {  
        if(navigator.userAgent.indexOf("Html5Plus") > -1) {  
            //5+ 原生分享  
            window.plusShare({  
                title: "my-app-name",//应用名字  
                content: "分享具体内容",  
                href: location.href,//分享出去后,点击跳转地址  
                thumbs: ["http://m.example.com/imgs/1.png"] //分享缩略图  
            }, function(result) {  
                //分享回调  
            });  
        } else {  
            //原有wap分享实现   
        }  
    });

注意:

  • 具体分享内容,开发者可以根据M站业务自定义设置,比如动态读取当前页面顶部大图作为thumbs参数
  • plusShare.js的具体用户参考plusShare教程
收起阅读 »

plusShare教程 - 分享到微信好友、朋友圈、微博等

分享 Share

简介

plusShare是基于HTML5+的share模块封装的社交分享函数,开发者只需调用一个API,即可调起微信好友、微信朋友圈、系统更多分享功能。

plusShare目前支持:

  • 自动获取分享服务
  • 自动检测微信是否已安装
  • 设置分享消息标题、内容、缩略图、链接地址

plusShare的开源地址为GitHub

API介绍

plusShare只有一个方法,如下调用即可:

plusShare(message,callback);

其中:

  • message:分享内容设置
  • callback:分享结果回调

message

分享消息内容,类型为Object,主要包括如下属性:

  • title:分享消息的标题,类型为String,目前仅分享到微信好友时支持。
  • content:分享消息的文字内容,类型为String
  • href:分享的页面链接(用户点击消息时的跳转地址),类型为String
  • thumbs:分享消息的缩略图,类型为Array;

callback

分享结束的回调函数,函数包含一个参数res,boolean类型,分别表示:

  • true:分享成功
  • false:分享失败

备注:系统分享(更多分享)暂不支持判断分享是否成功

代码示例

如下是一个示例代码:

document.getElementById("share").addEventListener("click", function() {  
    //分享内容,开发者可自定义  
    var message = {  
        title: "plusShare示例", //应用名字  
        content: "plusShare基于HTML5+的share模块,开发者只需调用一个API,即可调起微信好友、微信朋友圈、系统更多分享功能",  
        href: "http://www.dcloud.io/hellomui", //分享出去后,点击跳转地址  
        thumbs: ["http://img-cdn-qiniu.dcloud.net.cn/icon3.png"] //分享缩略图  
    }  
    //调起分享  
    plusShare(message, function(res) {  
        //分享回调函数  
        if(res) {  
            plus.nativeUI.toast("分享成功");  
        } else {  
            plus.nativeUI.toast("分享失败");  
        }  
    })  
});

真机运行,点击分享到微信消息、微信朋友圈结果如下:

点击“更多分享”,然后选择短信,结果如下:

继续阅读 »

简介

plusShare是基于HTML5+的share模块封装的社交分享函数,开发者只需调用一个API,即可调起微信好友、微信朋友圈、系统更多分享功能。

plusShare目前支持:

  • 自动获取分享服务
  • 自动检测微信是否已安装
  • 设置分享消息标题、内容、缩略图、链接地址

plusShare的开源地址为GitHub

API介绍

plusShare只有一个方法,如下调用即可:

plusShare(message,callback);

其中:

  • message:分享内容设置
  • callback:分享结果回调

message

分享消息内容,类型为Object,主要包括如下属性:

  • title:分享消息的标题,类型为String,目前仅分享到微信好友时支持。
  • content:分享消息的文字内容,类型为String
  • href:分享的页面链接(用户点击消息时的跳转地址),类型为String
  • thumbs:分享消息的缩略图,类型为Array;

callback

分享结束的回调函数,函数包含一个参数res,boolean类型,分别表示:

  • true:分享成功
  • false:分享失败

备注:系统分享(更多分享)暂不支持判断分享是否成功

代码示例

如下是一个示例代码:

document.getElementById("share").addEventListener("click", function() {  
    //分享内容,开发者可自定义  
    var message = {  
        title: "plusShare示例", //应用名字  
        content: "plusShare基于HTML5+的share模块,开发者只需调用一个API,即可调起微信好友、微信朋友圈、系统更多分享功能",  
        href: "http://www.dcloud.io/hellomui", //分享出去后,点击跳转地址  
        thumbs: ["http://img-cdn-qiniu.dcloud.net.cn/icon3.png"] //分享缩略图  
    }  
    //调起分享  
    plusShare(message, function(res) {  
        //分享回调函数  
        if(res) {  
            plus.nativeUI.toast("分享成功");  
        } else {  
            plus.nativeUI.toast("分享失败");  
        }  
    })  
});

真机运行,点击分享到微信消息、微信朋友圈结果如下:

点击“更多分享”,然后选择短信,结果如下:

收起阅读 »

个推+nettysocketio实现IM即时通讯

WEBSOCKET

目标
技术选型
前端
后端java
MAVEN配置
离线消息

相关材料
目标
实现WEB和APP通用的即时通讯功能。一份代码同时支持各种WEB浏览器以及IOS,Android环境下的APP

技术选型
使用基于netty的nettysocketio实现,后台为JS,前台为HTML5页面,部署时采用nginx中转,java部署在tomcat上

前端
var socket
if(/^plus|android/.test(this.agent)) {
//android的默认浏览器内核不支持websocket10草案
socket = socketIO.connect(LContext.imUrl, {
transports: ['polling']
})
} else {
socket = socketIO.connect(LContext.imUrl)
}
socket.on('reconnect_attempt', function() {
socket.io.opts.transports = ['polling']
})
socket.on('connect', function() {
var data = lpage.user.getData()
////console.log('连接成功..')
////console.log('<span class="connect-msg">Client has connected to the server!</span>\n' + JSON.stringify(data))
//登记当前会话关联的用户
socket.emit('startConnection', {
extra: {
id: data.id,
token: data.token
}
})
})
socket.on('startConnection', function() {
////console.log('连接到服务器')
lpage.socket = socket
// document.querySelector('header .mui-title').innerText = lpage.fromUserName
})
//注册事件,服务器通过事件调用本方法
socket.on('push', function(data, ackCallback) {
////console.log('接收消息' + JSON.stringify(data))
lpage.addNews(data)
if(ackCallback) {
////console.log('接收到消息后,返回消息给服务器确认')
ackCallback('返回到服务器的确认消息')
}
})
socket.on('disconnect', function() {
////console.log('断开连接')
lpage.socket = undefined
setTimeout(function() {
lpage.initConnection()
}, 5000)
//5秒后尝试恢复连接
// document.querySelector('header .mui-title').innerText = '未连接(点击重新连接)'
})
// socket.on('error', (error) => {
// ////console.log('error' + JSON.stringify(error))
// })
//
// socket.on('connect_error', (error) => {
// ////console.log('error' + JSON.stringify(error))
// })
//
// socket.on('connect_timeout', (error) => {
// ////console.log('error' + JSON.stringify(error))
// })
awk
后端java
@Service
public class IMHandleService implements ApplicationListener<ContextRefreshedEvent> {
Logger logger = Logger.getLogger(IMHandleService.class);
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
logger.info("运行init event on IMHandleService");
if (event.getApplicationContext().getParent() != null)// root
// application
// context
// 没有parent,他就是老大.
{
// 需要执行的逻辑代码,当spring容器初始化完成后就会执行该方法。
logger.info("\n\n\n\n\n__\n\n\n已经加载了ContextRefreshedEvent\n\n\n\n");
return;
}
// 或者下面这种方式
// if (event.getApplicationContext().getDisplayName().equals("Root
// WebApplicationContext")) {
// System.out.println("\n\n\n
\n\n加载一次的 \n\n ____\n\n\n\n");
//
// logger.info("\n\n\n\n\n__\n\n\n加载一次的
// \n\n_____\n\n");
// }
Configuration config = new Configuration();
config.setHostname("localhost");
config.setPort(10086);
config.setUpgradeTimeout(60000000);// 设置websocket过期时间
// config.setFirstDataTimeout(60000000);
// config.setPingInterval(28);
SocketIOServer server = new SocketIOServer(config);
logger.info(server.getConfiguration().getFirstDataTimeout() + " " + server.getConfiguration().getPingInterval()

  • " " + server.getConfiguration().getUpgradeTimeout() + " "
  • server.getConfiguration().getPingTimeout());
    server.addConnectListener(new ConnectListener() {// 添加客户端连接监听器
    @Override
    public void onConnect(SocketIOClient client) {
    logger.info(client.getRemoteAddress().toString() + " " + client.getTransport().getValue() + " "
    • client.getHandshakeData().getUrl() + " 接入" + client.getSessionId());
      // 调用客户端的事件
      client.sendEvent("startConnection", "hello");
      }
      });
      server.addDisconnectListener(new DisconnectListener() {
      @Override
      public void onDisconnect(SocketIOClient client) {
      logger.info(client.getRemoteAddress().toString() + " " + client.getTransport().getValue() + " "
    • client.getHandshakeData().getUrl() + " 断开" + client.getSessionId());
      // 清理连接的session
      // 检测登录状态,已登录
      // 保存当前会话
      String userId = sessionUserMap.get("sessionUserMap", client.getSessionId().toString());
      if (userId == null) {
      return;
      }
      // 清除在线状态
      sessionUserMap.delete("sessionUserMap", client.getSessionId().toString());
      userChatSession.remove(SESSION_STORAGE_PREFIX + userId, client.getSessionId().toString());
      logger.info("清理session userId= " + userId);
      logger.info("session列表长度 " + userChatSession.members(SESSION_STORAGE_PREFIX + userId).toArray().length);
      }
      });
      /**
      • 监听事件,客户端连接成功后调用本方法
        */
        server.addEventListener("startConnection", Operation.class, new DataListener<Operation>() {
        @Override
        public void onData(final SocketIOClient client, Operation data, AckRequest ackRequest) {
        if (data.extra == null || data.extra.id == null || data.extra.token == null
        || !tokenManager.checkToken(new TokenModel(data.extra.id.toString(), data.extra.token))) {
        return;
        }
        // 检测登录状态,已登录
        // 保存当前会话
        sessionUserMap.put("sessionUserMap", client.getSessionId().toString(), data.extra.id.toString());
        userChatSession.add(SESSION_STORAGE_PREFIX + data.extra.id.toString(),
        client.getSessionId().toString());
        logger.info("确认连接成功,绑定userId= " + data.extra.id.toString() + " sessionId "
    • client.getSessionId().toString());
      logger.info("session列表长度 "
    • userChatSession.members(SESSION_STORAGE_PREFIX + data.extra.id.toString()).toArray().length);
      // 握手
      // if (data.getMessage().equals("hello")) {
      // Long userid = data.getUser();
      // logger.info(Thread.currentThread().getName() +
      // "web读取到的userid:" + userid);
      // // send message back to client with ack callback
      // // WITH data
      // client.sendEvent("push", new
      // AckCallback<String>(String.class) {
      // @Override
      // public void onSuccess(String result) {
      // logger.info("ack from client: " + client.getSessionId() + "
      // data: " + result);
      // }
      // }, context.SESSION_TIME);
      //
      // } else {
      // logger.info("行情接收到了不应该有的web客户端请求1111...");
      // }
      }
      });
      /**
      • 监听事件,客户端通过事件调用本方法
        */
        server.addEventListener("message", Operation.class, new DataListener<Operation>() {
        @Override
        public void onData(final SocketIOClient client, Operation data, AckRequest ackRequest) {
        // 握手
        if (data.getMessage().equals("hello")) {
        Long userid = data.getTargetuser();
        logger.info(Thread.currentThread().getName() + "web读取到的userid:" + userid);
        // send message back to client with ack callback
        // WITH data
        client.sendEvent("push", new AckCallback<String>(String.class) {
        @Override
        public void onSuccess(String result) {
        logger.info("接收到客户端反馈 " + client.getSessionId() + " data: " + result);
        }
        });
        } else {
        logger.info("接收到了不应该有的web客户端请求...");
        }
        }
        });
        if (serverBuff == null) {
        logger.info("初始化 IMHandleService的 监听server");
        try {
        server.start();
        serverBuff = server;
        } catch (Exception e) {
        e.printStackTrace();
        logger.error("\n\nIM服务器启动失败...\n\n");
        }
        // try {
        // Thread.sleep(Integer.MAX_VALUE);
        // } catch (InterruptedException e) {
        // // TODO Auto-generated catch block
        // e.printStackTrace();
        // }
        // server.stop();
        } else {
        logger.info(" IMHandleService的 监听server已启动");
        }
        }
        @PreDestroy
        public void closeSocket() {
        logger.info(" IMHandleService 关闭中...");
        if (serverBuff != null) {
        serverBuff.stop();
        for (String sessionId : sessionUserMap.keys("sessionUserMap")) {
        String userId = sessionUserMap.get("sessionUserMap", sessionId);
        userChatSession.remove(SESSION_STORAGE_PREFIX + userId, sessionId);
        sessionUserMap.delete("sessionUserMap", sessionId);
        logger.info("清理session " + userId + " 当前长度为" + sessionUserMap.keys("sessionUserMap").size() + " "
    • userChatSession.size(SESSION_STORAGE_PREFIX + userId));
      }
      logger.info(" IMHandleService 已关闭完成");
      }
      }
      }
      http
      MAVEN配置
      <dependency>
      <groupId>com.corundumstudio.socketio</groupId>
      <artifactId>netty-socketio</artifactId>
      <version>1.7.12</version>
      </dependency>
      xml
      离线消息
      对于所有消息,默认都通过在线服务发送,当发送失败时,启用个推的JAVA SDK进行消息推送即可,具体参考个推官网


在WEB和IOS设备上都可以正常使用,但是在androidAPP中会出现一分钟左右就断线一次的问题。最后在一篇博客上找到了问题所在,由于android的默认浏览器内核支持的websocket版本为10,而Netty的默认实现目前还只支持到7.6,如果有兴趣自己升级的话,可以参考netty升级websocket草案10,

本文链接:http://blog.betweenfriends.cn/post/imnettysocketio.html

继续阅读 »

目标
技术选型
前端
后端java
MAVEN配置
离线消息

相关材料
目标
实现WEB和APP通用的即时通讯功能。一份代码同时支持各种WEB浏览器以及IOS,Android环境下的APP

技术选型
使用基于netty的nettysocketio实现,后台为JS,前台为HTML5页面,部署时采用nginx中转,java部署在tomcat上

前端
var socket
if(/^plus|android/.test(this.agent)) {
//android的默认浏览器内核不支持websocket10草案
socket = socketIO.connect(LContext.imUrl, {
transports: ['polling']
})
} else {
socket = socketIO.connect(LContext.imUrl)
}
socket.on('reconnect_attempt', function() {
socket.io.opts.transports = ['polling']
})
socket.on('connect', function() {
var data = lpage.user.getData()
////console.log('连接成功..')
////console.log('<span class="connect-msg">Client has connected to the server!</span>\n' + JSON.stringify(data))
//登记当前会话关联的用户
socket.emit('startConnection', {
extra: {
id: data.id,
token: data.token
}
})
})
socket.on('startConnection', function() {
////console.log('连接到服务器')
lpage.socket = socket
// document.querySelector('header .mui-title').innerText = lpage.fromUserName
})
//注册事件,服务器通过事件调用本方法
socket.on('push', function(data, ackCallback) {
////console.log('接收消息' + JSON.stringify(data))
lpage.addNews(data)
if(ackCallback) {
////console.log('接收到消息后,返回消息给服务器确认')
ackCallback('返回到服务器的确认消息')
}
})
socket.on('disconnect', function() {
////console.log('断开连接')
lpage.socket = undefined
setTimeout(function() {
lpage.initConnection()
}, 5000)
//5秒后尝试恢复连接
// document.querySelector('header .mui-title').innerText = '未连接(点击重新连接)'
})
// socket.on('error', (error) => {
// ////console.log('error' + JSON.stringify(error))
// })
//
// socket.on('connect_error', (error) => {
// ////console.log('error' + JSON.stringify(error))
// })
//
// socket.on('connect_timeout', (error) => {
// ////console.log('error' + JSON.stringify(error))
// })
awk
后端java
@Service
public class IMHandleService implements ApplicationListener<ContextRefreshedEvent> {
Logger logger = Logger.getLogger(IMHandleService.class);
@Override
public void onApplicationEvent(ContextRefreshedEvent event) {
logger.info("运行init event on IMHandleService");
if (event.getApplicationContext().getParent() != null)// root
// application
// context
// 没有parent,他就是老大.
{
// 需要执行的逻辑代码,当spring容器初始化完成后就会执行该方法。
logger.info("\n\n\n\n\n__\n\n\n已经加载了ContextRefreshedEvent\n\n\n\n");
return;
}
// 或者下面这种方式
// if (event.getApplicationContext().getDisplayName().equals("Root
// WebApplicationContext")) {
// System.out.println("\n\n\n
\n\n加载一次的 \n\n ____\n\n\n\n");
//
// logger.info("\n\n\n\n\n__\n\n\n加载一次的
// \n\n_____\n\n");
// }
Configuration config = new Configuration();
config.setHostname("localhost");
config.setPort(10086);
config.setUpgradeTimeout(60000000);// 设置websocket过期时间
// config.setFirstDataTimeout(60000000);
// config.setPingInterval(28);
SocketIOServer server = new SocketIOServer(config);
logger.info(server.getConfiguration().getFirstDataTimeout() + " " + server.getConfiguration().getPingInterval()

  • " " + server.getConfiguration().getUpgradeTimeout() + " "
  • server.getConfiguration().getPingTimeout());
    server.addConnectListener(new ConnectListener() {// 添加客户端连接监听器
    @Override
    public void onConnect(SocketIOClient client) {
    logger.info(client.getRemoteAddress().toString() + " " + client.getTransport().getValue() + " "
    • client.getHandshakeData().getUrl() + " 接入" + client.getSessionId());
      // 调用客户端的事件
      client.sendEvent("startConnection", "hello");
      }
      });
      server.addDisconnectListener(new DisconnectListener() {
      @Override
      public void onDisconnect(SocketIOClient client) {
      logger.info(client.getRemoteAddress().toString() + " " + client.getTransport().getValue() + " "
    • client.getHandshakeData().getUrl() + " 断开" + client.getSessionId());
      // 清理连接的session
      // 检测登录状态,已登录
      // 保存当前会话
      String userId = sessionUserMap.get("sessionUserMap", client.getSessionId().toString());
      if (userId == null) {
      return;
      }
      // 清除在线状态
      sessionUserMap.delete("sessionUserMap", client.getSessionId().toString());
      userChatSession.remove(SESSION_STORAGE_PREFIX + userId, client.getSessionId().toString());
      logger.info("清理session userId= " + userId);
      logger.info("session列表长度 " + userChatSession.members(SESSION_STORAGE_PREFIX + userId).toArray().length);
      }
      });
      /**
      • 监听事件,客户端连接成功后调用本方法
        */
        server.addEventListener("startConnection", Operation.class, new DataListener<Operation>() {
        @Override
        public void onData(final SocketIOClient client, Operation data, AckRequest ackRequest) {
        if (data.extra == null || data.extra.id == null || data.extra.token == null
        || !tokenManager.checkToken(new TokenModel(data.extra.id.toString(), data.extra.token))) {
        return;
        }
        // 检测登录状态,已登录
        // 保存当前会话
        sessionUserMap.put("sessionUserMap", client.getSessionId().toString(), data.extra.id.toString());
        userChatSession.add(SESSION_STORAGE_PREFIX + data.extra.id.toString(),
        client.getSessionId().toString());
        logger.info("确认连接成功,绑定userId= " + data.extra.id.toString() + " sessionId "
    • client.getSessionId().toString());
      logger.info("session列表长度 "
    • userChatSession.members(SESSION_STORAGE_PREFIX + data.extra.id.toString()).toArray().length);
      // 握手
      // if (data.getMessage().equals("hello")) {
      // Long userid = data.getUser();
      // logger.info(Thread.currentThread().getName() +
      // "web读取到的userid:" + userid);
      // // send message back to client with ack callback
      // // WITH data
      // client.sendEvent("push", new
      // AckCallback<String>(String.class) {
      // @Override
      // public void onSuccess(String result) {
      // logger.info("ack from client: " + client.getSessionId() + "
      // data: " + result);
      // }
      // }, context.SESSION_TIME);
      //
      // } else {
      // logger.info("行情接收到了不应该有的web客户端请求1111...");
      // }
      }
      });
      /**
      • 监听事件,客户端通过事件调用本方法
        */
        server.addEventListener("message", Operation.class, new DataListener<Operation>() {
        @Override
        public void onData(final SocketIOClient client, Operation data, AckRequest ackRequest) {
        // 握手
        if (data.getMessage().equals("hello")) {
        Long userid = data.getTargetuser();
        logger.info(Thread.currentThread().getName() + "web读取到的userid:" + userid);
        // send message back to client with ack callback
        // WITH data
        client.sendEvent("push", new AckCallback<String>(String.class) {
        @Override
        public void onSuccess(String result) {
        logger.info("接收到客户端反馈 " + client.getSessionId() + " data: " + result);
        }
        });
        } else {
        logger.info("接收到了不应该有的web客户端请求...");
        }
        }
        });
        if (serverBuff == null) {
        logger.info("初始化 IMHandleService的 监听server");
        try {
        server.start();
        serverBuff = server;
        } catch (Exception e) {
        e.printStackTrace();
        logger.error("\n\nIM服务器启动失败...\n\n");
        }
        // try {
        // Thread.sleep(Integer.MAX_VALUE);
        // } catch (InterruptedException e) {
        // // TODO Auto-generated catch block
        // e.printStackTrace();
        // }
        // server.stop();
        } else {
        logger.info(" IMHandleService的 监听server已启动");
        }
        }
        @PreDestroy
        public void closeSocket() {
        logger.info(" IMHandleService 关闭中...");
        if (serverBuff != null) {
        serverBuff.stop();
        for (String sessionId : sessionUserMap.keys("sessionUserMap")) {
        String userId = sessionUserMap.get("sessionUserMap", sessionId);
        userChatSession.remove(SESSION_STORAGE_PREFIX + userId, sessionId);
        sessionUserMap.delete("sessionUserMap", sessionId);
        logger.info("清理session " + userId + " 当前长度为" + sessionUserMap.keys("sessionUserMap").size() + " "
    • userChatSession.size(SESSION_STORAGE_PREFIX + userId));
      }
      logger.info(" IMHandleService 已关闭完成");
      }
      }
      }
      http
      MAVEN配置
      <dependency>
      <groupId>com.corundumstudio.socketio</groupId>
      <artifactId>netty-socketio</artifactId>
      <version>1.7.12</version>
      </dependency>
      xml
      离线消息
      对于所有消息,默认都通过在线服务发送,当发送失败时,启用个推的JAVA SDK进行消息推送即可,具体参考个推官网


在WEB和IOS设备上都可以正常使用,但是在androidAPP中会出现一分钟左右就断线一次的问题。最后在一篇博客上找到了问题所在,由于android的默认浏览器内核支持的websocket版本为10,而Netty的默认实现目前还只支持到7.6,如果有兴趣自己升级的话,可以参考netty升级websocket草案10,

本文链接:http://blog.betweenfriends.cn/post/imnettysocketio.html

收起阅读 »

寻青岛可以做承接APP开发的团队或个人

外包

要求青岛本地,项目比较急,有意者联系 QQ 106865801

要求青岛本地,项目比较急,有意者联系 QQ 106865801

分享mui启动第三方应用遇到的坑 android&ios

if ( plus.os.name == "Android" ) {  
                plus.runtime.launchApplication( {  
                    pname:"com.tencent.mm"}, function ( e ) {  
                    plus.nativeUI.confirm( "检查到您未安装\"微信\",是否到商城搜索下载?", function(i){  
                        if ( i.index == 0 ) {  
                            androidMarket( "com.tencent.mm" );  
                        }  
                    } );  
                } );  
            } else if ( plus.os.name == "iOS" ) {  
                plus.runtime.launchApplication( {  
                    action:"weixin://RnUbAwvEilb1rU9g9yBU"}, function ( e ) {  
                    plus.nativeUI.confirm( "检查到您未安装\"微信\",是否到商城搜索下载?", function(i){  
                        if ( i.index == 0 ) {  
                            i   osAppstore( "itunes.apple.com/cn/app/wechat/id414478124?mt=8" );  
                        }  
                    } );  
                } );  
            }

上边例子是启动微信的,如果是自己公司开发的app 就换成响应的
android和ios不同点
1 pname 和 action
2 android用包名启动 ios用scheme启动

继续阅读 »
if ( plus.os.name == "Android" ) {  
                plus.runtime.launchApplication( {  
                    pname:"com.tencent.mm"}, function ( e ) {  
                    plus.nativeUI.confirm( "检查到您未安装\"微信\",是否到商城搜索下载?", function(i){  
                        if ( i.index == 0 ) {  
                            androidMarket( "com.tencent.mm" );  
                        }  
                    } );  
                } );  
            } else if ( plus.os.name == "iOS" ) {  
                plus.runtime.launchApplication( {  
                    action:"weixin://RnUbAwvEilb1rU9g9yBU"}, function ( e ) {  
                    plus.nativeUI.confirm( "检查到您未安装\"微信\",是否到商城搜索下载?", function(i){  
                        if ( i.index == 0 ) {  
                            i   osAppstore( "itunes.apple.com/cn/app/wechat/id414478124?mt=8" );  
                        }  
                    } );  
                } );  
            }

上边例子是启动微信的,如果是自己公司开发的app 就换成响应的
android和ios不同点
1 pname 和 action
2 android用包名启动 ios用scheme启动

收起阅读 »

找个兼职的MUI前端 稳定长期合作的

招聘

来个能稳定长期合作的联系下QQ16800606
太黑的离谱的就算了哦~

项目会出好设计搞,需要用MUI实现
接口会提供接口文档

来个能稳定长期合作的联系下QQ16800606
太黑的离谱的就算了哦~

项目会出好设计搞,需要用MUI实现
接口会提供接口文档

模板事件 - NView模板 - wap2app教程

wap2app 点击事件 NView模板

onclick事件

在NView模板中,很多控件都可以通过onclick属性配置控件的点击事件,配置方式如下:

<element onclick={this.func_name}></element>

func_name是一个函数,需要在<script>节点下的methods中声明,示例如下:

<script>  
    module.exports = {  
        data: {},//数据  
        methods: {  
            func_name: function(event) {   
                //点击事件触发  
            }  
        }  
    };  
</script>

点击事件的回调函数自带一个event参数,通过event.detail可以访问当前NView模板中绑定的数据(即data对象),如下是一个完整示例,点击按钮,打印当前的产品名称和版本号:

<template>  
    <nviews cachemaxage="86400">  
        <nview style="height:100px">  
            <canvas>  
                <button onclick={this.clickBtn}>按钮</button>  
            </canvas>  
        </nview>  
    </nviews>  
</template>  
<script>  
    module.exports = {  
        data: {  
            name: "wap2app",  
            version: "1.0.0"  
        },  
        methods: {  
            clickBtn: function(event) { //按钮点击事件触发  
                var data = event.detail;  
                console.log("name:" + data.name + ",version:" + data.version);  
            }  
        }  
    };  
</script>

NView模板中,列表组件(<list>)和流式布局控件(<richtext>)的点击事件相对特殊,下文分别介绍。

list组件

<list>标签的onclick点击事件同上,但列表项<item>的点击事件相对特殊,如果列表项是通过n-for指令循环渲染的,则在点击回调函数中,除了event参数外,还有item、index两个参数,分别表示当前列表项的数据及列表项索引,详细参考NView模板绘制原生列表教程

richtext布局控件

<richtext>布局控件也支持监听点击事件,配置方式同上;但<richtext>内部嵌套的子控件不支持监听onclick事件,用户点击<richtext>内部控件时,会向上冒泡到父容器<richtext>标签上,触发<richtext>的onclick回调。

同普通控件的点击回调,<richtext>的点击回调函数,自带一个event参数,通过event.detail可以访问当前NView模板中绑定的数据(即data对象);另外,还可以通过event访问点击目标对象的属性(tagName、href、src),具体情况如下:

  • 如果点击的是<richtext>中的<a>标签,则回调函数event参数中包含tagName(值为"a")和href属性;
  • 如果点击的是<richtext>中的<img>标签,则回调函数event参数中包含tagName(值为"img")和src属性;
  • 如果点击的是<richtext>中的其它区域或控件,则回调函数event参数中包含tagName(值为"")

如下是一个示例代码:

<template>  
    <nviews cachemaxage="86400">  
        <nview style="height:100px;" >  
            <richtext onclick={this.handleRichText}>  
                <img src="logo.png" width="20px" height="20px"></img>  
                <a href="http://www.example.com">  
                    http://www.example.com  
                </a>  
            </richtext>  
        </nview>  
    </nviews>  
</template>  
<script>  
    module.exports = {  
        methods: {  
            handleRichText:function(e){  
                console.log("tagName:" + e.tagName);  
                console.log("href:" + e.href);  
                console.log("src:" + e.src);  
            }  
        }  
    };  
</script>

事件冒泡

NView模板中,点击事件默认会从子控件向父控件冒泡。

如下示例中,nview和button标签同时监听了点击事件,当用户点击button按钮时,会先弹框提醒“click button!”,然后点击事件向父容器冒泡,接着弹框提醒"click nview!"。

<template>  
    <nviews cachemaxage="86400">  
        <nview style="height:100px" onclick={this.clickNView}>  
            <canvas>  
                <button style="top:10px;left:20px;" onclick={this.clickBtn}>按钮</button>  
                <img style="top:10px;left:100px;" src="_www/img/demo.png"></img>  
            </canvas>  
        </nview>  
    </nviews>  
</template>  
<script>  
    module.exports = {  
        data: {  
            //默认数据  
        },  
        init: function(url) {  
            //动态初始化数据  
        },  
        methods: {  
            clickNView:function(event){//nview点击事件触发  
                alert("click nview!");  
            },  
            clickBtn:function(event){//按钮点击事件触发  
                alert("click button!");  
            }  
        }  
    };  
</script>

如果想阻止事件冒泡,可以在子控件的点击函数增加return false语句,如下:

<template>  
    <nviews cachemaxage="86400">  
        <nview  style="height:100px;" onclick={this.clickNView}>  
            <canvas>  
                <button style="top:10px;left:20px;" onclick={this.clickBtn}>按钮</button>  
                <img style="top:10px;left:100px;" src="_www/img/demo.png"></img>  
            </canvas>  
        </nview>  
    </nviews>  
</template>  
<script>  
    module.exports = {  
        data: {  
            //默认数据  
        },  
        init: function(url) {  
            //动态初始化数据  
        },  
        methods: {  
            clickNView:function(event){//nview点击事件触发  
                alert("click nview!");  
            },  
            clickBtn:function(event){//按钮点击事件触发  
                alert("click button!");  
                return false;//阻止事件冒泡  
            }  
        }  
    };  
</script>

阻止事件冒泡后,用户再次点击button按钮,则会弹框提醒“click button!”,不会再弹框提醒"click nview!"。

事件拦截

subNView作为webview的一个子控件,覆盖在webview上方,当用户点击subNView控件时,subNView默认会拦截touch事件,不会将touch事件透传给下层的webview中的DOM元素。

wap2app支持通过设置nview标签的intercept属性配置touch事件是否拦截,默认为true,即拦截touch事件,下层webview中的DOM元素无法触发touch事件;如果设置为false,则不拦截touch事件,下层webview中的DOM元素将接收到用户的touch事件,从而触发原来HTML页面的touch事件逻辑。

如下示例代码中,nview1设置了不拦截touch事件,则在nview1区域点击时,都会透传给下方的webview页面中的DOM元素,触发DOM元素的点击事件.

<template>  
    <nviews cachemaxage="86400">  
        <nview id="nview1" style="position:static" intercept=“false”>  
            ...  
        </nview>  
    </nviews>  
</template>  
<script>  
    module.exports = {  

    };  
</script>

Tips:仅nview标签支持intercept属性设置。

继续阅读 »

onclick事件

在NView模板中,很多控件都可以通过onclick属性配置控件的点击事件,配置方式如下:

<element onclick={this.func_name}></element>

func_name是一个函数,需要在<script>节点下的methods中声明,示例如下:

<script>  
    module.exports = {  
        data: {},//数据  
        methods: {  
            func_name: function(event) {   
                //点击事件触发  
            }  
        }  
    };  
</script>

点击事件的回调函数自带一个event参数,通过event.detail可以访问当前NView模板中绑定的数据(即data对象),如下是一个完整示例,点击按钮,打印当前的产品名称和版本号:

<template>  
    <nviews cachemaxage="86400">  
        <nview style="height:100px">  
            <canvas>  
                <button onclick={this.clickBtn}>按钮</button>  
            </canvas>  
        </nview>  
    </nviews>  
</template>  
<script>  
    module.exports = {  
        data: {  
            name: "wap2app",  
            version: "1.0.0"  
        },  
        methods: {  
            clickBtn: function(event) { //按钮点击事件触发  
                var data = event.detail;  
                console.log("name:" + data.name + ",version:" + data.version);  
            }  
        }  
    };  
</script>

NView模板中,列表组件(<list>)和流式布局控件(<richtext>)的点击事件相对特殊,下文分别介绍。

list组件

<list>标签的onclick点击事件同上,但列表项<item>的点击事件相对特殊,如果列表项是通过n-for指令循环渲染的,则在点击回调函数中,除了event参数外,还有item、index两个参数,分别表示当前列表项的数据及列表项索引,详细参考NView模板绘制原生列表教程

richtext布局控件

<richtext>布局控件也支持监听点击事件,配置方式同上;但<richtext>内部嵌套的子控件不支持监听onclick事件,用户点击<richtext>内部控件时,会向上冒泡到父容器<richtext>标签上,触发<richtext>的onclick回调。

同普通控件的点击回调,<richtext>的点击回调函数,自带一个event参数,通过event.detail可以访问当前NView模板中绑定的数据(即data对象);另外,还可以通过event访问点击目标对象的属性(tagName、href、src),具体情况如下:

  • 如果点击的是<richtext>中的<a>标签,则回调函数event参数中包含tagName(值为"a")和href属性;
  • 如果点击的是<richtext>中的<img>标签,则回调函数event参数中包含tagName(值为"img")和src属性;
  • 如果点击的是<richtext>中的其它区域或控件,则回调函数event参数中包含tagName(值为"")

如下是一个示例代码:

<template>  
    <nviews cachemaxage="86400">  
        <nview style="height:100px;" >  
            <richtext onclick={this.handleRichText}>  
                <img src="logo.png" width="20px" height="20px"></img>  
                <a href="http://www.example.com">  
                    http://www.example.com  
                </a>  
            </richtext>  
        </nview>  
    </nviews>  
</template>  
<script>  
    module.exports = {  
        methods: {  
            handleRichText:function(e){  
                console.log("tagName:" + e.tagName);  
                console.log("href:" + e.href);  
                console.log("src:" + e.src);  
            }  
        }  
    };  
</script>

事件冒泡

NView模板中,点击事件默认会从子控件向父控件冒泡。

如下示例中,nview和button标签同时监听了点击事件,当用户点击button按钮时,会先弹框提醒“click button!”,然后点击事件向父容器冒泡,接着弹框提醒"click nview!"。

<template>  
    <nviews cachemaxage="86400">  
        <nview style="height:100px" onclick={this.clickNView}>  
            <canvas>  
                <button style="top:10px;left:20px;" onclick={this.clickBtn}>按钮</button>  
                <img style="top:10px;left:100px;" src="_www/img/demo.png"></img>  
            </canvas>  
        </nview>  
    </nviews>  
</template>  
<script>  
    module.exports = {  
        data: {  
            //默认数据  
        },  
        init: function(url) {  
            //动态初始化数据  
        },  
        methods: {  
            clickNView:function(event){//nview点击事件触发  
                alert("click nview!");  
            },  
            clickBtn:function(event){//按钮点击事件触发  
                alert("click button!");  
            }  
        }  
    };  
</script>

如果想阻止事件冒泡,可以在子控件的点击函数增加return false语句,如下:

<template>  
    <nviews cachemaxage="86400">  
        <nview  style="height:100px;" onclick={this.clickNView}>  
            <canvas>  
                <button style="top:10px;left:20px;" onclick={this.clickBtn}>按钮</button>  
                <img style="top:10px;left:100px;" src="_www/img/demo.png"></img>  
            </canvas>  
        </nview>  
    </nviews>  
</template>  
<script>  
    module.exports = {  
        data: {  
            //默认数据  
        },  
        init: function(url) {  
            //动态初始化数据  
        },  
        methods: {  
            clickNView:function(event){//nview点击事件触发  
                alert("click nview!");  
            },  
            clickBtn:function(event){//按钮点击事件触发  
                alert("click button!");  
                return false;//阻止事件冒泡  
            }  
        }  
    };  
</script>

阻止事件冒泡后,用户再次点击button按钮,则会弹框提醒“click button!”,不会再弹框提醒"click nview!"。

事件拦截

subNView作为webview的一个子控件,覆盖在webview上方,当用户点击subNView控件时,subNView默认会拦截touch事件,不会将touch事件透传给下层的webview中的DOM元素。

wap2app支持通过设置nview标签的intercept属性配置touch事件是否拦截,默认为true,即拦截touch事件,下层webview中的DOM元素无法触发touch事件;如果设置为false,则不拦截touch事件,下层webview中的DOM元素将接收到用户的touch事件,从而触发原来HTML页面的touch事件逻辑。

如下示例代码中,nview1设置了不拦截touch事件,则在nview1区域点击时,都会透传给下方的webview页面中的DOM元素,触发DOM元素的点击事件.

<template>  
    <nviews cachemaxage="86400">  
        <nview id="nview1" style="position:static" intercept=“false”>  
            ...  
        </nview>  
    </nviews>  
</template>  
<script>  
    module.exports = {  

    };  
</script>

Tips:仅nview标签支持intercept属性设置。

收起阅读 »

NView数据绑定

NView模板 wap2app

除了标准字符串模式的文本、属性设置外,NView模板支持将NView实例数据绑定至NView控件上。

插值

NView模板的文本、属性插值方式相同,均使用大括号({})表示当前为Javascript表达式,不是直接字符串赋值,如下为一个简单示例:

<template>  
    <nviews cacheMaxAge="86400">  
        <nview id="nview1" style="position:static;top:200px;height:50px;">  
            <Font>{data.product_name}</Font>  
        </nview>  
    </nviews>  
</template>  
<script>  
    module.exports = {  
        data: {  
            product_name:'产品名称'  
        }  
    };  
</script>

NView模板中的Javascript表达式,要求必须是单个表达式或JSON对象,如下都是合法的:

<nview id="nview1" style={{  
    //这里是json对象  
    position:"static",  
    top:data.top  
}}>  
    <Font>{data.rem * 17}</Font>  
    <img id="img1" style={{  
            //三元表达式  
            width:data.full?"100%":"80%"  
        }}>  
</nview>

备注:两个大括号{{}}的含义,外层大括号{}表示接下来为Javascript运算符,内层大括号{}表示当前为一个JSON对象。

下面的例子则不生效,因为他们是语句,而不是表达式

<font>{data.rem = 10}</font>

数据源

NView模板可以绑定JavaScript动态计算的数据,数据的提供主要有两种方式:

  • 直接通过data属性提供默认值,这种方式适用于数据相对固定,跟具体业务无关的场景,比如设置界面、开发商等
  • 在init属性中,编写JavaScript代码,根据业务场景动态计算(请求)数据

data属性

默认数据可直接在data属性中提供,data支持Object、Function两种方式,例如:

<script>  
    module.exports = {  
        data: {  
            vendor:'DCloud',  
            city:'北京'  
        }  
    };  
</script>

下面是Function示例,返回值要求是Object类型;Function适合需要简单计算的场景,比如根据屏幕分辨率动态计算文字大小、宽高等场景:

<script>  
    module.exports = {  
        data: function() {  
            return {  
                fontSize:window.screen.width > 360 ? "17px" : "15px",//动态计算  
                vendor:'DCloud'  
            }  
        }  
    };  
</script>

init属性

跟业务相关的数据,可以在init中通过JavaScript编程实现,使用方式如下:

<script>  
    module.exports = {  
        data: {  
            //默认数据  
        },  
        init: function(url) {  
            //url为新开webview加载的地址,可以通过url分析出部分业务参数  
            //TODO 获取数据,存储为newData  
            var newData = {};  
            ...  
            //必需:重新设置data  
            this.setData(newData);  
        }  
    };  
</script>

动态计算NView数据,通常有三种场景:

  • 通过页面url地址,分析出所需业务参数,适合页面url和业务数据有明确对应关系的场景
  • 通过前页获取数据,比如列表页已经显示了商品的名称,则详情页可以直接复用列表页的商品名称数据
  • 通过ajax请求获取数据,这样可以在Document加载完成之前提前发起请求并渲染

如上三种方式可混合使用,每次调用setData()方法,都会覆盖之前的数据(merge覆盖,非完全替换),如下是示例:

<script>  
    module.exports = {  
        data: {  
            vendor:'DCloud',  
            author:'CHB',  
            product:{  
                name:'wap2app'  
            }  
        },  
        init: function(url) {  
            var newData = {  
                author:'FXY',  
                product:{  
                    description:'基于M站的强化方案'  
                }  
            };  
            //必需:重新设置data  
            this.setData(newData);  
        }  
    };  
</script>

如上代码运行后,最终的模板数据为:

{  
    vendor:'DCloud',//保留  
    author:'FXY',//替换  
    product:{  
        name:'wap2app',//保留  
        description:'基于M站的强化方案'//新增  
    }  
}

通过url分析业务数据

这种模式适合页面url和业务参数有明确对应关系的场景,比如页面地址为http://www.example.com/detail/%id%,该页面上图片轮播地址固定为:

http://www.example.com/images/%id%/1.png  
http://www.example.com/images/%id%/2.png  
http://www.example.com/images/%id%/3.png

我们可以通过分析页面url,解析出id参数,然后拼接轮播组件图片源地址,如下是一个示例代码:

<template>  
    <nviews cachemaxage="86400">  
        <nview id="nvew2" style="height:200px;">  
            <!--图片轮播-->  
            <imageslider images={data.imgList}></imageslider>  
        </nview>  
    </nviews>  
</template>  
<script>  
    module.exports = {  
        data: {  
            //默认数据  
        },  
        init: function(url) {  
            //页面url地址为http://www.example.com/detail/%id%  
            //分析页面id  
            var id = url.substring(url.lastIndexOf("/")+1)  
            var newData = {};  
            var list = new Array();  
            //循环,构建原生轮播图片源信息  
            for (var i = 1; i <= 3; i++) {  
                var item = {};  
                //图片源地址  
                item.src = "http://www.example.com/images/" + id + "/" + i +".png";  
                item.width = "100%";  
                list.push(item);  
            }  
            //设置轮播组件信息  
            newData.imgList = list;  
            //重新设置data  
            this.setData(newData);  
        }  
    };  
</script>

复用前页数据

下图是一个示例,从列表打开详情(列表页就是前页),详情页的部分数据和列表页相同(如商品图片、标题、价格等),此时就可以复用列表页的数据,提前渲染:

复用前页数据的思路:

  • 用户在前页点击时,将可复用信息存储到本地缓存中
  • 在新页面创建后,从本地缓存中读取数据并渲染到NView中

我们以列表打开详情页,详情页使用NView加速渲染为例,演示如何实现复用前页(列表页)数据。

前页存储本地缓存

用户点击时存储数据的代码,建议在M站编写,也可以在HBuilder客户端编写,然后通过appendJS的方式插入到原站。如下是一个示例代码:

假设前页列表HTML代码如下:

<!-- 列表开始 -->  
<div id="list">  
    <!-- 第一个列表项 -->  
    <div class="item">  
        <img src="http://www.example.com/img/1.png" alt="" />  
        <p class="title">Item 1</p>  
    </div>  
    <!-- 第二个列表项 -->  
    <div class="item">  
        <img src="http://www.example.com/img/2.png" alt="" />  
        <p class="title">Item 2</p>  
    </div>  
</div>  
<!-- 列表结束 -->

我们可以增加如下JS代码,当用户点击列表项时,将列表项数据缓存到plus.storage中,示例如下:

(function() {  
    //非5+引擎环境,退出  
    if(navigator.userAgent.indexOf("Html5Plus") == -1) {  
        return false;  
    }  

    //判断DOM是否加载完成,如果M站已封装了类似方法,可以直接使用,例如jQuery.ready()  
    var readyRE = /complete|loaded|interactive/;  
    if(readyRE.test(document.readyState)) {  
        domReady();  
    } else {  
        document.addEventListener('DOMContentLoaded', function() {  
            domReady();  
        }, false);  
    }  

    function domReady() {  
        var cacheId = "detail"; //缓存的ID,NView模板会通过该ID读取缓存数据  
        var container = document.querySelector("#list"); //列表容器,根据M站实现改造  
        var eventType = "click"; //点击事件,根据M站实现更改,比如tap  

        //容器添加点击事件监听  
        container && container.addEventListener(eventType, function(e) {  
            var target = e.target;  
            for(; target && target !== container; target = target.parentNode) {  
                if(target.classList && target.classList.contains("item")) { //列表项  
                    var data = {};  
                    //获取图片路径  
                    var imgElem = target.querySelector("img"); //获取当前列表项下图片对象  
                    if(imgElem) {  
                        data.imgPath = imgElem.getAttribute("src"); //获取图片资源路径  
                    }  
                    //获取标题  
                    var titleElem = target.querySelector(".title"); //获取当前列表项下的标题对象  
                    if(titleElem) {  
                        data.title = titleElem.innerHTML.trim(); //获取标题  
                    }  
                    //将当前数据存储到缓存中  
                    plus.storage.setItem(cacheId, JSON.stringify(data));  
                    return;  
                }  
            }  
        });  
    }  
})();

NView模板中读取缓存数据

NView模板封装了一个wap2app.getFromCache()方法,该方法有两个功能:

  • 从plus.storage中读取缓存数据并自动转成object对象
  • 读取缓存数据后,自动清除数据,防止下次打开同类页面时(本例为打开其它详情页),数据错乱

wap2app.getFromCache()使用方法如下:

wap2app.getFromCache(cacheId,function(data){  
    //data是plus.storage.getItem(cacheId)返回的数据,已自动转成object类型  
})

继续上如列表示例,详情页NView模板完整代码如下:

<template>  
    <nviews cachemaxage="86400">  
        <nview id="detailNview" style="height:225px;">  
            <canvas>  
                <!--缩略图-->  
                <img style="top: 0;width:100%;height:225px;" src={data.imgPath}/>  
                <!--标题-->  
                <font style="bottom: 10px;height: 20px;">{data.title}</font>  
            </canvas>  
        </nview>  
    </nviews>  
</template>  
<script>  
    module.exports = {  
        data:{//默认数据,可选  
            imgPath:'http://www.example.com/img/default.png',  
            title:''  
        },  
        init: function(url) {  
            var self = this;//this作用域在回调函数中会变化,复制给变量self  
            //从缓存中读取数据  
            //'detail'为前页存储数据时的key  
            wap2app.getFromCache('detail', function(data) {  
                if(data) {  
                    //读取缓存数据成功,更新数据,刷新NView  
                    self.setData(data);  
                }  
            });  
        }  
    };  
</script>

ajax获取服务端数据

NView模板也支持动态从服务端获取数据,wap2app封装了一个wap2app.getFromWebService()方法来实现AJAX请求,如下为示例代码:

<script>  
    module.exports = {  
        init: function(url) {  
            var self = this;//this作用域在回调函数中会变化,复制给变量self  
            //动态初始化数据  
            wap2app.getFromWebService({  
                url: 'your-server-url',  
                data: {},  
                type: 'post'  
            }, function(data) {  
                //TODO 如果服务端返回数据格式和模板需要的格式不同,则需要做一下转换  
                self.setData(data);  
            })  
        }  
    };  
</script>
继续阅读 »

除了标准字符串模式的文本、属性设置外,NView模板支持将NView实例数据绑定至NView控件上。

插值

NView模板的文本、属性插值方式相同,均使用大括号({})表示当前为Javascript表达式,不是直接字符串赋值,如下为一个简单示例:

<template>  
    <nviews cacheMaxAge="86400">  
        <nview id="nview1" style="position:static;top:200px;height:50px;">  
            <Font>{data.product_name}</Font>  
        </nview>  
    </nviews>  
</template>  
<script>  
    module.exports = {  
        data: {  
            product_name:'产品名称'  
        }  
    };  
</script>

NView模板中的Javascript表达式,要求必须是单个表达式或JSON对象,如下都是合法的:

<nview id="nview1" style={{  
    //这里是json对象  
    position:"static",  
    top:data.top  
}}>  
    <Font>{data.rem * 17}</Font>  
    <img id="img1" style={{  
            //三元表达式  
            width:data.full?"100%":"80%"  
        }}>  
</nview>

备注:两个大括号{{}}的含义,外层大括号{}表示接下来为Javascript运算符,内层大括号{}表示当前为一个JSON对象。

下面的例子则不生效,因为他们是语句,而不是表达式

<font>{data.rem = 10}</font>

数据源

NView模板可以绑定JavaScript动态计算的数据,数据的提供主要有两种方式:

  • 直接通过data属性提供默认值,这种方式适用于数据相对固定,跟具体业务无关的场景,比如设置界面、开发商等
  • 在init属性中,编写JavaScript代码,根据业务场景动态计算(请求)数据

data属性

默认数据可直接在data属性中提供,data支持Object、Function两种方式,例如:

<script>  
    module.exports = {  
        data: {  
            vendor:'DCloud',  
            city:'北京'  
        }  
    };  
</script>

下面是Function示例,返回值要求是Object类型;Function适合需要简单计算的场景,比如根据屏幕分辨率动态计算文字大小、宽高等场景:

<script>  
    module.exports = {  
        data: function() {  
            return {  
                fontSize:window.screen.width > 360 ? "17px" : "15px",//动态计算  
                vendor:'DCloud'  
            }  
        }  
    };  
</script>

init属性

跟业务相关的数据,可以在init中通过JavaScript编程实现,使用方式如下:

<script>  
    module.exports = {  
        data: {  
            //默认数据  
        },  
        init: function(url) {  
            //url为新开webview加载的地址,可以通过url分析出部分业务参数  
            //TODO 获取数据,存储为newData  
            var newData = {};  
            ...  
            //必需:重新设置data  
            this.setData(newData);  
        }  
    };  
</script>

动态计算NView数据,通常有三种场景:

  • 通过页面url地址,分析出所需业务参数,适合页面url和业务数据有明确对应关系的场景
  • 通过前页获取数据,比如列表页已经显示了商品的名称,则详情页可以直接复用列表页的商品名称数据
  • 通过ajax请求获取数据,这样可以在Document加载完成之前提前发起请求并渲染

如上三种方式可混合使用,每次调用setData()方法,都会覆盖之前的数据(merge覆盖,非完全替换),如下是示例:

<script>  
    module.exports = {  
        data: {  
            vendor:'DCloud',  
            author:'CHB',  
            product:{  
                name:'wap2app'  
            }  
        },  
        init: function(url) {  
            var newData = {  
                author:'FXY',  
                product:{  
                    description:'基于M站的强化方案'  
                }  
            };  
            //必需:重新设置data  
            this.setData(newData);  
        }  
    };  
</script>

如上代码运行后,最终的模板数据为:

{  
    vendor:'DCloud',//保留  
    author:'FXY',//替换  
    product:{  
        name:'wap2app',//保留  
        description:'基于M站的强化方案'//新增  
    }  
}

通过url分析业务数据

这种模式适合页面url和业务参数有明确对应关系的场景,比如页面地址为http://www.example.com/detail/%id%,该页面上图片轮播地址固定为:

http://www.example.com/images/%id%/1.png  
http://www.example.com/images/%id%/2.png  
http://www.example.com/images/%id%/3.png

我们可以通过分析页面url,解析出id参数,然后拼接轮播组件图片源地址,如下是一个示例代码:

<template>  
    <nviews cachemaxage="86400">  
        <nview id="nvew2" style="height:200px;">  
            <!--图片轮播-->  
            <imageslider images={data.imgList}></imageslider>  
        </nview>  
    </nviews>  
</template>  
<script>  
    module.exports = {  
        data: {  
            //默认数据  
        },  
        init: function(url) {  
            //页面url地址为http://www.example.com/detail/%id%  
            //分析页面id  
            var id = url.substring(url.lastIndexOf("/")+1)  
            var newData = {};  
            var list = new Array();  
            //循环,构建原生轮播图片源信息  
            for (var i = 1; i <= 3; i++) {  
                var item = {};  
                //图片源地址  
                item.src = "http://www.example.com/images/" + id + "/" + i +".png";  
                item.width = "100%";  
                list.push(item);  
            }  
            //设置轮播组件信息  
            newData.imgList = list;  
            //重新设置data  
            this.setData(newData);  
        }  
    };  
</script>

复用前页数据

下图是一个示例,从列表打开详情(列表页就是前页),详情页的部分数据和列表页相同(如商品图片、标题、价格等),此时就可以复用列表页的数据,提前渲染:

复用前页数据的思路:

  • 用户在前页点击时,将可复用信息存储到本地缓存中
  • 在新页面创建后,从本地缓存中读取数据并渲染到NView中

我们以列表打开详情页,详情页使用NView加速渲染为例,演示如何实现复用前页(列表页)数据。

前页存储本地缓存

用户点击时存储数据的代码,建议在M站编写,也可以在HBuilder客户端编写,然后通过appendJS的方式插入到原站。如下是一个示例代码:

假设前页列表HTML代码如下:

<!-- 列表开始 -->  
<div id="list">  
    <!-- 第一个列表项 -->  
    <div class="item">  
        <img src="http://www.example.com/img/1.png" alt="" />  
        <p class="title">Item 1</p>  
    </div>  
    <!-- 第二个列表项 -->  
    <div class="item">  
        <img src="http://www.example.com/img/2.png" alt="" />  
        <p class="title">Item 2</p>  
    </div>  
</div>  
<!-- 列表结束 -->

我们可以增加如下JS代码,当用户点击列表项时,将列表项数据缓存到plus.storage中,示例如下:

(function() {  
    //非5+引擎环境,退出  
    if(navigator.userAgent.indexOf("Html5Plus") == -1) {  
        return false;  
    }  

    //判断DOM是否加载完成,如果M站已封装了类似方法,可以直接使用,例如jQuery.ready()  
    var readyRE = /complete|loaded|interactive/;  
    if(readyRE.test(document.readyState)) {  
        domReady();  
    } else {  
        document.addEventListener('DOMContentLoaded', function() {  
            domReady();  
        }, false);  
    }  

    function domReady() {  
        var cacheId = "detail"; //缓存的ID,NView模板会通过该ID读取缓存数据  
        var container = document.querySelector("#list"); //列表容器,根据M站实现改造  
        var eventType = "click"; //点击事件,根据M站实现更改,比如tap  

        //容器添加点击事件监听  
        container && container.addEventListener(eventType, function(e) {  
            var target = e.target;  
            for(; target && target !== container; target = target.parentNode) {  
                if(target.classList && target.classList.contains("item")) { //列表项  
                    var data = {};  
                    //获取图片路径  
                    var imgElem = target.querySelector("img"); //获取当前列表项下图片对象  
                    if(imgElem) {  
                        data.imgPath = imgElem.getAttribute("src"); //获取图片资源路径  
                    }  
                    //获取标题  
                    var titleElem = target.querySelector(".title"); //获取当前列表项下的标题对象  
                    if(titleElem) {  
                        data.title = titleElem.innerHTML.trim(); //获取标题  
                    }  
                    //将当前数据存储到缓存中  
                    plus.storage.setItem(cacheId, JSON.stringify(data));  
                    return;  
                }  
            }  
        });  
    }  
})();

NView模板中读取缓存数据

NView模板封装了一个wap2app.getFromCache()方法,该方法有两个功能:

  • 从plus.storage中读取缓存数据并自动转成object对象
  • 读取缓存数据后,自动清除数据,防止下次打开同类页面时(本例为打开其它详情页),数据错乱

wap2app.getFromCache()使用方法如下:

wap2app.getFromCache(cacheId,function(data){  
    //data是plus.storage.getItem(cacheId)返回的数据,已自动转成object类型  
})

继续上如列表示例,详情页NView模板完整代码如下:

<template>  
    <nviews cachemaxage="86400">  
        <nview id="detailNview" style="height:225px;">  
            <canvas>  
                <!--缩略图-->  
                <img style="top: 0;width:100%;height:225px;" src={data.imgPath}/>  
                <!--标题-->  
                <font style="bottom: 10px;height: 20px;">{data.title}</font>  
            </canvas>  
        </nview>  
    </nviews>  
</template>  
<script>  
    module.exports = {  
        data:{//默认数据,可选  
            imgPath:'http://www.example.com/img/default.png',  
            title:''  
        },  
        init: function(url) {  
            var self = this;//this作用域在回调函数中会变化,复制给变量self  
            //从缓存中读取数据  
            //'detail'为前页存储数据时的key  
            wap2app.getFromCache('detail', function(data) {  
                if(data) {  
                    //读取缓存数据成功,更新数据,刷新NView  
                    self.setData(data);  
                }  
            });  
        }  
    };  
</script>

ajax获取服务端数据

NView模板也支持动态从服务端获取数据,wap2app封装了一个wap2app.getFromWebService()方法来实现AJAX请求,如下为示例代码:

<script>  
    module.exports = {  
        init: function(url) {  
            var self = this;//this作用域在回调函数中会变化,复制给变量self  
            //动态初始化数据  
            wap2app.getFromWebService({  
                url: 'your-server-url',  
                data: {},  
                type: 'post'  
            }, function(data) {  
                //TODO 如果服务端返回数据格式和模板需要的格式不同,则需要做一下转换  
                self.setData(data);  
            })  
        }  
    };  
</script>
收起阅读 »

模板标签 - NView模板 - wap2app教程

wap2app NView模板

在NView模板中,仅允许使用有限的标签(可以认为是HTML的子集),标签嵌套顺序及支持的属性,均有较为严格的限制。一个NView模板文件,固定结构如下:

<template>  
    <nviews cachemaxage="86400">  
        <!--nview控件-->  
    </nviews>  
</template>  
<script>  
    module.exports = {  
        //通过JS处理数据  
    };  
</script>   

template/nviews/script这些是表示特定结构的标签,并不生成UI元素;另外还有一些标签,如font/button等,则会生成相应的UI元素。

大多数标签都是可以通过属性设置的元素的样式,比如位置、边框等。我们讲大多数模板标签均具备的属性,称为标签通用属性,主要包括id/onclick/style三个属性,详细说明参考这里。本文在介绍模板标签时,除非该标签有特殊限制,否则不会介绍通用属性。

<a>

a标签在原生view上绘制一个超链接(底部带下划线的文本),目前<a>标签仅支持在<richtext>容器下使用。

属性

<a>位于流式布局容器richtext标签下时,支持id、style属性,不支持onclick属性;style中可以定义font样式,详细参考模板样式;不支持align、background、position样式。

richtext容器下的a标签,还支持href属性,但点击不会触发页面跳转。若需处理a标签的跳转,则需监听richtext容器的onclick事件,通过点击回调中获取点击目标对象的href属性,然后通过JS API实现窗口跳转。

子标签

<a>标签下不支持嵌套子标签。

<br>

br标签用于插入一个简单的换行符,类似于HTML5中的br标签,目前仅可以在richtext容器下使用。

br标签不支持任何属性及子标签。

<button>

<button>用于在原生View上绘制按钮,button标签内部仅支持放置文字,按钮文字上下、左右居中显示。

属性

<button>处于canvas和richtext容器下时,支持属性不同。

canvas

button位于绝对布局容器canvas标签下时,支持id、onclick、style通用属性;style中可以定义background、border、font、position样式,详细参考模板样式

richtext

暂不支持、即将支持。

子标签

button标签下不支持嵌套子标签。

<canvas>

<canvas>表示绝对布局容器,默认和父容器nview具备相同的宽高,子节点全部为绝对定位,关于绝对布局参考模板布局.

属性

canvas支持id、onclick、style通用属性,style属性中支持background、position样式定义,详细参考模板样式

子标签

canvas标签下允许使用a、button、font、img、input、hr标签;

<font>

<font>用于在原生View上绘制文本,

属性

font处于canvas和richtext容器下时,支持属性不同。

canvas

font位于绝对布局容器canvas标签下时,支持id、onclick、style通用属性;style中可以定义align、background、font、position样式,详细参考模板样式

richtext

font位于流式布局容器richtext标签下时,支持id、style属性,不支持onclick属性;style中可以定义align、font样式,详细参考模板样式;不支持background、position样式。

子标签

font标签下不支持嵌套子标签。

<hr>

hr标签在原生view上创建一条水平分割线,从而可以将展示内容分割成各个部分。

属性

hr标签支持id、style通用属性,不支持onclick属性;

hr标签的style属性可以定义align(水平对齐)、border、position中的宽高(width/height),width支持绝对像素值和百分比,百分比是根据父容器宽度自动计算。

另外,hr处于canvas容器下时,还支持position中的left/top等坐标位置设置;处于richtext容器下是流式布局,不支持left/top等坐标设置。

子标签

hr标签下不支持嵌套子标签。

<imageslider>

imageslider是原生渲染的图片轮播组件,不允许嵌套子标签。imageslider支持id、style通用属性,style属性中可以设置background、position样式,具体参考NView模板样式;另外,imageslider还支持images、loop、fullscreen属性,详细说明如下。

images

配置轮播组件的图片信息,具体信息包括:

  • src: 图片地址,支持本地地址(相对地址、绝对路径、RelativeURL、本地路径URL); 也支持网络地址(http://或https://);
  • align: 图片水平对齐方式,仅在图片显示的宽度与图片轮播控件宽度不一致时有效,可取值: "left" - 图片在轮播控件中水平居左对齐; "center" - 图片在轮播控件中水平居中对齐; "right" - 图片在轮播控件中水平居右对齐。 默认值为"center"。
  • height: 图片显示的高度,可取值: 像素值,如"100px"; 百分比,如"10%",现对于图片轮播控件的高度; 自动计算"auto",如果指定图片宽度(width),则按图片实际大小等比缩放图片高度值,如果没有指定宽度(width值为"auto")则自动缩放图片至可完整显示。
  • width: 图片显示的宽度,可取值: 像素值,如"100px"; 百分比,如"10%",相对于图片轮播控件的宽度; 自动计算"auto",如果指定图片高度(height),则按图片实际大小等比缩放图片宽度值,如果没有指定高度(height值为"auto")则自动缩放图片至可完整显示。 默认值为"auto"。
  • verticalAlign: 图片垂直对齐方式,仅在图片显示的高度与图片轮播控件宽度不一致时有效,可取值: "top" - 图片在轮播控件中垂直居顶对齐; "middle" - 图片在轮播控件中垂直居中对齐; "bottom" - 图片在轮播控件中垂直居底对齐。 默认值为"middle"。

因为配置信息较为复杂,这里需要使用NView模板的插值方式赋值,如下是一个示例:

<imageslider id="slider" style="height:100px;"  
    images={[  
        {src:"http://www.example.com/images/1.jpg",width:"100%",height:"100%"},  
        {src:"http://www.example.com/images/2.jpg",width:"100%",height:"100%"}  
    ]}>  
</imageslider>

如上images属性值中,外层{}表示当前为JavaScript表达式,内部的[]表示当前为一个Array数组,数组中的每个元素是{}包裹的json对象;

loop

设置轮播图片是否可循环播放,可取值:

  • true - 支持循环轮播;
  • false - 不支持循环轮播

默认值为false。

fullscreen

用户点击轮播图片时,是否全屏显示图片,可取值:

  • true - 全屏显示,用户点击轮播图片时全屏显示;
  • false - 不可全屏显示,用户点击轮播图片时无响应。

默认值为true。

子标签

imageslider不支持嵌套子标签。

<img>

<img>用于在原生View上绘制图片。

属性

除了通用标签属性,img还支持src属性,用于设置图片资源路径。

img处于canvas和richtext容器下时,支持的通用属性不同。

canvas

img位于绝对布局容器canvas标签下时,支持id、onclick、style通用属性;style中可以定义position样式,详细参考模板样式

richtext

img位于流式布局容器richtext标签下时,支持id、style属性,不支持onclick属性;style中仅支持position样式中的width、height属性,不支持left、top等坐标样式,详细参考模板样式

子标签

img标签下不支持嵌套子标签。

<input>

<input>标签可以在原生View上绘制输入框,输入文本内容在指定区域水平向左、垂直居中显示。

目前input标签仅支持在canvas容器下使用,richtext容器下无法使用。

属性

input标签支持id、style通用属性,style属性中支持设置font中的font-size样式、border边框样式;input标签不支持onclick属性;另外,input标签还支持type、placeholder、oncomplete属性。

type

type属性设置输入框类型,可取值:

  • email - 邮箱地址输入框;
  • number - 数字输入框;
  • search - 搜索文本输入框;
  • tel - 电话号码输入框;
  • text - 普通文本输入框;
  • url - URL地址输入框。

placeholder

设置输入框的提示文本,当用户未输入内容时显示在编辑框中(灰色文字)。

oncomplete

弹出软键盘完成输入后,点击软键盘上的“完成”、“前往”按钮时触发,如下为示例:

<template>  
    <nviews cacheMaxAge="86400">  
        <nview id="nview_canvas" style="height:150px;background-color: #efeff4;">  
            <canvas>  
                <input id="tel" type="tel" placeholder="输入手机号码" style="left:15px;top:15px;height:30px;right:15px;border-color:#007aff" oncomplete={this.handleInput}/>  
            </canvas>  
        </nview>  
    </nviews>  
</template>  
<script>  
    module.exports = {  
        methods: {  
            //inpu输入完成的回调  
            handleInput:function(){  
                plus.nativeUI.toast("input complete");  
            }  
        }  
    };  
</script>

子标签

input标签下不支持嵌套子标签。

<item>

item表示list组件下的列表项,参考NView模板绘制原生列表教程

<list>

list是NView模板封装的列表组件,参考NView模板绘制原生列表教程

<nviews>

nviews位于template节点下,一个NView模板文件有且仅有一个nviews节点;

属性

nviews仅支持cachemaxage一个属性。

cachemaxage

subNView缓存时间,单位为秒,默认为1天(86400秒);wap2app会将subNView的计算结果缓存起来,在缓存期内再次打开同一页面时,将直接使用缓存数据渲染subNView,而不会再次发起网络请求。

子节点

nviews节点下只允许嵌套使用nview一种标签,数量不限。

<nview>

nview表示一个布局容器,类似于HTML5中的div标签,但不支持nview标签的互相嵌套。

属性

nview支持id、onclick、style通用属性,style属性中支持background、position样式,详细参考模板样式

另外,nview标签还支持intercept、autoclose属性,详细定义参考下文描述。

intercept

nview事件拦截配置,详细参考事件监听 - NView模板文章中的事件拦截章节。

autoclose

nview是否自动关闭,默认为false,即不关闭;开发者可以通过配置autoclose="loaded"实现下层webview加载完毕后,自动关闭上层的nview控件。

Tips:subNView作为webview的子控件,在webview上层使用原生技术,提前渲染,在窗口动画期间就完成主要内容的渲染工作;若下层webview加载的HTML页面完全渲染结束,则subNView的主要工作已完成,是否继续存在则可以根据具体场景而定。

若subNView上部分元素需要响应用户的点击操作,则通常有三种方法:

  • 配置相应元素的onclick属性,编写对应的触发函数
  • 设置nview的intercept属性为false,将触屏事件透传给下层的webview,由dom元素响应事件,此种方案可能出现半屏遮罩的问题(webview的全屏div遮罩,但无法遮住上层的subNView控件)。
  • 配置nview的autoclose属性,在webview加载完毕后自动关闭subNView,则用户触屏时就直接触发dom元素的响应事件

子节点

<nview>节点下允许使用<imageslider>、<list>、<canvas>、<richtext>4个标签。

<richtext>

<richtext>表示流式布局容器,默认和父容器nview具备相同的宽高,子节点全部为流式布局。关于流式布局参考模板布局.

属性

richtext支持id、onclick、style通用属性,style属性中支持background、position样式定义,详细参考模板样式

//TODO richtext点击事件描述

子标签

richtext标签下允许使用font、img、a、br、hr标签;

<script>

script节点下可以进行数据计算,一个模板文件仅允许出现一个script节点,默认代码如下:

     <script>  
        module.exports = {  
            data: {  
                //默认数据  
            },  
            init: function(url) {  
                //动态初始化数据  
            },  
            methods: {  
                //方法  
            }  
        };  
    </script>

script标签不支持设置任何属性,也不允许嵌套子标签。

<template>

模板标签根节点,一个NView模板文件有且仅有一个template节点,不支持设置属性;

template节点下仅允许嵌套nviews标签,且仅允许嵌套一个nviews标签。

继续阅读 »

在NView模板中,仅允许使用有限的标签(可以认为是HTML的子集),标签嵌套顺序及支持的属性,均有较为严格的限制。一个NView模板文件,固定结构如下:

<template>  
    <nviews cachemaxage="86400">  
        <!--nview控件-->  
    </nviews>  
</template>  
<script>  
    module.exports = {  
        //通过JS处理数据  
    };  
</script>   

template/nviews/script这些是表示特定结构的标签,并不生成UI元素;另外还有一些标签,如font/button等,则会生成相应的UI元素。

大多数标签都是可以通过属性设置的元素的样式,比如位置、边框等。我们讲大多数模板标签均具备的属性,称为标签通用属性,主要包括id/onclick/style三个属性,详细说明参考这里。本文在介绍模板标签时,除非该标签有特殊限制,否则不会介绍通用属性。

<a>

a标签在原生view上绘制一个超链接(底部带下划线的文本),目前<a>标签仅支持在<richtext>容器下使用。

属性

<a>位于流式布局容器richtext标签下时,支持id、style属性,不支持onclick属性;style中可以定义font样式,详细参考模板样式;不支持align、background、position样式。

richtext容器下的a标签,还支持href属性,但点击不会触发页面跳转。若需处理a标签的跳转,则需监听richtext容器的onclick事件,通过点击回调中获取点击目标对象的href属性,然后通过JS API实现窗口跳转。

子标签

<a>标签下不支持嵌套子标签。

<br>

br标签用于插入一个简单的换行符,类似于HTML5中的br标签,目前仅可以在richtext容器下使用。

br标签不支持任何属性及子标签。

<button>

<button>用于在原生View上绘制按钮,button标签内部仅支持放置文字,按钮文字上下、左右居中显示。

属性

<button>处于canvas和richtext容器下时,支持属性不同。

canvas

button位于绝对布局容器canvas标签下时,支持id、onclick、style通用属性;style中可以定义background、border、font、position样式,详细参考模板样式

richtext

暂不支持、即将支持。

子标签

button标签下不支持嵌套子标签。

<canvas>

<canvas>表示绝对布局容器,默认和父容器nview具备相同的宽高,子节点全部为绝对定位,关于绝对布局参考模板布局.

属性

canvas支持id、onclick、style通用属性,style属性中支持background、position样式定义,详细参考模板样式

子标签

canvas标签下允许使用a、button、font、img、input、hr标签;

<font>

<font>用于在原生View上绘制文本,

属性

font处于canvas和richtext容器下时,支持属性不同。

canvas

font位于绝对布局容器canvas标签下时,支持id、onclick、style通用属性;style中可以定义align、background、font、position样式,详细参考模板样式

richtext

font位于流式布局容器richtext标签下时,支持id、style属性,不支持onclick属性;style中可以定义align、font样式,详细参考模板样式;不支持background、position样式。

子标签

font标签下不支持嵌套子标签。

<hr>

hr标签在原生view上创建一条水平分割线,从而可以将展示内容分割成各个部分。

属性

hr标签支持id、style通用属性,不支持onclick属性;

hr标签的style属性可以定义align(水平对齐)、border、position中的宽高(width/height),width支持绝对像素值和百分比,百分比是根据父容器宽度自动计算。

另外,hr处于canvas容器下时,还支持position中的left/top等坐标位置设置;处于richtext容器下是流式布局,不支持left/top等坐标设置。

子标签

hr标签下不支持嵌套子标签。

<imageslider>

imageslider是原生渲染的图片轮播组件,不允许嵌套子标签。imageslider支持id、style通用属性,style属性中可以设置background、position样式,具体参考NView模板样式;另外,imageslider还支持images、loop、fullscreen属性,详细说明如下。

images

配置轮播组件的图片信息,具体信息包括:

  • src: 图片地址,支持本地地址(相对地址、绝对路径、RelativeURL、本地路径URL); 也支持网络地址(http://或https://);
  • align: 图片水平对齐方式,仅在图片显示的宽度与图片轮播控件宽度不一致时有效,可取值: "left" - 图片在轮播控件中水平居左对齐; "center" - 图片在轮播控件中水平居中对齐; "right" - 图片在轮播控件中水平居右对齐。 默认值为"center"。
  • height: 图片显示的高度,可取值: 像素值,如"100px"; 百分比,如"10%",现对于图片轮播控件的高度; 自动计算"auto",如果指定图片宽度(width),则按图片实际大小等比缩放图片高度值,如果没有指定宽度(width值为"auto")则自动缩放图片至可完整显示。
  • width: 图片显示的宽度,可取值: 像素值,如"100px"; 百分比,如"10%",相对于图片轮播控件的宽度; 自动计算"auto",如果指定图片高度(height),则按图片实际大小等比缩放图片宽度值,如果没有指定高度(height值为"auto")则自动缩放图片至可完整显示。 默认值为"auto"。
  • verticalAlign: 图片垂直对齐方式,仅在图片显示的高度与图片轮播控件宽度不一致时有效,可取值: "top" - 图片在轮播控件中垂直居顶对齐; "middle" - 图片在轮播控件中垂直居中对齐; "bottom" - 图片在轮播控件中垂直居底对齐。 默认值为"middle"。

因为配置信息较为复杂,这里需要使用NView模板的插值方式赋值,如下是一个示例:

<imageslider id="slider" style="height:100px;"  
    images={[  
        {src:"http://www.example.com/images/1.jpg",width:"100%",height:"100%"},  
        {src:"http://www.example.com/images/2.jpg",width:"100%",height:"100%"}  
    ]}>  
</imageslider>

如上images属性值中,外层{}表示当前为JavaScript表达式,内部的[]表示当前为一个Array数组,数组中的每个元素是{}包裹的json对象;

loop

设置轮播图片是否可循环播放,可取值:

  • true - 支持循环轮播;
  • false - 不支持循环轮播

默认值为false。

fullscreen

用户点击轮播图片时,是否全屏显示图片,可取值:

  • true - 全屏显示,用户点击轮播图片时全屏显示;
  • false - 不可全屏显示,用户点击轮播图片时无响应。

默认值为true。

子标签

imageslider不支持嵌套子标签。

<img>

<img>用于在原生View上绘制图片。

属性

除了通用标签属性,img还支持src属性,用于设置图片资源路径。

img处于canvas和richtext容器下时,支持的通用属性不同。

canvas

img位于绝对布局容器canvas标签下时,支持id、onclick、style通用属性;style中可以定义position样式,详细参考模板样式

richtext

img位于流式布局容器richtext标签下时,支持id、style属性,不支持onclick属性;style中仅支持position样式中的width、height属性,不支持left、top等坐标样式,详细参考模板样式

子标签

img标签下不支持嵌套子标签。

<input>

<input>标签可以在原生View上绘制输入框,输入文本内容在指定区域水平向左、垂直居中显示。

目前input标签仅支持在canvas容器下使用,richtext容器下无法使用。

属性

input标签支持id、style通用属性,style属性中支持设置font中的font-size样式、border边框样式;input标签不支持onclick属性;另外,input标签还支持type、placeholder、oncomplete属性。

type

type属性设置输入框类型,可取值:

  • email - 邮箱地址输入框;
  • number - 数字输入框;
  • search - 搜索文本输入框;
  • tel - 电话号码输入框;
  • text - 普通文本输入框;
  • url - URL地址输入框。

placeholder

设置输入框的提示文本,当用户未输入内容时显示在编辑框中(灰色文字)。

oncomplete

弹出软键盘完成输入后,点击软键盘上的“完成”、“前往”按钮时触发,如下为示例:

<template>  
    <nviews cacheMaxAge="86400">  
        <nview id="nview_canvas" style="height:150px;background-color: #efeff4;">  
            <canvas>  
                <input id="tel" type="tel" placeholder="输入手机号码" style="left:15px;top:15px;height:30px;right:15px;border-color:#007aff" oncomplete={this.handleInput}/>  
            </canvas>  
        </nview>  
    </nviews>  
</template>  
<script>  
    module.exports = {  
        methods: {  
            //inpu输入完成的回调  
            handleInput:function(){  
                plus.nativeUI.toast("input complete");  
            }  
        }  
    };  
</script>

子标签

input标签下不支持嵌套子标签。

<item>

item表示list组件下的列表项,参考NView模板绘制原生列表教程

<list>

list是NView模板封装的列表组件,参考NView模板绘制原生列表教程

<nviews>

nviews位于template节点下,一个NView模板文件有且仅有一个nviews节点;

属性

nviews仅支持cachemaxage一个属性。

cachemaxage

subNView缓存时间,单位为秒,默认为1天(86400秒);wap2app会将subNView的计算结果缓存起来,在缓存期内再次打开同一页面时,将直接使用缓存数据渲染subNView,而不会再次发起网络请求。

子节点

nviews节点下只允许嵌套使用nview一种标签,数量不限。

<nview>

nview表示一个布局容器,类似于HTML5中的div标签,但不支持nview标签的互相嵌套。

属性

nview支持id、onclick、style通用属性,style属性中支持background、position样式,详细参考模板样式

另外,nview标签还支持intercept、autoclose属性,详细定义参考下文描述。

intercept

nview事件拦截配置,详细参考事件监听 - NView模板文章中的事件拦截章节。

autoclose

nview是否自动关闭,默认为false,即不关闭;开发者可以通过配置autoclose="loaded"实现下层webview加载完毕后,自动关闭上层的nview控件。

Tips:subNView作为webview的子控件,在webview上层使用原生技术,提前渲染,在窗口动画期间就完成主要内容的渲染工作;若下层webview加载的HTML页面完全渲染结束,则subNView的主要工作已完成,是否继续存在则可以根据具体场景而定。

若subNView上部分元素需要响应用户的点击操作,则通常有三种方法:

  • 配置相应元素的onclick属性,编写对应的触发函数
  • 设置nview的intercept属性为false,将触屏事件透传给下层的webview,由dom元素响应事件,此种方案可能出现半屏遮罩的问题(webview的全屏div遮罩,但无法遮住上层的subNView控件)。
  • 配置nview的autoclose属性,在webview加载完毕后自动关闭subNView,则用户触屏时就直接触发dom元素的响应事件

子节点

<nview>节点下允许使用<imageslider>、<list>、<canvas>、<richtext>4个标签。

<richtext>

<richtext>表示流式布局容器,默认和父容器nview具备相同的宽高,子节点全部为流式布局。关于流式布局参考模板布局.

属性

richtext支持id、onclick、style通用属性,style属性中支持background、position样式定义,详细参考模板样式

//TODO richtext点击事件描述

子标签

richtext标签下允许使用font、img、a、br、hr标签;

<script>

script节点下可以进行数据计算,一个模板文件仅允许出现一个script节点,默认代码如下:

     <script>  
        module.exports = {  
            data: {  
                //默认数据  
            },  
            init: function(url) {  
                //动态初始化数据  
            },  
            methods: {  
                //方法  
            }  
        };  
    </script>

script标签不支持设置任何属性,也不允许嵌套子标签。

<template>

模板标签根节点,一个NView模板文件有且仅有一个template节点,不支持设置属性;

template节点下仅允许嵌套nviews标签,且仅允许嵌套一个nviews标签。

收起阅读 »

NView模板概述 - wap2app教程

wap2app NView模板

什么是subNView

subNView,字面拆开理解就是sub native view,有两个核心点:

  • native:subNView是一种原生绘制的View,和HTML5的DOM无关
  • sub:subNView属于webview的一部分,并不替代完整Webview。和所属webview保持同样的生命周期,跟随webview的close、hide、zindex变化而变化。

subNView作为webview的子控件,一个webview可以包含多个subNView,一个subNView上可以绘制多个图片(包括图片轮播)、文字、区域、按钮等。

subNView希望解决什么问题

web页面加载渲染速度相比原生App,有较大的体验差距,以至于在移动设备上严重影响业务体验。这些体验差距是如何造成的呢?原因大致有如下几个:

  • 渲染时机:web app需要等待服务端Document下载完成后才可启动渲染工作,无法分区域渲染;而原生App作为C/S结构的应用,仅向服务端请求业务数据,其它布局、样式大多在本地,故可以在用户触发打开新窗口时,立即渲染新窗口部分区域布局,服务端响应数据后,再更新对应区域的内容;
  • 图片资源请求时机:web app需要先下载Document,然后再根据Document中的<img>标签的src属性去异步加载图片资源,故在web app中总是先看到文字,然后再逐渐看到图片;而原生App则无需任务等待,可以直接加载图片资源,故原生App的图片显示会早于web app中的图片显示;
  • 业务数据请求时机:web app的实现若是前后端分离,则需要先下载封装业务逻辑的js文件,下载完毕后,再由js发起ajax请求;而原生App,则大多将业务逻辑封装在本地,无需等待下载js文件,可以更早的发起HTTP业务请求

如何提升web页面的渲染速度,很多公司都在尝试,比如Google主导的AMP技术,AMP技术以阅读类网页加速为主,适用范围有限。

而subNView,则是一种更为通用、可增强任意web页面渲染体验的技术;subNView在保留HTML5优势的基础上,在webview中增加了一个原生view子控件,利用原生View发挥原生渲染优势;当用户触发时,webview按照原有逻辑,继续加载Document,渲染页面;但无需等待Document加载完成,可同时在原生View上完成如下工作:

  • 绘制固定内容区域,或可从前页获取数据的区域,例如固定地址图片、购物车按钮等,而无需等待Document下载完毕后再去请求加载图片
  • 直接发起业务数据ajax请求,ajax响应后直接在原生View上绘制数据,无需等待业务封装的JS下载

如下为一个使用subNView增强后的商品详情页示例:

从上图可看出:

  • webview按照原有逻辑,加载Document,渲染页面,刚开始内容未加载时还是白屏(中间空白区域)
  • webview同时创建了2个subNView作为webview的子控件
  • subNView 1展示商品详情轮播图及商品价格,是通过ajax动态获取的;轮播图片支持滑动、点击放大预览,用户可提前查看商品详情
  • subNView 2展示购物车相关功能,属于固定显示内容,可直接渲染
  • 购物车按钮点击后事件透传给Webview里的购物车按钮,HTML页面的所有逻辑,仍然复用。subNView只是简单的侵入动画渲染过程,不引发业务逻辑的重新编写。

如何使用subNView

开发者可以通过webview的subNViews属性创建或修改subNview控件,这里需要传入复杂的json对象;为简化开发,DCloud提供了NView模板技术。

NView模板

NView模板类似于vue的single-file components(单文件组件),HBuilder中新建NView模板文件,默认代码如下:

<template>  
    <nviews cachemaxage="86400">  

    </nviews>  
</template>  
<script>  
    module.exports = {  
        data: {  
            //默认数据  
        },  
        init: function(url) {  
            //动态初始化数据  
        },  
        methods: {  
            //方法  
        }  
    };  
</script>

需要注意的是,在<template>节点下仅可以使用受限制的NView HTML,详细参考后续章节介绍

FAQ

Q: 既然有了nview机制,为何不使用nview完成所有业务,而是作为HTML5的补充?
A:
其实这就是subNView前缀是sub的原因之一。
DCloud认为HTML5虽然有功能和性能问题,但面对问题进行强化即可,无需推翻HTML5重新来一套。
在HTML5生态中,有太多精彩的、现成的东西,开发者想要复用之前的HTML5,自然也应该使用subNView强化。
而完全使用nview将导致重写所有业务逻辑,后续也要维护多套版本。
总结一下:subNView的定位就是解决HTML5页面渲染慢的问题,做些简单的界面显示工作,在动画期间就给用户显示部分内容。而完整的业务逻辑处理,仍然在HTML页面里处理。

Q: 为何在wap2app里能使用nview模板,而5+app里不能使用?
A:
首先nview在5+里是一直存在的,wap2app是基于5+的上层框架,没有脱离5+范畴。参考plus.nativeObj.nview的api。
但通过5+的js api写titleNView很简单,但写复杂的subnview,开发体验不太好,于是我们在wap2app里对subnview引入了nview模板式写法,提升开发体验。
所以其实5+app也可以使用nview,只是暂时不能通过模板式写法来写,要写js api。
5+里引入nview模板写法,我们后续会补充,但这个工作优先级不高。
开发者可能误以为5+里也应该大量使用subNView提升体验,但其实不需要。
但nview在5+app里最常用的是title和tab,中间使用subnview的场景很少。
为啥5+app不太需要使用subnview呢?
因为如果你的代码写的不烂,本地的HTML页面加载是非常快的,在新窗体载入时,只是联网取ajax数据,和cs的原生应用一致。
这种情况下引入subnview,不会对内存、流畅度提供帮助,反而增加了工程的复杂度、降低了界面的灵活度(nview的排版灵活性比HTML还是差很多的)。
但在wap2app里,HTML不是本地的,在线加载HTML并渲染的速度太慢了,所以subnview的加持非常必要。
没仔细研究的人,肯定认为原生部分越多越好,其实事实并非如此,HTML5已经解决好的问题,引入原生多此一举。
所以在5+app里,nview更重要的用途是做标题栏和底部tab,减少双Webview的使用。

继续阅读 »

什么是subNView

subNView,字面拆开理解就是sub native view,有两个核心点:

  • native:subNView是一种原生绘制的View,和HTML5的DOM无关
  • sub:subNView属于webview的一部分,并不替代完整Webview。和所属webview保持同样的生命周期,跟随webview的close、hide、zindex变化而变化。

subNView作为webview的子控件,一个webview可以包含多个subNView,一个subNView上可以绘制多个图片(包括图片轮播)、文字、区域、按钮等。

subNView希望解决什么问题

web页面加载渲染速度相比原生App,有较大的体验差距,以至于在移动设备上严重影响业务体验。这些体验差距是如何造成的呢?原因大致有如下几个:

  • 渲染时机:web app需要等待服务端Document下载完成后才可启动渲染工作,无法分区域渲染;而原生App作为C/S结构的应用,仅向服务端请求业务数据,其它布局、样式大多在本地,故可以在用户触发打开新窗口时,立即渲染新窗口部分区域布局,服务端响应数据后,再更新对应区域的内容;
  • 图片资源请求时机:web app需要先下载Document,然后再根据Document中的<img>标签的src属性去异步加载图片资源,故在web app中总是先看到文字,然后再逐渐看到图片;而原生App则无需任务等待,可以直接加载图片资源,故原生App的图片显示会早于web app中的图片显示;
  • 业务数据请求时机:web app的实现若是前后端分离,则需要先下载封装业务逻辑的js文件,下载完毕后,再由js发起ajax请求;而原生App,则大多将业务逻辑封装在本地,无需等待下载js文件,可以更早的发起HTTP业务请求

如何提升web页面的渲染速度,很多公司都在尝试,比如Google主导的AMP技术,AMP技术以阅读类网页加速为主,适用范围有限。

而subNView,则是一种更为通用、可增强任意web页面渲染体验的技术;subNView在保留HTML5优势的基础上,在webview中增加了一个原生view子控件,利用原生View发挥原生渲染优势;当用户触发时,webview按照原有逻辑,继续加载Document,渲染页面;但无需等待Document加载完成,可同时在原生View上完成如下工作:

  • 绘制固定内容区域,或可从前页获取数据的区域,例如固定地址图片、购物车按钮等,而无需等待Document下载完毕后再去请求加载图片
  • 直接发起业务数据ajax请求,ajax响应后直接在原生View上绘制数据,无需等待业务封装的JS下载

如下为一个使用subNView增强后的商品详情页示例:

从上图可看出:

  • webview按照原有逻辑,加载Document,渲染页面,刚开始内容未加载时还是白屏(中间空白区域)
  • webview同时创建了2个subNView作为webview的子控件
  • subNView 1展示商品详情轮播图及商品价格,是通过ajax动态获取的;轮播图片支持滑动、点击放大预览,用户可提前查看商品详情
  • subNView 2展示购物车相关功能,属于固定显示内容,可直接渲染
  • 购物车按钮点击后事件透传给Webview里的购物车按钮,HTML页面的所有逻辑,仍然复用。subNView只是简单的侵入动画渲染过程,不引发业务逻辑的重新编写。

如何使用subNView

开发者可以通过webview的subNViews属性创建或修改subNview控件,这里需要传入复杂的json对象;为简化开发,DCloud提供了NView模板技术。

NView模板

NView模板类似于vue的single-file components(单文件组件),HBuilder中新建NView模板文件,默认代码如下:

<template>  
    <nviews cachemaxage="86400">  

    </nviews>  
</template>  
<script>  
    module.exports = {  
        data: {  
            //默认数据  
        },  
        init: function(url) {  
            //动态初始化数据  
        },  
        methods: {  
            //方法  
        }  
    };  
</script>

需要注意的是,在<template>节点下仅可以使用受限制的NView HTML,详细参考后续章节介绍

FAQ

Q: 既然有了nview机制,为何不使用nview完成所有业务,而是作为HTML5的补充?
A:
其实这就是subNView前缀是sub的原因之一。
DCloud认为HTML5虽然有功能和性能问题,但面对问题进行强化即可,无需推翻HTML5重新来一套。
在HTML5生态中,有太多精彩的、现成的东西,开发者想要复用之前的HTML5,自然也应该使用subNView强化。
而完全使用nview将导致重写所有业务逻辑,后续也要维护多套版本。
总结一下:subNView的定位就是解决HTML5页面渲染慢的问题,做些简单的界面显示工作,在动画期间就给用户显示部分内容。而完整的业务逻辑处理,仍然在HTML页面里处理。

Q: 为何在wap2app里能使用nview模板,而5+app里不能使用?
A:
首先nview在5+里是一直存在的,wap2app是基于5+的上层框架,没有脱离5+范畴。参考plus.nativeObj.nview的api。
但通过5+的js api写titleNView很简单,但写复杂的subnview,开发体验不太好,于是我们在wap2app里对subnview引入了nview模板式写法,提升开发体验。
所以其实5+app也可以使用nview,只是暂时不能通过模板式写法来写,要写js api。
5+里引入nview模板写法,我们后续会补充,但这个工作优先级不高。
开发者可能误以为5+里也应该大量使用subNView提升体验,但其实不需要。
但nview在5+app里最常用的是title和tab,中间使用subnview的场景很少。
为啥5+app不太需要使用subnview呢?
因为如果你的代码写的不烂,本地的HTML页面加载是非常快的,在新窗体载入时,只是联网取ajax数据,和cs的原生应用一致。
这种情况下引入subnview,不会对内存、流畅度提供帮助,反而增加了工程的复杂度、降低了界面的灵活度(nview的排版灵活性比HTML还是差很多的)。
但在wap2app里,HTML不是本地的,在线加载HTML并渲染的速度太慢了,所以subnview的加持非常必要。
没仔细研究的人,肯定认为原生部分越多越好,其实事实并非如此,HTML5已经解决好的问题,引入原生多此一举。
所以在5+app里,nview更重要的用途是做标题栏和底部tab,减少双Webview的使用。

收起阅读 »

去除M站DOM元素 - wap2app教程

wap2app

屏蔽元素类型

在wap2app项目中,M站需要根据运行环境,判断当前是5+引擎时,做一些调整。

Tips: wap2app项目不管是打包成ipa/apk,还是发布成流应用,都依赖HTML5 PLUS引擎,简称5+引擎。

M站需调整的元素,主要包括几个方面:

1、客户端已有增强实现,屏蔽M站原有元素

比如顶部标题栏,wap2app已启用了原生标题栏,M站就无需再显示DIV的标题栏,否则会出现双标题栏的情况。

2、屏蔽明显wap化的DOM元素

比如:ICP备案、PC/mobile切换等

3、屏蔽原生下载信息

wap2app发布成原生apk/ipa或流应用,在手机用户眼里就是App,在App中出现App的下载链接是不科学的。同时流应用中默认也不支持下载apk或跳转到Appstore。

屏蔽方案

在5+引擎下,屏蔽M站元素的方案大致有两种:

  • 服务端直接不生成对应DOM节点
  • 服务端通过css隐藏对应DOM节点

服务端不生成对应DOM节点

如果M站的DOM是服务端渲染的,则可以在判断是5+引擎的环境下,不输出HTML的dom结构,这样即可减少网络传输的html大小,还可以避免wap化的展现形式。

Tips: 判断5+引擎的依据:navigator.userAgent含有“Html5Plus”字符串

如下为一段php示例代码,仅在非5+引擎环境下,才生成web导航栏

<?php  
    $agent = $_SERVER['HTTP_USER_AGENT'];  
    if(strpos($agent,"Html5Plus") === false){//仅在非5+引擎环境下才显示导航栏  
?>  
    <header class="mui-bar mui-bar-nav">  
        <a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a>  
        <h1 class="mui-title">web导航栏</h1>  
    </header>  
<?php  
    }  
?>

服务端隐藏DOM节点

这里以原生下载引导为例,如果原生下载banner是客户端渲染的,则可以通过JS动态隐藏下载banner,如下为示例代码:

if(navigator.userAgent.indexOf("Html5Plus") > -1){  
    downloadEl.style.display = "none";//隐藏下载banner  
}

还有一种更为通用的方式:

  • 探测当前为5+引擎,则在body节点上增加一个"html5plus"的class
  • 将所有需要在5+引擎环境下隐藏的元素,均增加"html5plus-hide"的class

如下为示例代码,5+引擎环境下body节点上增加一个“ html5plus”的class

if(navigator.userAgent.indexOf("Html5Plus") > -1 ){  
    document.body.classList.add("html5plus");  
}

在通用css中增加增加一段代码:

.html5plus .html5plus-hide{  
    display:none  
}

这样,所有需要在5+引擎下需隐藏的元素,均在class中增加"html5plus-hide",既不影响普通浏览器环境的显示,在5+引擎环境下也会自动隐藏。如下是一个简单的HTML示例代码,将js、css全部放在HTML页面中了,真实项目时,迁移到通用的js、css文件即可

<!DOCTYPE html>  
<html>  
    <head>  
        <meta charset="UTF-8">  
        <style>  
            /*5+ 引擎环境下自动隐藏无关元素*/  
            .html5plus .html5plus-hide {  
                display: none  
            }  
        </style>  
    </head>  

    <body>  
        <script>  
            if(navigator.userAgent.indexOf("Html5Plus") > -1) {  
                document.body.classList.add("html5plus");  
            }  
        </script>  
        <!--页面标题,class增加html5plus-hide类-->  
        <header class="mui-bar mui-bar-nav html5plus-hide">  
            <a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a>  
            <h1 class="mui-title">web导航栏</h1>  
        </header>  
        <!--页面主题内容-->  
        <div class="mui-content"></div>  
        <!--下载引导,class增加html5plus-hide类-->  
        <footer class="mui-bar mui-bar-nav download html5plus-hide">  
            <div class="logo"><img src="" /></div>  
            <div class="text">即点即用、流式发行</div>  
            <div class="btn">点击下载</div>  
        </footer>  
    </body>  
</html>

Tips:有些M站可以通过url加一个参数实现不显示原生下载,此时推荐使用这种url

FAQ

Q:如何移除原生导航栏
A:http://ask.dcloud.net.cn/question/50018

继续阅读 »

屏蔽元素类型

在wap2app项目中,M站需要根据运行环境,判断当前是5+引擎时,做一些调整。

Tips: wap2app项目不管是打包成ipa/apk,还是发布成流应用,都依赖HTML5 PLUS引擎,简称5+引擎。

M站需调整的元素,主要包括几个方面:

1、客户端已有增强实现,屏蔽M站原有元素

比如顶部标题栏,wap2app已启用了原生标题栏,M站就无需再显示DIV的标题栏,否则会出现双标题栏的情况。

2、屏蔽明显wap化的DOM元素

比如:ICP备案、PC/mobile切换等

3、屏蔽原生下载信息

wap2app发布成原生apk/ipa或流应用,在手机用户眼里就是App,在App中出现App的下载链接是不科学的。同时流应用中默认也不支持下载apk或跳转到Appstore。

屏蔽方案

在5+引擎下,屏蔽M站元素的方案大致有两种:

  • 服务端直接不生成对应DOM节点
  • 服务端通过css隐藏对应DOM节点

服务端不生成对应DOM节点

如果M站的DOM是服务端渲染的,则可以在判断是5+引擎的环境下,不输出HTML的dom结构,这样即可减少网络传输的html大小,还可以避免wap化的展现形式。

Tips: 判断5+引擎的依据:navigator.userAgent含有“Html5Plus”字符串

如下为一段php示例代码,仅在非5+引擎环境下,才生成web导航栏

<?php  
    $agent = $_SERVER['HTTP_USER_AGENT'];  
    if(strpos($agent,"Html5Plus") === false){//仅在非5+引擎环境下才显示导航栏  
?>  
    <header class="mui-bar mui-bar-nav">  
        <a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a>  
        <h1 class="mui-title">web导航栏</h1>  
    </header>  
<?php  
    }  
?>

服务端隐藏DOM节点

这里以原生下载引导为例,如果原生下载banner是客户端渲染的,则可以通过JS动态隐藏下载banner,如下为示例代码:

if(navigator.userAgent.indexOf("Html5Plus") > -1){  
    downloadEl.style.display = "none";//隐藏下载banner  
}

还有一种更为通用的方式:

  • 探测当前为5+引擎,则在body节点上增加一个"html5plus"的class
  • 将所有需要在5+引擎环境下隐藏的元素,均增加"html5plus-hide"的class

如下为示例代码,5+引擎环境下body节点上增加一个“ html5plus”的class

if(navigator.userAgent.indexOf("Html5Plus") > -1 ){  
    document.body.classList.add("html5plus");  
}

在通用css中增加增加一段代码:

.html5plus .html5plus-hide{  
    display:none  
}

这样,所有需要在5+引擎下需隐藏的元素,均在class中增加"html5plus-hide",既不影响普通浏览器环境的显示,在5+引擎环境下也会自动隐藏。如下是一个简单的HTML示例代码,将js、css全部放在HTML页面中了,真实项目时,迁移到通用的js、css文件即可

<!DOCTYPE html>  
<html>  
    <head>  
        <meta charset="UTF-8">  
        <style>  
            /*5+ 引擎环境下自动隐藏无关元素*/  
            .html5plus .html5plus-hide {  
                display: none  
            }  
        </style>  
    </head>  

    <body>  
        <script>  
            if(navigator.userAgent.indexOf("Html5Plus") > -1) {  
                document.body.classList.add("html5plus");  
            }  
        </script>  
        <!--页面标题,class增加html5plus-hide类-->  
        <header class="mui-bar mui-bar-nav html5plus-hide">  
            <a class="mui-action-back mui-icon mui-icon-left-nav mui-pull-left"></a>  
            <h1 class="mui-title">web导航栏</h1>  
        </header>  
        <!--页面主题内容-->  
        <div class="mui-content"></div>  
        <!--下载引导,class增加html5plus-hide类-->  
        <footer class="mui-bar mui-bar-nav download html5plus-hide">  
            <div class="logo"><img src="" /></div>  
            <div class="text">即点即用、流式发行</div>  
            <div class="btn">点击下载</div>  
        </footer>  
    </body>  
</html>

Tips:有些M站可以通过url加一个参数实现不显示原生下载,此时推荐使用这种url

FAQ

Q:如何移除原生导航栏
A:http://ask.dcloud.net.cn/question/50018

收起阅读 »