过去我曾经分析过日志里的图片上传机制 (
链接),讨论过在那里使用基于Flash的SWFUpload组件的必要性。但没想到论坛里的类似功能提供了基于Flash技术和不基于Flash技术的两套上传方案,按浏览器是否支持Flash来选择用哪套方案。本文分析一下它的机制,并将它扩展到日志,文章和相册里。
首先注意整个发表贴子的网页所用的模板是 forum/post.htm,它引用了 javascript 文件 static/js/forum_post.js。它还嵌入了两个子模板 forum/editor_menu_forum.htm 和 common/upload.htm,而后者引用了 javascript 文件 static/js/upload.js。
post.htm:
upload.htm:
{eval $_G['uploadjs'] = 1;}
1。我们先来分析是在哪里选择是否用基于Flash的上传方法的:
下面是在Firefox里的图片上传界面截屏,第一张是在Add-ons Manager激活了Shockwave Flash 的情形,第二张是不激活的情形:
从中可见,在这两种情形下,出现的是同一个弹出窗口,但所用的TAB不同。这四个TAB (上传图片,普通上传,相册图片,网络图片)定义在editor_menu_forum.htm 里:
- {lang close}
- < a href="javascript:;" hidefocus="true" onclick="switchImagebutton('imgattachlist');">{lang e_img_attach}
- < a href="javascript:;" hidefocus="true" onclick="switchImagebutton('local');">{lang normal_upload}
- < a href="javascript:;" hidefocus="true" onclick="switchImagebutton('albumlist');">{lang e_img_albumlist}
- < a href="javascript:;" hidefocus="true" onclick="switchImagebutton('www');">{lang e_img_www}
注意在定义普通上传TAB时用的属性
did="{$editorid}_btn_imgattachlist|local",这个信息告诉下面的代码上传图片TAB和普通上传TAB会有一个被隐藏:
upload.js:
function disableMultiUpload(obj) {
if(obj.uploadSource == 'forum' && obj.uploadFrom != 'fastpost') {
try{
obj.singleUpload.style.display = '';
var dIdStr = obj.singleUpload.getAttribute("did");
if(dIdStr != null) {
if(typeof forum_post_inited == 'undefined') {
appendscript(JSPATH + 'forum_post.js?' + VERHASH);
}
var idArr = dIdStr.split("|");
$(idArr[0]).style.display = 'none';
if(idArr[1] == 'local') {
switchImagebutton('local');
} else if(idArr[1] == 'upload') {
switchAttachbutton('upload');
}
}
} catch (e) {
}
}
}
function preLoad() {
if(!this.support.loading) {
disableMultiUpload(this.customSettings);
return false;
}
}
function loadFailed() {
disableMultiUpload(this.customSettings);
}
var SWFUpload;
var swfobject;
if (SWFUpload == undefined) {
SWFUpload = function (settings) {
this.initSWFUpload(settings);
};
}
SWFUpload.prototype.initSWFUpload = function (userSettings) {
...
this.loadSupport();
...
}
SWFUpload.prototype.loadSupport = function () {
this.support = {
loading : swfobject.hasFlashPlayerVersion("9.0.28"),
imageResize : false
};
};
post.htm:
var imgUpload = new SWFUpload({
...
swfupload_preload_handler : preLoad,
swfupload_load_failed_handler : loadFailed,
...
SWFUpload 定义在 upload.js 里,它在处理 preload 事件会检查浏览器是否支持 Flash。如果不支持就会调用 disableMultiUpload 来隐藏图片上传TAB而显示普通上传TAB。同时它需要用到Flash。所以当浏览器里的Flash没有被激活时,它的 load_failed 事件处理代码就会被调用,它同样会调用 disableMultiUpload。
2。接下来我们来分析一下不用Flash时的选择文件的代码:

上传图片列表和上传按钮定义在文件 editor_menu_forum.htm 里:
上传文件列表是 imgattachbody,它的内容是以 imgattachbodyhidden 为模版生成的。还有一个上传表单(form)列表 imgattachbtn,它的内容是以 imgattachbtnhidden 为模版生成的。接下来的代码显示了它们的生成过程。
当用户选择了一个文件后,界面上的上传文件列表会更新,上传表单列表也会更新,这是由这个选择文件按钮的 change 事件处理代码提供的功能: 注意下面的语句调用了一个按模版产生选择文件表单的函数,其中对选择文件按钮加了事件处理来达到前述效果。
post.htm:
addAttach('img');
forum_post.js:
function addAttach(prefix) {
var id = AID[prefix ? 1 : 0];
var tags, newnode, i;
prefix = isUndefined(prefix) ? '' : prefix;
newnode = $(prefix + 'attachbtnhidden').firstChild.cloneNode(true);
tags = newnode.getElementsByTagName('input');
for(i = 0;i < tags.length;i++) {
if(tags[i].name == 'Filedata') {
tags[i].id = prefix + 'attachnew_' + id;
tags[i].onchange = function() {insertAttach(prefix, id);};
tags[i].unselectable = 'on';
} else if(tags[i].name == 'attachid') {
tags[i].value = id;
}
}
tags = newnode.getElementsByTagName('form');
tags[0].name = tags[0].id = prefix + 'attachform_' + id;
$(prefix + 'attachbtn').appendChild(newnode);
newnode = $(prefix + 'attachbodyhidden').firstChild.cloneNode(true);
tags = newnode.getElementsByTagName('input');
for(i = 0;i < tags.length;i++) {
if(tags[i].name == prefix + 'localid[]') {
tags[i].value = id;
}
}
tags = newnode.getElementsByTagName('span');
for(i = 0;i < tags.length;i++) {
if(tags[i].id == prefix + 'localfile[]') {
tags[i].id = prefix + 'localfile_' + id;
} else if(tags[i].id == prefix + 'cpdel[]') {
tags[i].id = prefix + 'cpdel_' + id;
} else if(tags[i].id == prefix + 'localno[]') {
tags[i].id = prefix + 'localno_' + id;
} else if(tags[i].id == prefix + 'deschidden[]') {
tags[i].id = prefix + 'deschidden_' + id;
}
}
AID[prefix ? 1 : 0]++;
newnode.style.display = 'none';
$(prefix + 'attachbody').appendChild(newnode);
}
function insertAttach(prefix, id) {
var path = $(prefix + 'attachnew_' + id).value;
var extpos = path.lastIndexOf('.');
var ext = extpos == -1 ? '' : path.substr(extpos + 1, path.length).toLowerCase();
var re = new RegExp("(^|\\s|,)" + ext + "($|\\s|,)", "ig");
var localfile = $(prefix + 'attachnew_' + id).value.substr($(prefix + 'attachnew_' + id).value.replace(/\\/g, '/').lastIndexOf('/') + 1);
var filename = mb_cutstr(localfile, 30);
if(path == '') {
return;
}
if(extensions != '' && (re.exec(extensions) == null || ext == '')) {
reAddAttach(prefix, id);
showError('对不起,不支持上传此类扩展名的附件。');
return;
}
if(prefix == 'img' && imgexts.indexOf(ext) == -1) {
reAddAttach(prefix, id);
showError('请选择图片文件(' + imgexts + ')');
return;
}
$(prefix + 'cpdel_' + id).innerHTML = '< a href="javascript:;" class="d" onclick="reAddAttach(\'' + prefix + '\', ' + id + ')">删除';
$(prefix + 'localfile_' + id).innerHTML = '' + filename + '';
$(prefix + 'attachnew_' + id).style.display = 'none';
$(prefix + 'deschidden_' + id).style.display = '';
$(prefix + 'deschidden_' + id).title = localfile;
$(prefix + 'localno_' + id).parentNode.parentNode.style.display = '';
addAttach(prefix);
UPLOADSTATUS = 0;
}
上传按钮的值(value)只有在IE里才记录了所选文件的路径和文件名,在Firefox和Chrome里出于安全考虑都只记录了文件名。
3。接下来我们来分析一下不用Flash时的上传文件的代码。 上传按钮定义在接下来的代码里:
←{lang upload_after_selected}
{lang uploading}
当用户点击上传按钮后,处理 onclick 事件的代码调用了定义在 forum_post.js 里的函数 uploadAttach:
function uploadAttach(curId, statusid, prefix, sizelimit) {
prefix = isUndefined(prefix) ? '' : prefix;
var nextId = 0;
for(var i = 0; i < AID[prefix ? 1 : 0] - 1; i++) {
if($(prefix + 'attachform_' + i)) {
nextId = i;
if(curId == 0) {
break;
} else {
...
$(prefix + 'attachform_' + nextId).submit();
}
注意这个函数的结尾处将该表单 (form) 提交给了服务器。但是用户在这个网页上可能已经选择了多个图片,每个选择对应着一个上传表单。如何将每个表单里选择的图片都提交给服务器呢?注意表单的 target 属性的值是 attachframe ,对应着定义在 editor_menu_forum.htm 里的下述 iframe:
所以图片提交后会调用定义在 forum_post.js 里的函数 uploadNextAttach:
function uploadNextAttach() {
var str = $('attachframe').contentWindow.document.body.innerHTML;
if(str == '') return;
var arr = str.split('|');
var att = CURRENTATTACH.split('|');
var sizelimit = '';
if(arr[4] == 'ban') {
sizelimit = '(附件类型被禁止)';
} else if(arr[4] == 'perday') {
sizelimit = '(不能超过 ' + arr[5] + ' 字节)';
} else if(arr[4] > 0) {
sizelimit = '(不能超过 ' + arr[4] + ' 字节)';
}
uploadAttach(parseInt(att[0]), arr[0] == 'DISCUZUPLOAD' ? parseInt(arr[1]) : -1, att[1], sizelimit);
}
而在它的结尾处又调用了uploadAttach,周而复始,从而将所有选择的图片都上传到服务器了。
4。那么论坛里不用Flash的上传文件方法能不能用于日志(以及文章)呢?我们先对照一下两者用到的相关代码文件结构:
forum_post.php
defines $editorid = 'e'
includes post_newthread.php/post_newreply.php/post_editpost.php
template post.htm
includes forum_post.js
defines addAttach
subtemplate post_editor_body.htm
subtemplate editor.htm
defines editorid = '$editorid';
subtemplate editor_menu_forum.htm
defines switchImagebutton
subtemplate upload.htm
includes upload.js
defines SWFUpload class
creates SWFUpload object
calls addAttach('img')
home_spacecp.php
includes spacecp_blog.php
template spacecp_blog.htm
includes editor_function.js
subtemplate editor_image_menu.htm
subtemplate upload.htm
includes upload.js
defines SWFUpload class
creates SWFUpload object
defines switchImagebutton
大致上说 editor_image_menu.htm 和 editor_menu_forum.htm 对应定义了上传文件的对话框的内容,而 editor_function.js 和 forum_post.js 对应定义了上传文件的对话框的在客户端的功能。这两个是我们要重点修改的文件。
我们先做些辅助性的改动:
1) 文件 static/js/upload.js 里的函数 disableMultiUpload 引发了在支持和不支持Flash时在上传文件对话框里选择显示合适的TAB。我们要加入对日志和文章的支持。将下面的代码
function disableMultiUpload(obj) {
if(obj.uploadSource == 'forum' && obj.uploadFrom != 'fastpost') {
try{
obj.singleUpload.style.display = '';
var dIdStr = obj.singleUpload.getAttribute("did");
if(dIdStr != null) {
if(typeof forum_post_inited == 'undefined') {
appendscript(JSPATH + 'forum_post.js?' + VERHASH);
}
修改为
function disableMultiUpload(obj) {
if ((obj.uploadSource == 'portal' || obj.uploadSource == 'forum') && obj.uploadFrom != 'fastpost') {
try {
obj.singleUpload.style.display = '';
var dIdStr = obj.singleUpload.getAttribute("did");
if (dIdStr != null) {
if (obj.uploadSource == 'forum') {
if (typeof forum_post_inited == 'undefined') {
appendscript(JSPATH + 'forum_post.js?' + VERHASH);
}
}
2) 接下来在文件 static/image/editor/editor_function.js 里加入下面的函数来支持上传文件的功能:
function uploadFile(prefix) {
prefix = isUndefined(prefix) ? '' : prefix;
var path = $(prefix + 'attachnew').value;
var extpos = path.lastIndexOf('.');
var ext = extpos == -1 ? '' : path.substr(extpos + 1, path.length).toLowerCase();
var re = new RegExp("(^|\\s|,)" + ext + "($|\\s|,)", "ig");
var localfile = $(prefix + 'attachnew').value.substr($(prefix + 'attachnew').value.replace(/\\/g, '/').lastIndexOf('/') + 1);
var filename = mb_cutstr(localfile, 30);
if (path == '') {
return;
}
if (extensions != '' && (re.exec(extensions) == null || ext == '')) {
showError('对不起,不支持上传此类扩展名的附件。');
return;
}
if (prefix == 'img' && imgexts.indexOf(ext) == -1) {
showError('请选择图片文件(' + imgexts + ')');
return;
}
var form = $(prefix + 'attachnew').form;
if ($('formaction')) {
$('formaction').value = form.action;
form.action = $('localformaction').value;
form.target = (prefix == 'img') ? 'imageframe' : 'attachframe';
if (prefix != 'img') $('imgattachnew').name = '';
if (prefix != '') $('attachnew').name = '';
}
form.submit();
}
function uploadImageDone() {
if ($('formaction').value) {
$('formaction').form.action = $('formaction').value;
$('formaction').form.target = "";
$('imgattachnew').name = 'Filedata';
$('attachnew').name = 'Filedata';
}
var str = $('imageframe').contentWindow.document.body.innerHTML;
if (str == '') return;
var data = eval('(' + str + ')');
if (parseInt(data.picid)) {
var tdObj = getInsertTdId($('imgattachlist'), 'image_td_' + data.picid);
var img = new Image();
img.src = data.url;
var imgObj = document.createElement("img");
imgObj.src = img.src;
imgObj.className = "cur1";
imgObj.onclick = function () { insertImage(data.bigimg); };
tdObj.appendChild(imgObj);
var inputObj = document.createElement("input");
inputObj.type = 'hidden';
inputObj.name = 'picids[' + data.picid + ']';
inputObj.value = data.picid;
tdObj.appendChild(inputObj);
}
else if (data.aid) {
var tdObj = getInsertTdId($('imgattachlist'), 'attach_list_' + data.aid);
ajaxget('portal.php?mod=attachment&op=getattach&id=' + data.aid, tdObj.id);
}
else {
showDialog('图片上传失败', 'notice', null, null, 0, null, null, null, null, sdCloseTime);
}
}
function uploadAttachmentDone() {
if ($('formaction').value) {
$('formaction').form.action = $('formaction').value;
$('formaction').form.target = "";
$('imgattachnew').name = 'Filedata';
$('attachnew').name = 'Filedata';
}
var str = $('attachframe').contentWindow.document.body.innerHTML;
if (str == '') return;
var data = eval('(' + str + ')');
if (data.aid) {
var tdObj = $('attachlist');
var fileObj = document.createElement("div");
fileObj.id = 'FileUpload_' + tdObj.childNodes.length;
tdObj.appendChild(fileObj);
ajaxget('portal.php?mod=attachment&op=getattach&type=attach&id=' + data.aid, fileObj.id);
if ($('attach_tblheader')) {
$('attach_tblheader').style.display = '';
}
}
else {
showDialog('上传失败', 'notice', null, null, 0, null, null, null, null, sdCloseTime);
}
}
它们效仿了 static/js/forum_post.js 里的函数 uploadAttach 和 uploadNextAttach的框架,而其中后两个函数的代码参照了日志和文章在支持Flash时处理(static/js/upload.js 里的 uploadSuccess 函数)。
再对原有的函数 switchButton 略做改动,使之同时适用于图片上传和附件上传:
function switchButton(btn, type) {
var pre = 'ico' + ((type == 'attach') ? 'Attach' : 'Img') + '_';
var btnpre = pre + 'btn_';
if (!$(btnpre + btn) || !$(pre + btn)) {
return;
}
var tabs = $(pre + type + '_ctrl').getElementsByTagName('LI');
$(btnpre + btn).style.display = '';
$(pre + btn).style.display = '';
$(btnpre + btn).className = 'current';
var btni = '';
for(i = 0;i < tabs.length;i++) {
if(tabs[i].id.indexOf(btnpre) !== -1) {
btni = tabs[i].id.substr(btnpre.length);
}
if(btni != btn) {
if (!$(pre + btni) || !$('icoImg_btn_' + btni)) {
continue;
}
$(pre + btni).style.display = 'none';
$(btnpre + btni).className = '';
}
}
}
3) 最后我们要对修改定义上传文件对话框的模板文件 template/default/home/editor_image_menu.htm 做多处改动:
3a) 在文件的开头加上
3b) 在图片对话框里要添加一个TAB,它在没有 Flash 的环境里替代了原来的"上传图片"TAB。先添加新TAB的标题: 在下面这句
{lang upload_pic}
后加上
{lang upload_pic}
再添加新TAB的内容: 在下面这句
前加上
3c) 对文章里的附件上传要做类似的改动: 在下面这句
{lang upload_attach}
后加上
{lang upload_attach}
在下面这句
前加上
3d) 还要加些支持上传的内容:
将下面这句
改为:
3e) 还要告诉SWFUpload的两个实例如何处理不支持 Flash 的情形。将下面这句
imgBoxObj: $('attachlist')
改为
imgBoxObj: $('attachlist'),
singleUpload: $('icoAttach_btn_upload')
将下面这句
imgBoxObj: $('imgattachlist')
改为
imgBoxObj: $('imgattachlist'),
singleUpload: $('icoImg_btn_local')
3f) 最后将该文件后面的函数 switchImagebutton 的定义删掉:
function switchImagebutton(btn) {
switchButton(btn, 'image');
$('icoImg_image_menu').style.height = '';
doane();
}
这些修改借鉴了template/default/forum/editor_menu_forum.htm 里的代码。
5。最后我们讨论一下在相册里不用Flash的上传图片方法。

首先将 static/js/upload.js 里的函数 disableMultiUpload 的第一行
if ((obj.uploadSource == 'portal' || obj.uploadSource == 'forum') && obj.uploadFrom != 'fastpost') {
改为
if ((obj.uploadSource == 'home' || obj.uploadSource == 'portal' || obj.uploadSource == 'forum') && obj.uploadFrom != 'fastpost') {
其次在文件 static/image/editor/editor_function.js 里再添加一个辅助函数来支持在相册里上传图片的功能:
function uploadToAlbumDone() {
var str = $('imageframe').contentWindow.document.body.innerHTML;
if (str == '') return;
var data = eval('(' + str + ')');
if (parseInt(data.picid)) {
var newTr = document.createElement("TR");
var newTd = document.createElement("TD");
var img = new Image();
img.src = data.url;
var imgObj = document.createElement("img");
imgObj.src = img.src;
newTd.className = 'c';
newTd.appendChild(imgObj);
newTr.appendChild(newTd);
newTd = document.createElement("TD");
var localfile = $('imgattachnew').value.substr($('imgattachnew').value.replace(/\\/g, '/').lastIndexOf('/') + 1);
var filename = mb_cutstr(localfile, 30);
newTd.innerHTML = '' + filename + '';
newTr.appendChild(newTd);
newTd = document.createElement("TD");
newTd.className = 'd';
newTd.innerHTML = '图片描述
';
newTr.appendChild(newTd);
$('attachbody').appendChild(newTr);
} else {
showDialog('图片上传失败', 'notice', null, null, 0, null, null, null, null, sdCloseTime);
}
}
和前面提到的函数类似,这个函数也是模仿了static/js/upload.js 里的 uploadSuccess 函数中的相关部分。
最后我们要修改定义相册上传图片网页的模板文件 template/default/home/spacecp_upload.htm。要做下面五部分的修改:
1)在第一行
的下面插入:
2)要加上一个在不支持Flash情形下显示的TAB,所以要加一个LI,还有给已有的元素也加上ID。找到下面这段:
- < a href="home.php?mod=spacecp&ac=album&op=edit&albumid=$albumid" target="_blank">{lang edit_album_information}
- < a href="home.php?mod=spacecp&ac=album&op=editpic&albumid=$albumid" target="_blank">{lang edit_pic}
- < a href="home.php?mod=spacecp&ac=upload&albumid=$albumid" target="_blank">{lang common_upload}
将它改为
- < a href="home.php?mod=spacecp&ac=album&op=edit&albumid=$albumid" target="_blank">{lang edit_album_information}
- < a href="home.php?mod=spacecp&ac=album&op=editpic&albumid=$albumid" target="_blank">{lang edit_pic}
- < a href="home.php?mod=spacecp&ac=upload&albumid=$albumid" target="_blank">{lang common_upload}
- < a href="javascript:;" hidefocus="true" onclick="switchImagebutton('local');">{lang common_upload}
3)我们要将现有的表单的范围缩小一点,使得新加的上传图片的表单与它没有重叠之处。找到