UEditor的图片上传采用了Flash上传的方式,在功能上支持批量、本地预览和实时进度提示,在界面上支持自定义背景、上传按钮和预览框等视觉元素的样式属性,基本能够满足各种用户的不同上传需求。
由于涉及到了后端开发语言、flash、js和html等各种不同的web元素,图片上传这块的配置和使用相对来说稍显复杂,不过只要你认真看完以下的内容,那一切都不是什么问题了。下面我们仍然以上文中的完整版实例来一步一步完成这个过程。
先来回顾一下完整版部署章节中提到的项目目录结构,如下左图所示。为了更加符合一般网站的具体实际,也为了更清晰地展示整体的路径配置流程,我们先对这个目录做一下小的改动:将ueditor目录下的server文件夹提取出来放置到根目录中的admin文件夹下。调整后的目录结构如下右图所示
首先来看前端部分的文件及其配置。打开dialogs/image文件夹,我们可以发现四个文件:
• callback.js:本上传模块支持的所有回调函数,你可以按需选用其中的内容。对于不需要做二次开发的你说,可以放心地直接删除。
• image.html:图片上传对话框的主体文件。前端的配置和修改基本在此处完成。
• imageUploader.swf:Flash上传文件。
• tangram.js:百度前端框架tangram封装的flash操作模块。一般无需修改。
打开image.html文件,找到baidu.swf.create()方法,有关图片上传的大部分配置都在这里进行。这其中的绝大部分参数相信你对着注释都能很容易明白意思,下面将挑选几个需要稍微解释的参数进行讲解:
首先是backgroundUrl、listBackgroundUrl和buttonUrl这三个参数。UEditor为了让上传flash的界面能够满足各种不同的网站风格,预留出了这三个参数让你可以自己定义上传框的背景、图片预览框的背景和上传按钮的背景,默认留空情况下就是同学们看到的样式。
其次是compressSide和compressLength这两个参数。前者规定了上传图片等比压缩的基准边,后者规定了前者定义的基准边的最大边长,并以该值为基准进行等比缩放。此处的值设置是通过读取配置文件中的maxImageSideLength值来进行,嫌麻烦的可以直接设置数值即可。
第三个是url参数。这个参数设置了图片上传后台处理程序的路径。以当前项目示例来说,此处的url值应该是“../../../admin/server/upload/up.php”。
第四个要介绍的参数是ext。这个参数允许让你自己定义需要向后端post的参数。只要书写符合我们demo中的格式,后台就可以直接像接收一个普通的表单项一样的方式来接收对应的自定义值。
最后一个需要要介绍的参数是fileType。它在前端限定了文件选择框中能够显示的文件类型,如果需要上传其他非图片格式的文件,你只要修改对应的扩展名即可。
前端部分的配置暂时结束,下面来看后台处理程序中的配置。打开admin/server/upload/文件夹,在up.php文件中,我们可以发现需要配置的参数有以下三个uploadPath, fileType 和 fileSize。有你后两个参数的存在表示疑问,认为前端已经通过Flash限制了文件大小和文件类型,此处不需要再进行限制。这种认识其实是不对的,有经验的hacker们完全可以通过自己构造提交表单来绕开前端Flash的各种限制,直接上传文件到我们的服务器。
我们重点来看uploadPath这个参数。默认状态下,这个参数的配置会在up.php文件所在目录的父目录中创建一个uploadfiles文件夹,并将图片保存在这个文件夹中。完成图片的保存之后需要返回的字符串是一个纯粹的json字符串,形如
{'url':'../uploadfiles/23123213.jpg','title':'描述','state':'SUCCESS'}
此处需要说明的一点是url这个参数。可以看到,当前这个url参数中携带了“../”这样的相对路径,这种表示方式由于具有明显的上下文含义,因此不大适合于用来进行跨页面的数据传递。如果非要使用这种路径传递方式,那么必须在图片显示页面进行地址修正。 一般来说,比较正确的做法是在返回给客户端之前将相对路径转换成带域名的绝对路径,这一步操作一般应该结合后台的网站根目录配置来完成。UEditor不希望了解具体的服务器端配置,因此在此处进行了另外一种处理:去掉所有./以及../等相对路径的字符串,只返回从uploadfiles开始的图片路径,如“uploadfiles/23123213.jpg”, 然后通过在editor_config.js中配置imagePath这个参数来修正图片的真实地址。imagePath代表的含义是uploadfiles文件夹所在的文件夹地址。以当前实例项目为例,uploadfiles位于server目录底下,所以imagePath的值应该设置为“admin/server/”。此处之所以从根目录开始,原因同完整版部署时候说的一样,是因为editor_config.js被index.php导入,所以其真实路径就是根目录。 另外一个state参数则是由后台的具体逻辑来确定,其值可自定义,但如果上传成功,必须返回“SUCCESS”字符串,其他状态将直接在预览框中展示。
到此为止,图片上传所需要的所有配置和修改就已经完成了。在地址栏中输入地址,体验下UEditor提供的强大的图片上传功能吧!
PS:JSP版的图片上传采用了commons-fileupload包,请先下载commons-fileupload-1.2.2.jar,并将其加入到项目中的WEB-INF/lib/目录下方可正常使用
国内优秀web前端Javascript框架库:Arale(支付宝) - http://aralejs.org/
Como(盛大) - http://www.comsome.com
EasyJs - https://github.com/chenmnkken/easyjs
EdoJs - http://www.edojs.com/
DWZ - http://j-ui.com/
JX(腾讯) - http://alloyteam.github.com/JX
JSI - http://code.google.com/p/jsi/
KISSY(淘宝) - http://www.kissyui.com
KindEditor - https://github.com/kindsoft/kindeditor
Mass - https://github.com/RubyLouvre/mass-Framework
ModJS(腾讯) - http://madscript.com/modjs/
ModuleJS(腾讯) - http://modulejs.github.com/modulejs/
NEJ(网易) - http://nej.netease.com/
NJF - http://code.google.com/p/njf/
Puerh(百姓网) - https://github.com/baixing/Puerh
QWrap(百度) - http://www.qwrap.com
SeaJS(淘宝) - http://seajs.org/
Tangram(百度) - http://tangram.baidu.com
UEditor(百度) - http://ueditor.baidu.com
与 mouseenter 事件不同,不论鼠标指针穿过被选元素或其子元素,都会触发 mouseover 事件。只有在鼠标指针穿过被选元素时,才会触发 mouseenter 事件。与 mouseout 事件不同,只有在鼠标指针离开被选元素时,才会触发 mouseleave 事件。如果鼠标指针离开任何子元素,同样会触发 mouseout 事件。
解决两者的区别,看下面引用的例子:
当为某个容器绑定了 onmouseover 或者onmouseout 事件时,如果这个容器中有其它元素节点,那么鼠标在内部移动时会频繁触发 onmouseover和onmouseout 事件。
而我想要的效果是:事件仅在鼠标进入/离开元素区域触发一次,当鼠标在元素区域内部移动的时候不会触发。
为什么会出现这个原因呢?其实是因为事件冒泡导致的。当鼠标移上或者移出容器中的子节点时,会分别触发mouseover和mouseout事件,紧随着dom树向上冒泡传递,直到被事件处理程序(监听器)捕获捕获或者冒泡到根节点(document或者window),也就是说事件会向它的父级对象派发。
知道了问题产生原因,那么解决起来是不是也很简单呢?最初我想的是取消事件冒泡,使用event.cancelBubble = true(IE)和e.stopPropagation()(其它浏览器),但是简单测试后发现貌似没有什么效果,问题依旧,貌似冒泡停止不了,原因不明。(补充:我是测试将容器中的a链接节点取消冒泡,但是发现鼠标移上移下还会触发事件。a节点下还有span节点。难道要将容器中所有节点都取消冒泡才行?有心人可以测试,如果真的这样,那也太恶心了,要是N多的节点,难道都要停止冒泡下?)
其实在IE下鼠标事件有个 mouseEnter 和 mouseLeave,这个就是移进和移处容器时触发一次,在内部移动则不会触发,遗憾的是只有IE支持。我们现在要做的就是“为非IE浏览器添加mouseEnter和mouseLeave支持”。
我翻阅了百度最新开源的JS库tangram,看了里面的处理,发现貌似是单独处理了非IE浏览器下的事件,使用一个叫“baidu.event._eventFilter._crossElementBoundary(listener, e)”的方法修正mouseover和mouseout,然后封装了个mouseEnter和mouseLeave事件。
baidu.event._eventFilter._crossElementBoundary = function(listener, e){
var related = e.relatedTarget,
current = e.currentTarget if(typeof related == 'undefined'){return listener.call(current, e)
}// 如果current和related都是body,contains函数会返回false
// Firefox有时会把XUL元素作为relatedTarget
// 这些元素不能访问parentNode属性
// thanks jquery &mootools
//如果current包含related,说明没有经过current的边界
//注:baidu.dom.contains是个定义的检测节点是否包含的函数,下面我会讲到
if(related === false || current == related || related.prefix == 'xul' || baidu.dom.contains(current, related)){ return
}//调用执行
return listener.call(current, e)
}123456789101112131415161718
百度的方法我并不喜欢,首先它只对非IE浏览器进行了处理,当然,它又进行了封装,可以直接使用mouseEnter和mouseLeave;但是,我们做普通开发,没必要这么封装,我只是想要简单的去掉mouseover和mouseout的这个恼人特性。
而jQuery则不是这么做的,它是直接对IE和其它所有浏览器下的mouseover和mouseout事件进行了修正。参考jQuery,我得到了我目前所有的代码。
首先,介绍个判断节点对象是否包含的函数contains.
function contains(p,c){
return p.contains ?
p != c &&p.contains(c) : !!(p.compareDocumentPosition(c) &16)
}1234
然后就是重点的了,这里我们在IE下用到了fromElement和toElement,这两个是IE下的鼠标移上去时和移出时的节点对象。
function fixedMouse(e,target){
var related,type=e.type.toLowerCase()//这里获取事件名字
if(type=='mouseover'){
related=e.relatedTarget||e.fromElement
}else if(type='mouseout'){
related=e.relatedTarget||e.toElement
}else return true return related &&related.prefix!='xul' &&!contains(target,related) &&related!==target
}12345678910
然后我们怎么用呢?比如在绑定事件时,
//addListener为封装的事件绑定函数addListener(target,'mouseover',function(e){
e=e||window.event if(fixedMouse(e, target)){//do something
}
},false)1234567
这样就会只在移入移出target节点时触发mouseover和mouseout了。
当然,你也可以将上面的代码单独封装成mouseEnter和mouseLeave,这样可以以后调用时更好区别mouseover和mouseout。