当前位置 : 主页 > 网页制作 > Dojo >

dojo 1.7简易入门教程(转)

来源:互联网 收集:自由互联 发布时间:2021-06-15
最近在做一个公司的项目,我们公司的角色是前端制作(HTML+CSS+JS),开发方为IBM,所以前端的Javascript被指定了使用 dojo 。本来是没有我什么事情,可是偏偏眼睛里不容沙子,非得要求

最近在做一个公司的项目,我们公司的角色是前端制作(HTML+CSS+JS),开发方为IBM,所以前端的Javascript被指定了使用dojo。本来是没有我什么事情,可是偏偏眼睛里不容沙子,非得要求重构,于是开始漫漫的HTML重构和使用dojo的过程。初初上手dojo,真的觉得十分苦恼,因为介绍它的文档实在少之又少,尤其是1.7版本。偏偏1.7版本又是dojo一个较为重要的升级版本——引入了AMD的机制,所以想要把代码写得更加dojo些,还着实花了我不少的精力去看这个新版本的源代码,包括dijit和dojox部分。

基于dojo 1.7的js目录规划

为什么要说这点呢,因为实现了AMD机制的,如require.js的,对目录的规划尤为重要,因为涉及到require加载的模式,而dojo 1.7尤甚。

基本上,dojo1.7已经默认会认为你的目录是基于如下的规划的:

/webroot/js/dojo/
/webroot/js/dijit/
/webroot/js/dojox/

则,当一个/webroot/index.html的页面,尝试按照上述目录加载的时候:

<script type="text/javascript" src="js/dojo/dojo.js"></script>

它会默认认为,js的目录,为所有modules的基础目录,即当你希望添加一个叫做app的新module时:

define('app', ['dojo/_base/kernel'], function(dojo) {
	return {
		anyMethod: function() {
			// ...
		}
	}
});

require(['app'], function(app) {
	app.anyMethod();
});

而该文件则放在/webroot/js/app.js目录下即可。

当然,dojo本身提供多种声明dojoConfig的模式,但是根据我的尝试,会认为上述的实践模式会令项目较为简单且易于他人理解。dojo提供了baseUrl用于声明require的基础路径入口,我尝试了,由于dojo本身的目录层级问题,会存在一些小问题,类如:

baseUrl: 'js/modules' // 尝试告诉dojo,所有项目自定义的modules的目录

而由于dojo的目录层级关系(js/dojo,为该目录的上一级),他会同样认为你声明的这个目录也是在js/module的上一级,即还是指向了js这个目录。当然,我也可以将baseUrl改为js/modules/any,来强迫使他指向js/modules,但这实在不雅。

当然,实在不行,你还是可以通过声明paths、packages等模式来强迫指定某个基础的paht、package的入口,但是总归来说,还是让理解项目结构、代码等造成更大的困扰性,都就此作罢。既然dojo本就如此,就按照回他本身默认的机制、设定去规划目录,将会是走最少弯路的实践模式。

dojoConfig声明模式和重要参数说明

dojoConfig声明模式支持3种:

<script type="text/javascript">
var dojoConfig = {
	async: false
	// more ...
}
</script>
<script type="text/javascript" src="js/dojo/dojo.js"></script>

 

<script type="text/javascript" src="js/dojo/dojo.js" data-dojo-config="async: false, parseOnLoad: false, isDebug: true"></script>

 

<script type="text/javascript" src="js/dojo/dojo.js"></script>
<script type="text/javascript">
require({
	async: false,
	parseOnLoad: false
});
</script>

实践证明,第二种模式会是比较易于理解,且又不会在页面写太多代码的模式。

而dojoConfig中,比较重要的参数即为async和parseOnLoad(就我目前的使用程度来说),简单介绍如下:

async: true | false | legacyAsync

async表示的是dojo core(其实就是dojo/dojo.js所定义的模块、方法)是否使用异步的模式加载,而其中,我至今未试验出legacyAsync的模式为何,而true | false主要差异为:

async: true,启用异步模式,则不存在全局对象dojo,而且你即使:

require(['dojo/_base/kernel'], function(dojo) {
	console.log(dojo);
});

你会发现dojo的实例里面,只包含了最精简的一些方法和api,你需要逐个逐个你需要使用的module去将其加载进去。

而使用了false,则dojo/dojo.js中声明定义了的API,他都会以全局的dojo实例装载之。

使用true和false,分别有其用途,尤其在使用了true,我认为,是可以同时使用多个不同版本的dojo实例分别使用的(只是显得有些叶公好龙、画蛇添足)。

而parseOnLoad,则是指dojo在页面自动加载时,会使用dojo/parser库自动解析页面指定了data-dojo-*的DOM标签,比如<select data-dojo-type="dijit.form.ComboBox">,自动转化为dijit.form.ComboBox的widget。不过话说,如果你启用了parseOnLoad: true,碰到页面有这种data-dojo-type,而你又没有事先require了相关的类库,他是会抛出一个js error的。而且说句实话,光是一个dijit.form.ComboBox,他就会require一大堆js文件,真是为这个页面感到费劲啊。

DOM基础使用习惯

据我所知,dojo曾在某一段时间内和MooTools合作过,dijit就是他们合作后的产物之一,而包括像David Walsh等,这些MooTools开发团队成员,也在自己的博客大力鼓吹Dojo(至今),为其拉广告、做宣传,所以可以想见dojo受MooTools的影响之深。

而事实上,dojo的DOM方法中,存在和MooTools极其接近的定义结构,他同时支持两种DOM查询模式:

1、传统的byId的模式,dojo采用的是dojo.byId('elId'),而MooTools则是document.id('elId')。

2、xpath的查询模式,dojodojo.query('#id'),而MooTools则是,document.getElements('#id')。

为何说dojo更加类MooTools而不是说类jQuery呢(同样支持xpath的query),基于以下几点:

1、jQuery的概念里面,是不存在支持byId的查询模式的。

2、 jQuery的操作模式里面,是不支持批量操作的,即,jQuery('.submit-btn').css(),表示的是对该query的第一个element进行操作  而MooTools和dojo的操作是批量的操作,即:document.getElements('.submit-btn').addClass('focus'),或者dojodojo.query('.submit-btn').style({ color: 'red' })

3、而且从定义对象结构上来说,dojo更像MooTools,即:
dojo:
dojo.byId() -> HTML Element实例
dojo.query() -> dojo.NodeList类实例
MooTools:
document.id() -> Element类
document.getElements() -> Elements类实例

4、链式操作更类似MooTools,即:
dojo:
dojo.query('.any-class').query('anySubElement').doSomeThing();
MooTools:
document.getElements('.any-class').getElements('anySubElement').doSomeThing();

所以在使用dojo的DOM方法的时候,最好养成一个统一的使用习惯,不然一会儿dojo.byId,一会儿dojo.query,非让日后跟进代码的人看得头冒青烟不可。

在这里,我推荐使用dojo.NodeList的模式作为基础操作,原因基于如下:

1、dojo.NodeList更易于使用链式操作模式,即:dojo.query('.any-class').attr({  }),但是千万注意,这里会对所有query到得element做同样的attr操作。

2、dojo为了控制性能和不污染原生DOM对象,不采用原型链的方式去扩展DOM Element,于是操作一个原生的DOM Element在dojo里的做法显有些蹩脚,如:dojo.attr(DOMElement, {}),dojo.style(DOMElement, {}),使用起来真的不太舒服。

于是有人会问,假定我已经获得了某个DOMElement,那如何能快速的进行操作呢?以下举个例子:

 

// $ 伪装成大家习惯的jQuery操作符
require(['dojo/_base/kernel', 'dojo/dom', 'dojo/query'], function(dojo, dom, $) {
	var el = dom.byId('el_id');
	// 正统的操作模式应该是:
	dojo.style(el, { display: 'none' });
	// 不过事实上,你可以这样写,会让你的代码写得舒服,别人读起来也更加舒服
	$(el).style({ display: 'none' });
	// 这个例子是可以延伸发展下去的:
	$($('body')[0]).style({ display: 'none' }); // 这是类jQuery的查询模式
	$($('#any_id')[0]).style({ height: 100 + 'px' });
});

 

 

如果$('#any_id')[0],不存在,则$($('#any_id')[0]),的NodeList内容是空的,则其后的style操作也不会继续进行,这可以缓解很多时候,为了判断某个element是否存在,而写了大堆if ... else的判断结构。

dojo的DOMEvent

dojo的DOM事件,潜伏得比较深,不过为了深入简出,这里只谈两个问题:

1、怎么快速绑定事件?

2、怎么stop一个事件。

基于上述的NodeList的操作习惯,有以下的代码:

 

require(['dojo/_base/kernel', 'dojo/query'], function(dojo, dom, $) {
	$('body').connect('mousemove', function(event) {
		
	});
	$('tag').on('click', function(event) {
		
	});
});

 

connect是 dojo一个比较特殊的方法,他用于实现了对任意对象,实现事件注入(类似Qt框架的信号槽的机制),很多对象,类如 dojo.Animate的事件绑定,也是经过此方法绑定。

 

stopEvent的方式:

 

require(['dojo/_base/kernel', 'dojo/query', 'dojo/_base/event'], function(dojo, $) {
	$('body').connect('mousemove', function(event) {
		dojo.stopEvent(event); // 是不是有点好像以前写JS原生的stop event呢?
	});
});

 

和jQuery不同,jQuery的事件只要return false即可, dojo需要额外引入  dojo/_base/event并执行 dojo.stopEvent(event),这让我想很早以前兼容IE和Firefox时候的写法,event = event || window.event。

 

Animate的使用

dojo的Animate,顶多只实现到了mootools的tween的级,还没到Fx.Morph。他实现代码上,当你使用dojo.animateProperty创建一个新的animate实例时,他会不断的创建新的animate实例,如果大量的这样写,你会发现你写的JS效果,大量存在:非同步,抖动厉害的情况出现。不过这个问题,也非一时半刻能说得明白的问题,对于简单的特效,还是可以使用dojo.animateProperty的,但是对于稍微复杂一些的效果的时候,则需要注意用法,这里简单说明一下:

 如上图显示,眼下要做一个Slider,里面除了有两个toPrev、toNext两个操作按钮外,中间有一个存放图片的容器,这个容器用于展现该容器内的图片的滚动。

假定我们限定了SliderWrap的宽度,比如370px,使其永远只会显示两张图片,要显示更多的图片,我们将对SliderWrap使用margin-left,使其margin-left为-xxxpx来实现其slider的滚动。

一个基本的做法就是:

 

$('#toPrev').connect('click', function() {
	dojo.animateProperty({
		node: $('#SliderWrap')[0],
		properties: {
			marginLeft: anyPx
		}
	}).play(;
});

$('#toNext').connect('click', function() {
	dojo.animateProperty({
		node: $('#SliderWrap')[0],
		properties: {
			marginLeft: anyPx
		}
	}).play();
});

 

但是,一个预料之外的事情的发生了,用户狂点toNext的按钮,这时候,你的slider滚动的就没有那么流畅了,而是会出现卡顿、不流畅、不自然等情况发生(因为多次click的事件被执行了)。使用click作为触发的event还是比较好处理的,因为他必须满足click的条件——mousedown & mouseup,如果你将触发的事件设置为mouseenter(mouseover),恐怕情况会急剧的恶化下去。

如果用MooTools来实现同样的效果,会考虑改为如下:

 

var fx = new Fx.Morph($('SliderWrap'));

$('toPrev').addEvent('click', function() {
	fx.pause().start({ 'margin-left': anyPx });
});

$('toNext').addEvent('click', function() {
	fx.pause().start({ 'margin-left': anyPx });
});

 

差异主要在于pause(),假定用户以很高频率点击toNext,那么不管之前fx播放到什么情况,都先暂停执行,而后将fx播放至用户当前的请求操作下。

 

而同样的,在dojo,则需要这样做:

 

var anim = dojo.animateProperty({
	node: $('#SliderWrap')[0]
});

$('#toPrev').connect('click', function() {
	anim.properties = {
		marginLeft: anyPx
	};
	anim.stop().play();
});

$('#toNext').connect('click', function() {
	anim.properties = {
		marginLeft: anyPx
	};
	anim.stop().play();
});

 

附录1:常用方法整合

至此,dojo使用上的一些小问题,已经基本解决,不过为了辅助dojo的编程,我还是写了几个小方法,为了改善dojo的编程代码过于冗余,过于繁重的情况,附录如下:

 

 

(function(app) {

	/**
	 * 获取item的类型,即typeof item === ?
	 *
	 * 取自MooTools 1.4
	 */
	var typeOf = this.typeOf = function(item) {
		if (item == null) return 'null';
		if (item.nodeName){
			if (item.nodeType == 1) return 'element';
			if (item.nodeType == 3) return (/\S/).test(item.nodeValue) ? 'textnode' : 'whitespace';
		} else if (typeof item.length == 'number'){
			if (item.callee) return 'arguments';
		}
		return typeof item;
	};

	/**
	 * 判断某个item是否为某个对象的实例
	 *
	 * 取自MooTools 1.4
	 */
	var instanceOf = this.instanceOf = function(item, object) {
		if (item == null) return false;
		var constructor = item.$constructor || item.constructor;
		while (constructor){
			if (constructor === object) return true;
			constructor = constructor.parent;
		}
		/*<ltIE8>*/
		if (!item.hasOwnProperty) return false;
		/*</ltIE8>*/
		return item instanceof object;
	};

	define('app', ['dojo/_base/kernel', 'dojo/_base/lang', 'dojo/dom', 'dojo/query', 'dojo/_base/NodeList', 'dojo/_base/event'], function(dojo, lang) {

		/**
		 * 传入DOM Element | dojo.NodeList | string,取出指定index的DOM Element
		 * 为了在某些场景需要明确的操作某个DOM Element使用
		 *
		 * @param DOMElement|dojo.NodeList|string el
		 * @param int index
		 * @return DOMElement|false
		 */
		var element = app.element = function(el, index) {
			if (!index)
				index = 0;
			if (typeOf(el) == 'string') {
				var elById = dojo.byId(el);
				if (el.match(/\#|\.|\s/, el) || !elById)
					el = dojo.query(el);
				else
					el = elById;
			}
			if (typeOf(el) == 'object' && instanceOf(el, dojo.NodeList))
				el = el[index];
			return typeOf(el) == 'element' ? el : false;
		};

		/**
		 * 执行animate,并返回该animate对象
		 *
		 * @param DOMElement|dojo.NodeList|string el
		 * @param object styles 需要animate执行的样式
		 * @param object args   其他animate参数
		 * @return dojo.Animate
		 */
		var animate = app.animate = function(el, styles, args) {
			if (element(el) !== false) {
				if (typeOf(args) === 'function') {
					args = { onEnd: args };
				}
				else {
					args = args || {};
				}
				args.node = element(el);
				args.properties = styles;
				return dojo.animateProperty(args).play();
			}
			return false;
		};

		/**
		 * 生成animate对象,但不play
		 *
		 * @param DOMElement|dojo.NodeList|string el
		 * @param object styles 需要animate执行的样式
		 * @param object args   其他animate参数
		 * @return dojo.Animate
		 */
		var animateObject = app.animateObject = function(el, styles, args) {
			if (element(el) !== false) {
				if (typeOf(args) === 'function') {
					args = { onEnd: args };
				}
				else {
					args = args || {};
				}
				args.node = element(el);
				args.properties = styles;
				return dojo.animateProperty(args);
			}
			return false;
		};

		/**
		 * 当某个DOMEvent发生时,判断当前DOMEvent是否经过某个DOMElement
		 *
		 * @param DOMElement|dojo.NodeList|string el
		 * @param DOMEvent event
		 * @return bool
		 */
		var hovered = app.hovered = function(el, event) {
			if (element(el) === false)
				return false;
			var coords = dojo.coords(element(el));
			var x = event.pageX, y = event.pageY;
			return (x >= coords.x && x <= coords.x + coords.w && y >= coords.y && y <= coords.y + coords.h)
		};

		return app;
	});

}).call(this, this.app || {});

 

整体而言, dojo还是一个很不错的框架,只是文档少得有些可怜,而且大多比较老,想用好 dojo还是得费一番心思才行。
网友评论