ECUI 是以 js es5、css 语法为基础,基于面向对象理念设计的一套企业级前端开发语言。
- 支持一套代码多平台发布,开发者编写一套代码,可发布到iOS、Android、Web(响应式)等多个平台;
- 控件对象化处理,支持对象封装、继承、接口等抽象模式,既保证了较高的扩展性,同时使得对象间解耦,减少多人开发导致的控件冲突等问题;
- 支持单页应用 开发,支持 ES6+ 语法。(打包工具已采用 uglify-es )
- 提供了 pc 和 移动端 开发 常用控件(自带默认样式);
- 通过src/esr.js来统一管理路由;
- 通过src/core.js来对事件与状态进行统一管理;
- 通过src/etpl.js 来实现模板渲染;
- 通过src/adapter.js 来兼容第三方库;
- 通过src/control 来扩展原生 DOM 节点的标准事件;
这里 只介绍在mac环境下 如何快速 从0搭建一个 ecui 项目,并着手进行日常开发。
- node/v9.3.0 以上版本 请用户自行百度安装
- npm/6.1.0 以上版本 请用户自行百度安装
- nginx 请用户自行百度安装
前置假定 node 已安装好,从环境中可以找到node (无论是位于 /usr/local/bin/node 还是PATH中能找到)
>>> npm install [email protected] -g ### js 代码检测器 node-jslint version: 0.12.0 JSLint edition 2013-08-26; 稍后需用欧阳改造后的 lib/jslint.js
>>> npm install [email protected] -g ### css 检测器
>>> npm install [email protected] -g ### css 语法高亮
>>> npm install [email protected] -g ### 高效率压缩 js 文件 ,打包工具,参见 https://github.com/mishoo/UglifyJS2/tree/harmony
安装完jslint后,找到安装路径(mac下默认安装路径:/usr/local/lib/node_modules),使用ECUI项目下的ECUI/tools/jslint.js替换掉安装的jslint中lib目录下的jslint.js文件( /usr/local/lib/node_modules/jslint/lib/jslint.js)
若您的项目不直接在 ECUI 框架根目录下,则需要配置 nginx ,以便您的本地项目可以访问到 ECUI 控件库。 nginx 安装成功以后,在 mac 中打开终端, 执行以下命令:
// 打开 nginx.conf 配置文件后,参照 附录 中的 nginx配置示例, 修改 框架和项目 root 路径和文件名即可。
open /usr/local/etc/nginx/nginx.conf
// 第一次启动,需要root 权限,输入密码
sudo nginx
// 若是启动不成功,则需要多次需改nginx.conf,使用如下命令 重启
sudo nginx -s reload
建议先创建一个工作目录,把框架源码和项目代码 都放在该目录下:
// 创建并进入该目录
mkdir work && cd work
// 下载源码
git clone https://github.com/allskystar/ECUI.git
cd ECUI 在ECUI项目目录下执行 java -jar ecui-doc.jar
您可采用以下几种方式,完成您的项目创建
- 使用脚本 :使用 ECUI下的 generator-ecui.sh 脚本创建项目(脚本长久未维护,可能存在问题)
- 解压 ECUI下的 ecui-demo.zip 文件,作为您的初试项目
- 手动按照框架对目录和文件命名的要求一一创建:
- 您也可以在您已有项目中引用 ECUI 框架
打开终端,在 work目录下执行以下命令:
// 初始化项目并且创建路由 demo.list 和 demo.detail,并且在index中添加路由的链接
./ECUI/generator-ecui.sh -i -r demo.list,demo.detail
在 work 按照以下目录层次创建对应文件,并按照实例 引入 ecui 相关文件:
demo(项目目录)
|
|_ _ _
|_ _ _ _ helloworld
| |
| |_ _ _ _ helloworld.js
| |
| |_ _ _ _ route.helloworld.demo.html
| |
| |_ _ _ _ route.helloworld.demo.js
| |
| |_ _ _ _ route.helloworld.demo.css
|
|_ _ _ _ index.html
|
|_ _ _ _ index.js
|
|_ _ _ _ index.css
<link rel="stylesheet/less" type="text/css" href="ecui.css" />
<link rel="stylesheet/less" type="text/css" href="index.css">
<script type="text/javascript" src="options.js"></script>
<script type="text/javascript" src="ecui.js"></script>
<script type="text/javascript" src="index.js"></script>
根据 nginx.conf 配置的端口号和项目名称,然后在浏览器中输入,如:
http://localhost:9006
使用您喜欢的编辑器 去打开 项目和框架目录即可,有时需要去 看一下框架 源码,这样避免在 业务代码中 做 重复的实现。对于pc和移动端开发的不同,请查看有关教程实例或者联系框架开发者!
请参考下图(DEMO 目录结构):
demo(项目目录)
|
|_ _ _
|_ _ _ demo(文件夹) ------------ demo 目录
| |
| |_ _ _ _define_.js -------- demo 目录下全局js,可用于路由加载
| |
| |_ _ _ _define_.css ------- demo 目录下全局css
| |
| |_ _ _ route.demo.html --- demo 页面
| |
| |_ _ _ route.demo.js ----- demo 页面相应的js文件
| |
| |_ _ _ route.demo.css ---- demo 页面相应的css样式
| |
| |_ _ _ _ _ _ childdemo(文件夹) ------------ childdemo子目录
| |
| |_ _ _ _define_.js ------------- demo子目录js
| |
| |_ _ _ _define_.css ------------ demo子目录css
| |
| |_ _ _ route.childdemo.html
| |
| |_ _ _ route.childdemo.js
| |
| |_ _ _ route.childdemo.css
|
|_ _ _ index.html --------------------- 首页 html
|
|_ _ _ index.js --------------------- 首页对应的 js 文件,全局适用
|
|_ _ _ index.css --------------------- 首页相应的 css 样式,全局适用
- xxx.html、index.js、index.css 为该 xxx 页面的一组文件,请保持命名规范
- define.js 为必要文件,框架根据该文件进行路由扫描,请勿更改文件名
- childdemo 下页面问 demo 的子路由,同级路由写在同一文件夹下即可
ECUI 支持路由与子路由操作,程序按照注册路由对应的目录结构实现路由模式。 ECUI 的路由在每次跳转(也即 url hash变化)时,计算当前路由以及目标路由。与 vue 框架不同的是,vue 仅支持页面级别的缓存,如果同一个页面复用,后面的缓存就会覆盖前者。而 ECUI 通过hash处理,每个页面均为独立的新路由,不会导致定义的复用页面的数据丢失或错乱。举个典型的例子,从列表访问详情页,详情页再打开列表,数次循环。 VUE 框架中,往往这时候 back 到第一个列表页时,当时页面填写的筛选数据等数据就丢失了。而 ECUI 每 i 次打开的列表页为 xxxx~ HISTORY=i , 这个机制可以保存所有页面的缓存数据。 基于上述底层逻辑,ECUI 提供了自动缓存、回填的接口,无需任何额外开发,便可完成。
如 demo 中所示,路由的注册仅需以下三步:
- 步骤一:在 xxx.js ( demo 中为 route.demo.js ) 中调用 ecui.esr.addRoute() 方法添加路由。
/**
* 添加路由信息。
* @public
*
* @param {string} name 路由名称
* @param {object} route 路由对象
*/
addRoute: function (name, route) {}
- route.model : xxx
- route.main : 页面所属容器
- route.view : 页面所属试图
- route.onbeforerequest : 页面发生请求前事件、方法集合
- route.onbeforerender : 页面加载前事件、方法集合
- route.onafterrender : 页面加载后事件、方法集合
ecui.esr.addRoute('demo', {
model: [''],
main: 'main',
view: 'demo_demo',
onbeforerequest: function (context) {
},
onbeforerender: function (context) {
},
onafterrender: function () {
// ecui.get('button_demo').onclick = function () {
// ecui.esr.redirect('/demo/childdemo');
// };
},
});
- 步骤二:在 xxx.html 文件头,加上 “<!-- target: 视图名 -->”,* *视图名务必与第一步注册路由时,route.view 定义的视图名一致**,如下:
<!-- target: demo_childdemo -->
<div class="assess-demo-content">
<a href="#/demo/demo">go-demo</a>
</div>
- 步骤三:在同级目录的 define.js 中调用 ecui.esr.loadRoute() 方法 加载该路由,代码如下:
ecui.esr.loadRoute('childdemo');
在 ECUI 框架中,子文件夹内注册的路由,自动视为子路由。逐级延伸。
您可以通过 a 标签跳转页面,或者调用 ecui.esr.redirect() 方法完成路由跳转。
<a href="#/demo/childdemo/childdemo">go-childdemo</a>
ecui.get('button_demo').onclick = function () {
ecui.esr.redirect('/demo/childdemo/childdemo');//路由构成为 文件夹路径+路由名
};
ECUI通过core.js实现继承和接口处理能力。 调用 core.js 的 inherits()方法定义控件对象,并指定其继承对象。 子类可以直接拥有父类的所有方法,以及可以使用父类定义的全局变量。要想重写父类方法,只需在声明子类时,在“子控件的构造函数” constructor 参数中定义即可。子类若想调用父类的方法,可通过 super.xxx() 进行调用。
/**
* 控件继承。
* 如果不指定类型样式,表示使用父控件的类型样式,如果指定的类型样式以 * 符号开头,表示移除父控件的类型样式并以之后的类型样式代替。生成的子类构造函数已经使用了 constructor/TYPES/CLASS 三个属性,TYPES 属性是控件的全部类型样式,CLASS 属性是控件的全部类型样式字符串。
* @public
*
* @param {function} superClass 父控件类
* @param {boolean} singleton 是否单例
* @param {string} type 子控件的类型样式
* @param {function} constructor 子控件的标准构造函数,如果忽略将直接调用父控件类的构造函数
* @param {object} ... 控件扩展的方法
* @return {function} 新控件的构造函数
*/
inherits: function (superClass, singleton, type, constructor) {}
ui.Control 控件
/**
* 基础控件。
* 基础控件 与 ECUI状态与事件控制器 共同构成 ECUI核心。基础控件扩展了原生 DOM 节点的标准事件,提供对控件基础属性的操作,是所有控件实现的基础。
* options 属性:
* id 名称,指定后可以使用 ecui.get([id]) 的方式获取控件
* uid 唯一标识符,不可自行定义,系统自动生成
* primary 主元素需要绑定的样式
* capturable 是否接收交互事件,如果设置不接收交互事件,交互事件由控件的父控件处理,缺省值为 true
* disabled 是否失效,如果设置失效,控件忽略所有事件,缺省值为 false
* focusable 是否允许获取焦点,如果设置不允许获取焦点,控件的交互事件不会改变当前拥有焦点的控件,用于自定义滚动条,缺省值为 true
* userSelect 是否允许选中内容,缺省值为 true
* @control
*/
ui.Control = core.inherits(
null, //Control 是所有控件对象的父类,它没有父类对象
function (el, options) {
this._eMain = this._eBody = el;
if (options.primary) {
el.className = (options.id || '') + ' ' + el.className + options.primary;
}
// svg classname 是数组 不能做trim操作
if (typeof el.className === 'string') {
this._sClass = el.classList[0];
}
this._bDisabled = !!options.disabled;
this._bCapturable = options.capturable !== false;
this._bUserSelect = options.userSelect !== false;
this._bFocusable = options.focusable;
this._bGesture = true;
this._sSubType = '';
this._aStatus = ['', ' '];
this._sWidth = el.style.width;
this._sHeight = el.style.height;
this._hResize = util.blank;
if (!options.main) {
this._UIControl_oHandler = unitReadyHandler.bind(this);
}
},
{} //此处为 Control 组件定义的事件触发,因代码过长,不在此处展示。
);
Title控件
/**
* 标题栏部件。
* 继承自 ui.Control(所有控件均为 ui.Control 的子类)
*
* @unit
*/
Title: core.inherits( //通过core.inherits() 实现空间继承
ui.Control,
{
/**
* 标题栏激活时触发拖动,如果当前窗体未得到焦点则得到焦点。
* @override
*/
$activate: function (event) {
_super.$activate(event);
core.drag(this.getParent(), event);
}
}
)
ECUI中定义的接口,是指抽象出对象的共有行为(也就是抽象出各种方法),在对象定义的时候,它实现了哪些接口,相当于直接拥有了这些接口的方法。以此实现控件方法的横向扩展,减少重复造轮子的同时,便于一次维护,多方受用。 控件扩展的方法遵循后声明覆盖前者标准,即,相同方法,仅后声明的生效。
调用 core.js 的 interfaces()方法声明接口
/**
* 接口声明。
* @public
*
* @param {string} name 接口名称
* @param {Array} superClass 接口的基类的数组
* @param {object} methods 接口的方法集合
* @param {function} interceptor 拦截器
* @return {Interface} 接口定义
*/
interfaces: function (name, superClass, methods, interceptor) {
接口某种意义上,就是控件的扩展方法。所以,在定义控件的inherits()方法,将接口传入“控件扩展的方法”即可实现该接口。同时,可以通过继续传入自定义方法,来实现接口方法的重写。 可使用 _class.xxx() 调用接口链上之前接口里的方法。
/**
* 下拉框控件。
* 扩展了原生 SelectElement 的功能,允许指定下拉选项框的最大选项数量,在屏幕显示不下的时候,会自动显示在下拉框的上方。在没有选项时,下拉选项框有一个选项的高度。下拉框控件允许使用键盘与滚轮操作,在下拉选项框打开时,可以通过回车键或鼠标点击选择,上下键选择选项的当前条目,在关闭下拉选项框后,只要拥有焦点,就可以通过滚轮上下选择选项。
* options 属性:
* optionSize 下拉框最大允许显示的选项数量,默认为5
* required 是否必须选择
* @control
*/
ui.Select = core.inherits(
ui.abstractSelect,
'ui-select',
function (el, options) {
_super(el, options);
// 初始化下拉区域最多显示的选项数量
this._nOptionSize = options.optionSize || 10;
},
{},
ui.iPopup, ui.iResource,//这里实现了 ui.iPopup、ui.iResource 两个接口
{}//这里可以传入方法重写ui.iPopup的方法。同时可以自定义控件方法
);
通过在属性名前加 下划线“_”(如 _name),可将该属性定义为私有变量,私有变量仅改类可以访问,其子类无法访问。因此,为避免程序异常,请勿在该类方法之外的地方引用私有变量。 顺带一提,框架通过以下两种方式实现变量的绝对私有,保障私有变量隔离:
- 调试过程中,框架提供了pushCaller与popCaller方法检查私有化属性
- 打包编译后,私有化属性会重新命名
将变量命名以_ECUI_开头,即可认定为全局变量,全局变量可作用于该对象及其所有子类访问。
请注意,不要把 filepath 、root 之类的目录位置原封不动的照抄! 根据你实际情况来填写
该配置示例要求 框架和业务项目的目录 在同一目录下,主要更改以下地方:
- 将 root 后面的路径 改为 您电脑上 ecui 框架或业务项目 的对应目录;
请各位同事在研发态 给本机 nginx 静态资源的 location 段加一下 Cache-Control 控制,研发时,一些 css html jpg 废不了太多流量
location ~ /你的路径规则 {
add_header Cache-Control 'no-store,no-cache,must-revalidate';
add_header xx-via 'developer-你的名' ; ## 这样可以明确的从http响应头 感知到 静态资源来自你机器
……
}
下方是一个简易的配置文件
#控制工作进程数
worker_processes 1;
error_log /tmp/nginx_error.log notice;
#包含nginx中所有处理连接的设置
events {
#工作进程的最大连接数量
worker_connections 1024;
}
#控制 nginx http处理的所有核心特性
http {
#该文件内定义指定文件头所对应的文件格式
include mime.types;
default_type application/octet-stream;
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for"';
#指定 nginx 是否调用sendfile 函数(zero copy 方式)来输出文件,对于普通应用,必须设为on。
#如果用来进行下载等应用磁盘IO重负载应用,可设置为off,以平衡磁盘与网络IO处理速度,降低系统负载
sendfile on;
#keepalive_timeout 参数是一个请求完成之后还要保持连接多久,不是请求时间多久,
#目的是保持长连接,减少创建连接过程给系统带来的性能损耗,类似于线程池,数据库连接池。
keepalive_timeout 65;
#本地开发时用于请求项目的静态文件,需要在hosts中配置 127.0.0.1 为localhost(Mac一般默认配置)
#否则通过127.0.0.1加 文件路径进行访问
server {
listen 9006; # 一般习惯设置80,但很多开发者 本机80端口常被占
server_name localhost; # 请确认是本机,最好解析为127 以免走入 ipv6型回路
#配置默认请求为 ecui-demo 的静态资源
set $root '/Users/chentiancheng/IdeaProjects/ecui-demo';
set $ecui_root '/Users/chentiancheng/IdeaProjects/ECUI';
root $root; #项目所在的路径
index index.html index.htm;#默认请求的文件
# ECUI文件本地调试
location ~ ^/(ie-es5\.|ecui\.|compatible-2\.0\.0|options\.|common\.|update\.|common/|tools/|css/|src/|images/ecui/).*$ {
if ( !-f $document_root$uri ) {
proxy_pass http://localhost:8000;
}
root $root;
index index.html index.htm;
}
location ~ /(_layer_\.js|_define_\.(css|html)|layer\.[^/]+\.(js|css|html))$ {
if ( !-f $document_root$uri ) {
# default_type text/html;
add_header Content-Type 'text/html; charset=utf-8';
return 200 '';
}
root $root;
}
# 打包后工程访问
location ~ ^/output-.*/[/\w-]+$ {
rewrite /output-(.*) /$1;
}
location = / {
root $root;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
#控制开发环境中用于请求的ecui有关文件
server {
listen 8000;
set $root '/Users/chentiancheng/IdeaProjects/ecui-demo';
set $ecui_root '/Users/chentiancheng/IdeaProjects/ECUI';
location / {
# 引入文件所在的路径
root $ecui_root;
}
}
}