hydra 是一个移动端轻量级的mvc框架,实现了路由,模板系统,组件系统,实现了从model到view的单向绑定,模板底层使用virtual dom差量更新。

目录结构

+demo
    +src
        +css
        +images
        +--js
            +--component
            +--controllers
                ---AppController.js
                ---IndexController.js
            +--stores
                ---listStore.js
            +--views
                ---index.tpl
            ---app.js
            ---hydra.js
        ---index.html
	---zebra.js
    ---zebra.config.js

html

app.js

	var app = window.app = hydra.module('koala-demo');

	app.config({
		base: 'js',
		router: {
			'/': {
				controller: 'IndexController',
				template: 'views/index'
			}
		},
		view: '#view'
	});

模板 views/index.tpl

控制器 IndexController.js


app.controller('IndexController', 'stores/listStore', function (listStore) {

	var sort = 'asc';
	Template.index.events({
		'.del-button click': function (e) {
			var did = this.parentNode.parentNode.id.split('_');
			listStore.deleteItem(did[1]);
		}
	});

	listStore.initialState();

});

store listStore.js


app.store('listStore', function (state) {
	var root = this;

	return {
		initialState: function () {
			var data = {
				topLevel: '1',
				list: [{
					id: 1,
					avatar: 'images/avatar1.jpg',
					name: '比尔·盖茨',
					fortune: 5200,
					company: '微软',
					age: 59,
					country: '美国'
				}, {
					id: 2,
					avatar: 'images/avatar2.jpg',
					name: '卡洛斯·斯利姆·埃卢家族',
					fortune: 5100,
					company: '美洲电信',
					age: 59,
					country: '墨西哥'
				}, {
					id: 3,
					avatar: 'images/avatar3.jpg',
					name: '沃伦·巴菲特',
					fortune: 4600,
					company: '伯克希尔·哈撒韦',
					age: 84,
					country: '美国'
				}]
			};
			state.setState(data);
		},

		deleteItem: function (did) {
			var data = state.getState();
			data.list = data.list.filter(function (item) {
				if (item.id == did) {
					return false;
				} else {
					return true;
				}
			});
			state.setState(data);
		}
	};
});

自己搭建一个静态服务器, 然后编译demo node zebra.js -w 完整代码请参见 github 项目 https://github.com/lin-xi/hydra/tree/master/examples/todo




模板系统

1. 模板系统

1.1 模板语法

模板使用 mustang 系统,github地址为:http://github.com/lin-xi/mustang,mustang的风格和handlerbars 比较像。

普通的绑定
var tpl = '

'; hydra.render(tpl,{country:"中国",address:{city:"北京"}}, function(text){ console.log(text); //'

中国

北京

});
循环
var tpl = '

'; hydra.render(tpl,[{country:"中国"}, {country:"美国"}, {country:"阿富汗"}], function(text){ console.log(text); //

中国

美国

阿富汗

});
If 判断
var tpl = '
-老得很

-老头
';

hydra.render(tpl,{age:"68"}, function(text){
	console.log(text); //老头
});
引入另一模板
var tpl = '

'; //js/views/antherTemplate.tpl hydra.render(tpl,{time: new Date().getTime()}, function(text){ console.log(text); //1445942479195 });
上面的语法可以组合使用

1.2 模板js文件

可以给模板指定一个同名的js文件,模板在渲染时会先执行js文件 view/temp.tpl

其中, dateTime 为filter,可以对time的值进行处理 view/temp.js
//定义一个helper值
Template.newTemplate.helpers({
	time: function (argument) {
		return new Date().getTime();
	}
});

//定义一个fitler
Template.newTemplate.filters({
	dateTime: function (input) {
		var out = "";
		if (input && input != '0') {
			var d = new Date(input);
			var dt = [],
				ti = [];
			dt.push(d.getFullYear());
			dt.push(d.getMonth() + 1);
			dt.push(d.getDate());
			ti.push(d.getHours() < 10 ? '0' + d.getHours() : d.getHours());
			ti.push(d.getMinutes() < 10 ? '0' + d.getMinutes() : d.getMinutes());
			ti.push(d.getSeconds() < 10 ? '0' + d.getSeconds() : d.getSeconds());
			out = dt.join('/') + ' ' + ti.join(':');
		}
		return out;
	}
});
模板渲染结果
hydra.load('view/temp').render('#demo', {}); //
2015/10/27 22:30:30
模板的名称为newTemplate,在任何js中,可以通过Template.模板名 添加helpers和filters。

1.3 事件绑定

对模板中dom元素的事件绑定可以通过如下方式
Template.index.events({
	'.del-button click': function (e) {
		var did = this.parentNode.parentNode.id.split('_');
		listStore.deleteItem(did[1]);
	}
});

2. store

store主要负责数据的获取,并将修改和删除同步到数据源,并且通知模版系统将数据修改同步到dom上。

2.1 创建

app.store('listStore', function (state) {
	var me = this;

	return {
		initialState: function () {
			me.get('/getListData', {}, function (data) {
			 	state.setState(data);
			});
		},

		deleteItem: function (did) {
			var data = state.getState();
			data.list = data.list.filter(function (item) {
				if (item.id == did) {
					return false;
				} else {
					return true;
				}
			});
			me.post('/removeListItem', {id: did}, function () {
			 	state.setState(data);
			});
		}
	};
});

创建一个store时,会注入state对象。 state对象有两个方法 state.getState(); 返回当前数据 state.setState(); 设置当前数据 setState时,会自动将新数据渲染到dom上

2.2 store使用

store可以注入到controller中
app.controller('IndexController', 'stores/listStore', function (listStore) {
	listStore.initialState();
});

2.3 store上下文

store中的this被注入了StoreUtil对象,封装了ajax的get和post方法



3. 组件系统

3.1 创建组件

hydra.component('AlertDialog', {
	props: {
		title: '提示'
	},
	template: __inline('./alertDialog.tpl'),
	render: function () {
		var el = this.$el;
		var dialog = el.querySelector('.dialog');

		var w = dialog.offsetWidth,
			h = dialog.offsetHeight;

		var l = (window.innerWidth - w) / 2;
		var t = (window.innerHeight - h) / 2;

		dialog.style.left = l + 'px';
		dialog.style.top = t + 'px';
	},
	events: function () {
		var el = this.$el;

		return {
			'.close-button click': function (e) {
				el.parentNode.removeChild(el);
			}
		};
	}
});
在Component渲染到dom上之后,会调用render方法,然后进行事件绑定。 render和events的上下文中,会注入Component的根节点$el。 __inline('./alertDialog.tpl'); zebra在预编译的时候会将tpl的内容注入

3.2 使用组件

在template中使用tag

直接加载

hydra.loadComponent('alertDialog', function (alertDialog) {
	alertDialog.newInstance({
		transclude: '添加成功',
	})
});

3.3 组件通信

组件间可以通过内置的事件管道进行通信,同一个管道中的各个主体都能相互直接通信,能接受或者是发送事件。
hydra.component('AlertDialog', {
    props: {
        title: '提示'
    },
    template: __inline('./alertDialog.tpl'),
    render: function () {
        ......
    },
    events: function () {
        var el = this.$el;
        var pipe = this.eventPipe('stateChange');
        pipe.receive({
	        'close': function(){
		        el.parentNode.removeChild(el);
	        }
        });
        
        return {
            '.close-button click': function (e) {
                el.parentNode.removeChild(el);
                pipe.write('close');
            }
        };
    }
});

4 编译构建工具

4.1 zebra.js

推荐使用zebra.js,项目地址http://lin-xi.github.io/zebra zebra提供了对 __inline 的支持,以及生成resourceMap zebra的配置如下:
{
	"base": "./src",
	"output": "./static",
	"rules": {
		"js": {
			"compile": false,
			"uglify": false,
			"md5": {
				"exclude": []
			}
		},
		"css": {
			"compress": true,
			"md5": {
				"exclude": []
			}
		},
		"html": {
			"resourceMap": true
		},
		"image": {},
		"other": {
			"copy": true
		}
	},
	"deploy": [{
		"receiver": "http://cq02-map-sv-control04.cq02.baidu.com:8890/receiver.php",
		"from": "./static",
		"to": "/home/map/odp_crm/webroot/mobile-framework",
		"exclude": "svn|rar|psd|docx"
	}],
	"clean": true
}
执行 node zebra.js -w 之后,zebra会自动监听src目录文件的修改,然后重新编译发布到static目录下。



1. hydra

1.1 hydra.moudule()

创建一个模块,返回hydra模块对象 app
var app = hydra.module('app');

1.2 hydra.load()

加载一个模板文件,返回模板对象 MTemplate
var template = hydra.load('view/index');

1.3 hydra.render()

渲染模板
var template = hydra.render('

', {country: '中国'}, function(html){ console.log(html); });

1.4 hydra.component()

创建一个组件
hydra.component('AlertDialog', {
	props: {
		title: '提示'
	},
	template: __inline('./alertDialog.tpl'),
	render: function () {
	},
	events: function () {
		var el = this.$el;

		return {};
	}
});

1.5 hydra.loadComponent()

加载一个组件,返回组件类
hydra.loadComponent('alertDialog', function (alertDialog) {
	alertDialog.newInstance({
		transclude: '添加成功',
	})
});

2 app模块

2.1 app.config()

配置app的根目录,路由,的视图容器
app.config({
	"base": 'js',
	"router": {
		'/': {
			controller: 'IndexController',
			template: 'views/index'
		}
	},
	"view": '#view'
});

2.2 app.controller()

创建一个controller

2.3 app.store()

创建一个store

2.4 app.redirect()

跳转到一个路由
app.redirect('/product');

2.5 app.setRouteParam()

设置路由参数,跳转时,可以给另一个控制器传参
app.setRouteParam({shopId: 30872628});

2.6 app.getRouteParam()

获取路由参数
var shopId = app.getRouteParam('shopId');

2.7 app.removeRouteParam()

删除路由参数
app.removeRouteParam('shopId');

2.7 app.emptyRouteParam()

清空路由参数
app.removeRouteParam('shopId');

2.8 app.getCurrentRoute()

获取当前的路由路径
var route = app.getCurrentRoute();

2.9 app.getCurrentTemplate()

获取当前的模板对象,返回模板对象
var template = app.getCurrentTemplate();

controller scope

在controller中this会被替换成scope对象

3.1 scope.setState()

设置当前数据,controller的视图重新渲染
scope.setState(data);

3.2 scope.apply()

强制controller的视图重新渲染
scope.apply();

4 模板实例对象 XTemplate

4.1 XTemplate


加载解析一个模板后,会生成一个模板的实例对象 通过 Template.index 访问

4.2 XTemplate.helpers()

生成一个helper
Template.index.helpers({
	time: function (argument) {
		return new Date().getTime();
	}
});

4.2 XTemplate.filters()

生成一个filter
Template.brandItem.filters({
	dateTime: function (input) {
		var out = "";
		if (input && input != '0') {
			var d = new Date(input);
			var dt = [],
				ti = [];
			dt.push(d.getFullYear());
			dt.push(d.getMonth() + 1);
			dt.push(d.getDate());
			ti.push(d.getHours() < 10 ? '0' + d.getHours() : d.getHours());
			ti.push(d.getMinutes() < 10 ? '0' + d.getMinutes() : d.getMinutes());
			ti.push(d.getSeconds() < 10 ? '0' + d.getSeconds() : d.getSeconds());
			out = dt.join('/') + ' ' + ti.join(':');
		}
		return out;
	}
});
日期格式化

4.2 XTemplate.events()

绑定dom事件
Template.index.events({
	'.sort1 click': function (e) {
		sort = sort == 'asc' ? 'desc' : 'asc';
		listStore.sortItem(sort);
	},
	'.del-button click': function (e) {
		var did = this.parentNode.parentNode.id.split('_');
		listStore.deleteItem(did[1]);
	}
});

4.2 XTemplate.compile()

编译模板
Template.index.compile({
	content: '

', //模板字符串 data: , //当前作用域数据 global: , //整个数据 _filters: filters //当前模板的filters });

4.2 XTemplate.eval()

执行表达式
Template.index.eval("country == '中国'",, );
 //参数1:表达式
 //参数2:当前作用域数据
 //参数3:整个数据

4.2 XTemplate.bindEvent()

模板绑定事件
Template.index.bindEvent();
 //系统内部自动调用

5 模板对象 MTemplate

5.1 MTemplate

var template = hydra.load('view/index');

5.2 MTemplate.render()

将指定的数据渲染到指定的dom中
var template = hydra.load('view/index');
template.render(nodeSelector, data); 
//nodeSelector  dom
//data 数据

6 组件对象 XComponent

6.1 XComponent

var component = hydra.loadComponent('alertDialog');

6.2 XComponent.new()

创建组件实例
var component = hydra.loadComponent('alertDialog');
component.new('com_43d82f2a54', 'instance_name', {transclude:‘hello,world'}, 'index'); 
 //参数1:component 唯一Id
 //参数2:实例名称
 //参数3:属性对象
 //参数4:模板名称
一般为系统内部调用

6.3 XComponent.newInstance()

创建组件实例
var component = hydra.loadComponent('alertDialog');
component.newInstance(document.body, {transclude:'hello,world'}); 
//参数1:渲染的dom节点
//参数2:属性对象

组件

帮助

Release Note

thanks